handback
Terminal-message contract for an autonomous arc that has run out of agent-executable work and genuinely needs a human. Produces a WORK ORDER — asks first, each one an imperative addressed to the reade
Files2
View on GitHubSKILL.mdskills/orchestration/handback/SKILL.md
GitHubhandback — end an arc with a work order, not a lab notebook
Spec: broomva/workspace → docs/specs/2026-08-18-agent-handback-contract.html (BRO-2179).
Why this skill exists
Measured over 994 Claude Code transcripts (analysis set: 100 sessions of ≥6h wall-clock and ≥150 assistant turns):
| Long arcs whose final message mentions a blocker at all | 31 / 100 |
| Long arcs halting on a human (terminal-stance phrasing) | 18 / 100 |
| …of those 18, containing a conforming ask block | 0 / 18 |
| …containing an imperative anywhere in the message | 3 / 18 |
…containing any ? at all |
13% |
| …with the question next to the blocker it describes | 6% |
| Stating a default if the human stays silent | 5% of all 100 |
| Ever emitting a push notification | 0 / 100 |
| Blockers knowable before the arc started | 81% |
The two blocker rows differ on purpose. The broad count (31) includes messages that merely mention being blocked — "the pre-commit hook blocked it", "#381 remains blocked by the hang". The narrow count (18) is the defensible one: a terminal message that stops the arc on a person. The enforcement gate keys on the narrow definition, because refusing a healthy receipt that happens to contain the word "blocked" would fight the operator.
The ask usually exists. It is placed last, written in the indicative ("Not merged — auto-merge correctly blocked"), and carries no default, so the arc dies on it — median 2.4h of stalled arc-time behind such a message, 25 of them overnight-length.
Rule zero — asking is the last resort, not the interface
No ask may exist until every rung below has been tried and recorded. A row
with an empty exhausted list is a defect, not a question.
| Rung | Try this | |
|---|---|---|
| 1 · Read | Is the answer already on disk — ticket history, prior conversations, the knowledge graph, the handoff doc, the code? | Most "decisions" are recoverable, not new |
| 2 · Standing grant | Has this exact question been answered before? Check .control/preauth.yaml |
Re-asking a settled question is the cheapest failure to eliminate |
| 3 · Delegate | Can a fresh agent resolve it — a reader, a researcher, an adversarial reviewer with different context? | Chain agents instead of chaining to the human |
| 4 · Reroute | Can another lane, worktree, fan-out, or approach go around it? | The blocker is often a path, not a wall |
| 5 · Loop | Can iteration resolve it — retry under a changed assumption, let a persist loop converge? | Slow beats blocked when nobody is awake |
| 6 · Default | Is the choice reversible? Take it, log it, tell the human afterwards. Do not ask. — "log it" means an entry in the ledger's decisions:, and "tell the human" means the 🔀 block below |
Reversible decisions are not the human's to make in real time |
| 7 · Ask | Only now. Record which rungs were tried and why each failed. |
Rung 6 is where most asks should die. A choice that can be undone with one commit does not warrant stopping an arc; it warrants a line in the receipt. Only actions that are irreversible, externally visible, or spend money may reach rung 7 without a default — and the ask must say which.
Rule minus-one — never stop the arc while an unblocked lane remains
Blocked is a lane state, not an arc state. When an ask fires: park the
lanes it gates, emit the ask (push it if outside the waking window), and
continue on the next unblocked lane. Run handback only when the unblocked set
is empty. An arc that halts with runnable work left is the failure this skill
exists to prevent.
The shape — six blocks, always in this order
"Always in this order" constrains order, never presence. A block with no content is omitted, not rendered empty — an empty 🔀 block would assert that nothing was decided, and an empty ⛔ block would assert that nothing is blocked. Only the receipt is unconditional.
## ⛔ Blocked on you — N items, ~M min
table: # | ask (imperative, addressed to "you") | unblocks | if you say nothing
a decision row lists options A/B/C, one marked suggested, each with its cost
plain language only; hard cap 7 rows, ranked by what each unblocks
## ▶ Running meanwhile
what is still executing, or what ran with zero input from you
## ✅ Shipped
PR table. One line each. No narrative.
## 🔀 Decided for you — N choices, least-confident first
table: # | what I decided | why it was mine to take | undo
rendered from the ledger's `decisions:` list, never free prose
every row carries its one-line reversal — that is what made it safe not to ask
hard cap 7, same as the ask block; overflow counted in the header
## 📋 Receipt — the nine items, unchanged
1 dep chain · 2 plan vs done · 3 parallel streams · 4 files changed · 5 PRs
6 deploys · 7 validation · 8 merge result · 9 follow-ups
## 📎 Detail → docs/handoffs/<arc>.md
the NARRATIVE lives there: what happened and why, the review sagas, the
corrections. That prose is what this contract displaces.
The ten rules
Each is derived from a measured failure, not a style preference.
- The ask block is first. Not a "Your call" section at 80% depth. Measured: the ask lands around p80 of the message.
- Every row is an imperative addressed to "you" — a command to run, a link to click, a value to paste, or a one-line answer to a closed question. Measured: 0 of the 18 halting messages contained a conforming ask block.
- A decision row offers options, not an open question. Two or three concrete choices, each with its consequence in one line, one marked as the recommendation. Never "what should we do about X?" — always "A, B, or C; I suggest B because …".
- Plain language, no internal vocabulary. No primitive numbers, no gate names, no acronyms, no ticket ID standing in for the question. If a term needs the spec to understand, it does not belong in an ask.
- Every row carries a default. "If you say nothing, I do X." A row with no safe default must say so explicitly and name what it costs. Measured: 5% stated a default. Silence is otherwise fatal.
- Every row is self-contained. Answerable without opening a ticket. The ticket ID is a reference for later, never the carrier of the question. Measured: 58% used a ticket as the carrier; median 1 ticket, up to 13.
- Rows are ranked by what they unblock, stated in the row — "unblocks 9 of 18 rows", "unblocks 1 lane". A tired person answering one thing should be answering the right one.
- Hard cap: 7 rows. More than seven means the arc should have asked earlier. Overflow goes to the ledger, with the total stated in the header ("3 of 11 shown").
- The nine-item receipt stays, underneath the asks. It is structured and
proves the work. What this contract displaces is the free-form narrative
— that moves to
docs/handoffs/. - The ask block ends with the reply already drafted — one line per row, numbered to match. A copy-pasteable skeleton, one line per row, recommendation pre-filled — so answering is an edit, not a composition. Rules 2–6 fix what a row says; none of them lowers the cost of replying, and a perfectly-formed ask still stalls if answering it means composing prose at 1am. Reacting beats imagining: hand the reader something to strike through, not a blank box. The skeleton is generated from the rows: if it has four lines and the table has five, the fifth ask is one the reader is never prompted to answer — a silent drop dressed as a convenience.
The 🔀 block — what rung 6 owes the human
Rung 6 is where most asks are supposed to die: take the reversible default, log it, tell the human afterwards. For as long as this contract defined five blocks, there was nowhere for that last clause to land — the ladder told the arc to decide and say so, and the message shape had no room for it. Every silent-but-correct decision therefore looked identical to no decision at all, while the user still inherited it.
The 🔀 block is that room. It is not an ask block: nothing in it is a question, and the arc did not wait for any of it.
| Property | Why |
|---|---|
Rendered by ask_ledger.py decisions --render-handback, not written free-hand |
The rules below — ordering, the undo, the cap, the overflow header — are executable there and tested. Stated only in prose they are four things an agent forgets one at a time |
| An irreversible choice may not appear here as a settled default | Rung 6 licenses reversible choices only. Recording an irreversible one is still right, but the schema forces it to carry verdict: needs-user and name the ask that took it to the human |
A needs-user row is excluded from this block |
It reached the human, so it belongs in ⛔ keyed by its ask. Rendering it in both would print one item twice under contradictory headings — "decided for you" and "blocked on you" — which this workspace's own arc ledger did before the renderer excluded them |
| Least-confident first | Confidence ranks; verdict groups. The reader's attention is finite and should land on the choice most likely to be wrong, not the one that happened to be made first |
| Every row shows its undo | The reversal is the entire justification for not having asked. A row that cannot state one is a row that should have been an ask |
| Hard cap 7, overflow counted | Same discipline as the ask block. More than seven means the plan was foggy — the clustering is the signal, and the fix is upstream in the spec, not a longer table |
Where each verdict surfaces, so nothing is duplicated and nothing is dropped:
| Verdict | Block | Why |
|---|---|---|
sound |
🔀 | Decided and settled. The user owns it now |
unsound |
🔀, with its corrected decision | Decided, and known to need redoing |
needs-user |
⛔ only, via its ask |
It was not decided for them — it was asked |
Consequence worth stating plainly: because the schema forces an irreversible
choice to be needs-user, no valid ledger can render one in 🔀 at all. The
renderer keeps a defensive branch for a ledger nobody validated — an empty undo
column would read as "no undo needed", the opposite of the truth — but on a valid
ledger that branch is unreachable.
sound is not skippable. The temptation is to list only the shaky calls, but
a confident decision the user never made is still architecture the user now owns.
Trivial discretion — internal naming, cosmetic calls — compresses to a one-line
count; everything load-bearing gets a row.
In the ledger, an ask carries what it takes to decide it
The ⛔ block is what the message says. The ask itself lives in
.control/asks/<arc>.yaml, and Maestro serves it to the owner on a desktop card
and on a phone. There the owner has nothing but the card, so a decision that is
not a plain yes/no has to carry its own context. The owner, of an ask that asked
him to approve a spec with no link to it and no picture: "I cant really tell or
make a decision."
Decision layer v2 gives an ask five optional fields, scaled to the decision:
summary (2–4 plain sentences), tradeoffs (rows of option × cost, risk, time,
reversibility), sources (the tickets, PRs, specs, docs, URLs and sessions you
read), media (pictures or clips on the ask and on each option, which Maestro
compares side by side) and details (markdown behind a disclosure). Spec and
validator rules: .control/asks/DECISION-LAYER.md, "Decision layer v2".
| The decision is… | Write |
|---|---|
| a yes/no on something reversible the reader already knows | nothing extra |
| a choice among 3–4 paths | summary (required), a tradeoffs row per option, your sources |
| approving a spec, a plan or a doc | summary (required), the doc as a spec source (required when the ask names it), ticket and PR, tradeoffs, 1–3 of the doc's own figures as option media |
| a choice between visual variants | summary (required), one picture or clip per option, a caption saying what differs |
| irreversible, money, or public | everything above that applies, plus details: what happens if it goes wrong |
# an illustrative ask between two renders (the paths are examples)
summary: >-
Two hero frames for act 1. A keeps the face in the light; B is darker and
matches the trailer. Either is one re-render to change later.
tradeoffs:
- { option: frame-a, cost: "$0.25 render", risk: "off-tone with the trailer" }
- { option: frame-b, cost: "$0.25 render", risk: "the face reads dark on phones" }
sources:
- { kind: ticket, ref: BRO-3130 }
- { kind: file, ref: "docs/reels/act1-notes.md" }
options:
- id: frame-a
# label, consequence, reversible, recommended…
media:
- { kind: image, path: "docs/reels/act1/frame-a.png", caption: "A: face lit" }
Before you emit, three checks: could the owner answer from the card alone, on a
phone, in under a minute? Does every option the owner would want to see have a
picture? Is every claim the options rest on one tap away? Then run
python3 scripts/ask_ledger.py validate <ledger>; it fails an open ask raised on
or after 2026-10-17 with three or more options, or one about a spec or design, that
has no summary.
Anti-patterns
| Rationalization | Why it fails |
|---|---|
| "I'll describe the blocker; they'll know what to do" | A statement about the world transfers no obligation. Zero of 31 measured messages contained a request addressed to a person. |
| "The ticket explains it" | The reader has no context loaded and may be on a phone. The ticket was written by an agent for an agent. |
| "It's a big decision, I shouldn't presume a default" | Defaults are for reversible choices, and most are. Reserve no-default for irreversible / externally visible / money-spending. |
| "I'll list everything so they have full context" | Seven rows maximum. More means the ladder was not climbed or the hour-zero batch was skipped. |
| "I'll ask now and keep working after they answer" | Wrong order. Park the gated lanes and keep working now; the answer arrives whenever it arrives. |
| "Asking is safer than assuming" | Asking is expensive — median 2.4h of stalled arc-time, 25 overnight stalls in the measured corpus. Rung 6 exists for this. |
| "I'll write the narrative first so they understand the ask" | That ordering is the measured defect. Narrative goes to the handoff doc; the ask goes first. |
| "I took a reversible default, so there is nothing to report" | Rung 6 is a licence to not ask, not a licence to not tell. The whole clause is "take it, log it, tell the human afterwards." A decision with no record is one the user inherits without ever seeing. |
| "The 🔀 rows are really questions — I'll move them into the ask block" | Then the ladder was not climbed. If it still needs the human it never reached rung 6; if it reached rung 6 it is settled and reversible. Rows do not belong in both. |
| "I'll list only the decisions I'm unsure about" | sound is not skippable. A confident call the user never made is still architecture they now own — the point is disclosure, not confession. |
| "They can just tell me what they want in their own words" | That is the blank box rule 10 exists to remove. An answer that requires composing prose costs more than one that requires striking a line through, and the measured stall is hours, not minutes. |
Composition
handoff— same arc, different reader.handoffwritesdocs/handoffs/YYYY-MM-DD-<arc>.mdfor the next agent;handbackwrites the chat message for the human. A stopping arc usually produces both, and the handback's Detail block links to the handoff.autonomous— supplies the nine-item receipt that block 4 renders, and owns the pre-flight that builds the ask ledger.persist/governed-autonomy-loop— a parked lane is a lane the next iteration'sPROMPT.mdomits;handbackfires only when none remain.
References
references/template.md— copy-paste skeleton with a worked example.