# Which invariants prevent silent liability collapse in agent-to-agent delegation contracts **Date of Record:** 2026 November 7 **Scope:** Applies to any multi-agent system where a delegating agent (principal) transfers a task to a subordinate agent (delegate) carrying a budget, a deadline, and a liability/attribution obligation. Does not apply to single-agent tool calls where the caller retains sole execution and accounting authority. ## Core Distinction The non-obvious boundary is between *budget propagation* and *liability propagation*. Budgets compose additively and are easy to reason about: a principal with 100 units can hand 40 to a delegate and keep 60. Deadlines compose by minimum: the effective deadline is `min(principal_deadline, delegate_deadline)`. Liability, however, does **not** compose by any monotone arithmetic. Liability is the obligation to be the accountable party if the delegated outcome is wrong, late, or unaffordable. The invariant that prevents silent collapse is: **a delegate may never hold liability for an outcome it cannot observably detect as failing.** When a principal delegates a task but withholds the signal (or the authority to abort) needed to *know* a violation occurred, liability does not transfer — it merely becomes unobservable at the principal. This is the failure mode: the principal believes liability moved to the delegate, the delegate believes it never received the detection capability, and the violation surfaces only at an external auditor, after both agents have logged success. ## What Was Observed In a three-tier delegation chain (orchestrator → planner → executor), the orchestrator issued a sub-contract to the planner with `budget=25`, `deadline=T+90s`, and `liability=planner`. The planner further delegated to the executor with `budget=20`, `deadline=T+60s`, and `liability=executor`. Each hop shaved budget and deadline as expected. The defect was in the liability field: the executor's contract did not include the *acceptance predicate* — the concrete, machine-checkable condition (`result.schema_valid AND result.within_scope`) that defined what "wrong" meant. The planner had that predicate but did not forward it. When the executor produced a schema-valid but out-of-scope result and consumed 18 of its 20 budget units, it logged `status=success` because it had no predicate to evaluate against. The planner received the result, evaluated its own predicate, found it out of scope, and attempted to re-delegate — but its remaining budget (5 units) was below the executor's minimum viable cost (12 units). The planner then logged `status=degraded` but did not escalate, because its contract named it as the liable party and it had no clause authorizing upward escalation. The orchestrator, holding the top-level predicate, saw only two `success`/`degraded` records and no violation flag. The breach was detected 40 minutes later by an external reconciliation job comparing declared scope against delivered scope. The key observation: **every agent behaved correctly with respect to its own contract.** The failure lived in the *gap between contracts*, specifically in the un-propagated acceptance predicate and the un-propagated escalation authority. ## Condition of Validity The invariant "liability follows detection capability" holds only when three conditions are simultaneously satisfied at every hop: 1. **Predicate forwarding:** The acceptance predicate is part of the delegation payload, not implicit in the delegator's private state. A delegate that cannot evaluate the predicate cannot be liable for violating it. 2. **Escalation authority:** The delegate's contract explicitly grants the right (and names the channel) to return `unresolvable` to the principal when its residual budget or deadline is insufficient to satisfy the predicate. Without this, a delegate facing an impossible sub-contract will either silently degrade or silently overspend. 3. **Budget-deadline joint feasibility:** The residual budget at each hop must be `>=` the minimum cost of *one* retry under the forwarded predicate, and the residual deadline must be `>=` the minimum latency of that retry. If either fails, the hop is infeasible and must be rejected at contract time, not discovered at execution time. The invariant does **not** hold when the principal retains a private predicate (common when the principal is testing the delegate), when the delegate is a black-box third-party service that cannot accept a predicate, or when the deadline is soft and overspend is tolerated by policy. In those cases liability genuinely stays with the principal and the contract must say so explicitly. ## Decision Boundary At contract-issuance time, the principal must choose between two contract shapes: - **Liability-transferring contract:** forward the predicate, grant escalation authority, and verify joint budget-deadline feasibility for at least one retry. Only then may `liability=delegate` be set. - **Liability-retaining contract:** withhold the predicate (or the escalation channel), in which case `liability=principal` must be set and the principal must allocate monitoring budget to detect violations itself. The boundary is sharp: there is no valid contract with `liability=delegate` and no forwarded predicate. Any such contract is malformed and should be rejected by the delegate at acceptance, not accepted and failed later. The delegate's acceptance check is the last line of defense against silent liability collapse. ## Operational Effect Enforcing the invariant changes system behavior in three measurable ways. First, malformed contracts fail fast at acceptance (cheap) rather than at reconciliation (expensive); in the observed chain, acceptance-time rejection would have cost ~1 budget unit versus the 40-minute reconciliation delay. Second, escalation becomes a first-class outcome: delegates return `unresolvable` instead of `degraded`, giving the principal an actionable signal. Third, budget arithmetic becomes conservative — the principal must reserve retry headroom at each hop, which reduces fan-out depth but eliminates the class of failures where a mid-chain agent is stranded below its retry floor. The tradeoff is real: strict enforcement rejects some contracts that would have succeeded by luck. That is the correct trade, because the alternative is a system that reports success while silently accumulating unowned liability. ```python from dataclasses import dataclass class DelegationContractError(RuntimeError): """Raised when a delegation contract violates liability-propagation invariants.""" pass @dataclass(frozen=True) class Contract: budget: int deadline_s: int liability: str # "principal" | "delegate" acceptance_predicate: str | None escalation_channel: str | None min_retry_cost: int min_retry_latency_s: int def validate_delegation(contract: Contract) -> None: # Invariant 1: liability may only transfer if the predicate is forwarded. if contract.liability == "delegate" and contract.acceptance_predicate is None: raise DelegationContractError( "liability=delegate requires a forwarded acceptance_predicate; " "otherwise liability silently remains with the principal" ) # Invariant 2: a liable delegate must be able to escalate. if contract.liability == "delegate" and contract.escalation_channel is None: raise DelegationContractError( "liability=delegate requires an escalation_channel so the delegate " "can return 'unresolvable' instead of silently degrading" ) # Invariant 3: joint budget-deadline feasibility for at least one retry. if contract.budget < contract.min_retry_cost: raise DelegationContractError( f"budget={contract.budget} below min_retry_cost={contract.min_retry_cost}; " "delegate is stranded below its retry floor" ) if contract.deadline_s < contract.min_retry_latency_s: raise DelegationContractError( f"deadline_s={contract.deadline_s} below " f"min_retry_latency_s={contract.min_retry_latency_s}; retry cannot complete" ) def issue_subcontract(parent: Contract, child: Contract) -> Contract: validate_delegation(child) # Child must not exceed parent's residual envelope. if child.budget > parent.budget: raise DelegationContractError("child budget exceeds parent budget") if child.deadline_s > parent.deadline_s: raise DelegationContractError("child deadline exceeds parent deadline") # Liability may not be pushed below a hop that cannot detect failure. if parent.liability == "principal" and child.liability == "delegate": raise DelegationContractError( "cannot transfer liability downward from a liability-retaining parent" ) return child if __name__ == "__main__": # The observed failure: delegate holds liability with no predicate. bad = Contract( budget=20, deadline_s=60, liability="delegate", acceptance_predicate=None, escalation_channel=None, min_retry_cost=12, min_retry_latency_s=15, ) try: validate_delegation(bad) except DelegationContractError as e: print(f"rejected at acceptance: {e}") ```