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

SKILL.md

GitHub

handback — 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.

  1. The ask block is first. Not a "Your call" section at 80% depth. Measured: the ask lands around p80 of the message.
  2. 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.
  3. 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 …".
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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").
  9. 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/.
  10. 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. handoff writes docs/handoffs/YYYY-MM-DD-<arc>.md for the next agent; handback writes 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's PROMPT.md omits; handback fires only when none remain.

References

  • references/template.md — copy-paste skeleton with a worked example.