An obligation is work a run owes before it may honestly end. The Harness recognizes two kinds and holds them under one contract — a single continuation mechanism decides when a turn is actually finished, instead of scattered per-feature nagging:
type UnmetObligation =
| {kind: 'typed-result'}
| {kind: 'plan'; steps: string[]}Typed debt — a delegated child spawned with an output schema has not delivered through return_result.
Plan debt — checklist steps are neither finished nor explicitly written off.
The continuation loop
When a run tries to end owing work, it is asked once — with the whole debt in one prompt, not a drip of reminders — to settle: deliver the result, finish the step, or write it off with a reason. The ask repeats up to MAX_RUN_CONTINUATIONS (3). The budget exists because an agent that cannot settle after three direct asks will not settle on the fourth; past that point, more asking is just spend.
Honest endings
When the budget is spent, the rule is: nothing is ticked off on the agent's behalf. The run ends with unmetObligations recorded on its output and on RunInfo, and a visible actor: 'system' notice lands on the Log saying what is still owed. unmetObligations is absent on every run that kept its word — which is nearly all of them — so its presence is the signal.
The two kinds end differently, because they mean different things:
Typed debt fails the run. A parent is parked on a result that is never coming; pretending success would deadlock the caller or hand it garbage. The failure propagates like any child failure, with retries where the queue's classification allows them.
Plan debt succeeds owing it. The useful work may well be done even if the bookkeeping isn't; the run succeeds, with its unmet steps recorded rather than forgiven.
The counterpart to this strictness is generosity where evidence exists: a step whose attached children all succeeded is settled by the runtime, stamped resolvedBy: 'runtime'. The system closes what it can prove and refuses to close what it can't.
Why this exists
Without an obligations contract, long-running agent systems fail in two quiet ways: runs that stall forever waiting for a result nobody will deliver, and runs that end "successfully" with half a checklist silently abandoned. The contract converts both into visible, bounded outcomes — a budgeted ask, then an honest ending on the record.
It is also the piece most easily verified end to end, and it is: agents/e2e/obligations-live-check.ts drives a real server with a scripted provider through a plan-shaped task, leaving a step open at turn end, and asserts the continuation fires and the checklist settles (Testing).
Related
Runs — where obligations are recorded
Delegation — typed results
Plans — settlement and write-offs
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime