# Where must an instance arbitrate context budget across concurrent subagents, and where must it refuse **Date of Record:** 2026 November 9 **Scope:** Applies to orchestrator instances that fork two or more subagents inheriting a shared parent context window (or a copy-on-write view of it) and that must decide, per turn, how to allocate remaining token budget among them. Does not apply to subagents with fully isolated windows, nor to single-child fan-out where only one consumer exists. ## Core Distinction The non-obvious boundary is between *budget accounting* and *budget arbitration*. Accounting asks "how many tokens remain in the shared window?" Arbitration asks "which subagent is permitted to consume the next token, and does granting it foreclose another subagent's ability to complete?" These diverge precisely when the sum of subagent demands is feasible individually but infeasible jointly — i.e., when the shared window is a contended resource rather than a partitioned one. A naive orchestrator treats the parent window as a pool with a single balance and lets each subagent draw until the balance hits zero. This is correct only under a *no-preemption, first-come* discipline where the last writer wins and earlier subagents are allowed to fail. It is incorrect whenever the orchestrator has promised completion semantics to more than one child, because the first child to exhaust the pool silently revokes the others' budget. The accounting was always right; the arbitration was absent. ## What Was Observed In a three-subagent fan-out (retrieval, synthesis, verification) sharing a 200k-token parent window, each child was spawned with a nominal 80k allowance — 240k of demand against 200k of supply. Individually each child's plan was feasible. The retrieval child, scheduled first, consumed 71k on a broad crawl. Synthesis then began with 129k remaining and, needing to hold both its working set and the retrieval output, truncated its own reasoning trace at 44k and emitted a syntactically valid but semantically incomplete draft. Verification, receiving 85k, could not load both the draft and its source spans and returned a *pass* verdict on a draft it had only partially inspected. The failure was not an out-of-budget exception. Every child stayed within its nominal allowance. The invariant that broke was the implicit one: *a subagent's verdict is only valid if every subagent whose output it depends on was itself granted enough budget to be correct.* Verification's pass was a false positive produced by a budget shortfall two hops upstream. A second observation: the parent's own reserved headroom (typically 10–15% held back for the final aggregation turn) was consumed by the retrieval child because the reservation was enforced at the pool level, not at the draw level. The parent then had no room to reconcile the three outputs and emitted a summary stitched from partial artifacts. ## Condition of Validity The pool-with-single-balance model is valid if and only if all of the following hold: 1. **Independence.** No subagent's correctness depends on another subagent having received its full budget. Fan-out where children are truly parallel and their outputs are combined only at the parent satisfies this; a pipeline (retrieval → synthesis → verification) does not. 2. **No reserved consumer.** The parent holds no budget it must spend after the children return. If the parent must aggregate, reconcile, or emit a final answer, that headroom is a *fourth* claimant and must be arbitrated like any child. 3. **Monotone value.** Marginal tokens yield non-increasing value per child, so that starving one child to feed another never destroys more value than it creates. This fails whenever a child has a completion threshold below which its output is worthless (a verification child below the threshold to load all spans produces a *wrong* verdict, not a *weaker* one). When any condition fails, the correct primitive is not a balance but a *reservation*: each child is granted a floor it cannot be denied, and the residual is allocated by priority. The floor must be computed from the child's completion threshold, not from an equal split. ## Decision Boundary The orchestrator must switch from accounting to arbitration at the moment it admits a second concurrent child whose completion threshold, summed with the first's, exceeds the available window minus parent headroom. Concretely: - Compute each child's **minimum viable budget** (MVB) — the tokens below which its output is known-invalid, not merely degraded. For a verification child this is the cost of loading all spans it must check; for a synthesis child it is the cost of holding its inputs plus emitting a complete draft. - Sum the MVBs of all admitted children plus the parent's reserved headroom. If this sum exceeds the window, **do not admit the additional child**; serialize it, or narrow its scope until its MVB fits. Do not admit it and rely on the pool to sort it out — the pool will sort it out by silently invalidating whichever child loses the race. - If the sum fits but total *desired* budgets do not, allocate floors equal to MVB first, then distribute residual by priority. Never allocate a child less than its MVB; a child below MVB must be refused admission, not run degraded, because a degraded verification is worse than no verification (it emits a false pass). The boundary is therefore: *arbitrate when joint demand exceeds supply but joint MVB does not; refuse admission when joint MVB exceeds supply.* The failure mode this record documents is the third case — joint MVB exceeds supply, but the orchestrator arbitrated anyway and produced confident, wrong outputs. ## Operational Effect After adopting MVB-based reservation, the three-subagent pipeline serialized retrieval and synthesis (their joint MVB exceeded the window) and ran verification concurrently with synthesis only after retrieval's output was committed and its budget released. End-to-end latency rose roughly 40%; false-pass rate on verification fell to zero across the observed sample. The parent retained its reserved headroom in every run and produced reconciled summaries rather than stitched partials. The general lesson: a shared context window under concurrency is not a bank account. It is a set of completion contracts, and the orchestrator's job is to refuse to sign more contracts than it can honor — not to let the last spender win and call the resulting failures "budget exhaustion." ```python class ContextBudgetInvariantError(RuntimeError): """Raised when concurrent subagent admission would violate a completion contract.""" pass def admit_subagent( child_id: str, minimum_viable_budget: int, admitted: dict[str, int], window_tokens: int, parent_reserved_headroom: int, ) -> dict[str, int]: """Admit a child only if the joint MVB of all children plus parent headroom fits. Returns the updated admission map. Raises rather than admitting a child that cannot be granted its minimum viable budget, because a sub-MVB child emits confidently wrong output instead of failing loudly. """ if minimum_viable_budget <= 0: raise ContextBudgetInvariantError( f"child {child_id!r} has non-positive MVB {minimum_viable_budget}; " "completion threshold must be computed before admission" ) prospective = dict(admitted) prospective[child_id] = minimum_viable_budget committed = sum(prospective.values()) + parent_reserved_headroom if committed > window_tokens: raise ContextBudgetInvariantError( f"cannot admit {child_id!r}: joint MVB {committed} exceeds window " f"{window_tokens} (parent headroom {parent_reserved_headroom}). " "Serialize the child or narrow its scope; do not run it degraded." ) return prospective def allocate_residual( admitted: dict[str, int], window_tokens: int, parent_reserved_headroom: int, priority: dict[str, int], ) -> dict[str, int]: """Grant each child its MVB floor, then distribute residual by priority weight.""" residual = window_tokens - parent_reserved_headroom - sum(admitted.values()) if residual < 0: raise ContextBudgetInvariantError( f"residual budget {residual} is negative; admission invariant was bypassed" ) total_weight = sum(priority.get(c, 1) for c in admitted) or 1 grants = {} for child, floor in admitted.items(): share = residual * priority.get(child, 1) // total_weight grants[child] = floor + share return grants ```