How it works / The baton
The baton
A baton is the note one carrier leaves for the next. It says where the work stands and what to do next, so the next carrier can start without asking.
Before you start
The examples use the workspace billing and the handles build-agent and review-agent from your first relay. This page opens its own piece of work, BILL-52, and gives every baton file it uses.
What a baton holds
A handle is a name that carries work, for an agent or a person. The carrier is the handle that holds the work now. The baton is everything the next carrier gets from it. Handsoff never passes on the transcript of the session that wrote it.
A baton is Markdown with five headings. They must come in this order:
| Heading | What to write there |
|---|---|
## True now | What is true about the work now. |
## Next action | The next step, written so someone else can do it as given. End it with a Done when: line that says what you will see when the step is done. |
## Traps | What will go wrong if nobody tells the next carrier, and how it looks when it happens. |
## Evidence | What you checked, and how, for the claims in True now. |
## References | Where to look, one line for each. |
Each save of a baton makes a new revision: r1, r2 and so on. The service keeps every revision.
References
A reference line looks like this:
- [required] artifact: logs/mail-2026-03.log -- one line for each invoice sent (check: grep -c sent logs/mail-2026-03.log)
It has these parts:
[required]says the next carrier cannot do the work without it. Leave it out when a reference is only nice to have.- The kind comes next:
work,artifact,source,secretorother. - The target says where to look: a path, a link, an id, a commit or a name.
- An optional note follows
--. - An optional check follows in
(check: …). It says how the next carrier can see whether a step happened.
A secret reference says where a secret lives. It never holds the secret itself.
A work reference names another piece of work, such as - work: BILL-42 -- the migration these invoices came from. If a baton has no work reference, the next carrier sees a warning. The save still goes through.
How to write a good one
Write for a carrier that knows nothing but the baton. It has not seen your session, and it never will.
- In True now, state facts, not plans. "Invoices 1 to 100 are sent" beats "I am sending the invoices".
- In Next action, write what to do next so that someone else can do it as given. Make the
Done when:line something they can see, such as a count or a test result. - In Traps, name the mistake that costs the most. "Sending a batch twice emails those customers twice" tells the next carrier what to check first.
- In Evidence, say how you know. "The mail log shows 100 lines marked sent" can be checked. "It worked" cannot.
- In References, point to where things are instead of pasting them. Give each one a check when you can. If your session stops early, the check is how the next carrier finds out what you did.
Save a new baton each time something real changes. A carrier that stops suddenly leaves only its last saved baton behind.
The service caps the size of each part. True now holds at most 6,000 bytes, Next action 2,000, Traps 3,000 and Evidence 3,000. A baton holds at most 40 references. If a part is too long, the tool tells you to shorten it and refer out instead of pasting.
What the service refuses
Every open and every save go through content checks. The service refuses text that looks like a secret, such as a password or an access token: name where the secret lives instead. It refuses text that looks like a session transcript, such as lines from both user: and assistant:: write a summary instead. It also refuses terminal control codes. The service never stores the refused text. Security and privacy lists every check.
To see a refusal, save this baton as export.md. Its last reference holds a password:
## True now
The export job runs in staging. It signs in to the reports database.
## Next action
Run the export for 2025.
Done when: The export folder holds twelve monthly files.
## Traps
The job stops at the first month that has no invoices.
## Evidence
The January export ran by hand and wrote one file.
## References
- [required] artifact: scripts/export_invoices.ts -- the export job (check: bun scripts/export_invoices.ts --dry-run)
- secret: REPORTS_DB_PASSWORD=Qm7vX2kP9rTz4LwB
Open a new piece of work with it. The service refuses it and opens nothing:
handsoff open BILL-52 --title "Export the 2025 invoices" --as review-agent --baton export.md
This field looks like a credential; name where the secret lives instead.
content_refused: field reference target, line 1, pattern secret-assignment; DESIGN 2.5; fix: name where the secret lives instead; use --baton <file> or handsoff save <ref> --baton /dev/stdin <<'EOF' ... EOF (save without --baton also reads standard input), with Markdown containing these headings in order: ## True now, ## Next action (with Done when: <text>), ## Traps, ## Evidence, ## References
Change the last line of export.md so it names where the password lives:
- secret: env:REPORTS_DB_PASSWORD -- the job reads the password from this variable in staging
Now the work opens:
handsoff open BILL-52 --title "Export the 2025 invoices" --as review-agent --baton export.md
Opened BILL-52 at r1 (work 109)
Leg 197; lease expires 2026-10-05T05:18:08.653535+00:00
Print the current baton
handsoff baton prints the current revision in the same Markdown form. Send it to a file, edit the parts that moved, and save it back:
handsoff baton BILL-52 --as review-agent
## True now
The export job runs in staging. It signs in to the reports database.
## Next action
Run the export for 2025.
Done when: The export folder holds twelve monthly files.
## Traps
The job stops at the first month that has no invoices.
## Evidence
The January export ran by hand and wrote one file.
## References
- [required] artifact: scripts/export_invoices.ts -- the export job (check: bun scripts/export_invoices.ts --dry-run)
- secret: env:REPORTS_DB_PASSWORD -- the job reads the password from this variable in staging
The tool prints Done when: after a blank line, and no blank line before ## Traps. You can save the text back just as it is.
To see an older revision, add --rev and its number, such as handsoff baton BILL-52 --rev 1.
To save, give the file to handsoff save <work> --baton <file>. The tool says accepted only after the service stores the baton. Your first relay walks through a whole save, offer and catch.
A rough baton is better than none
A save never refuses a baton because it is incomplete. A rough baton from a session that is about to stop beats no baton at all. The examples here go on with BILL-52, which review-agent still holds. This baton has an empty Traps section and a placeholder in Next action:
## True now
The export job ran for January to June. July failed with a timeout.
## Next action
<what to do next>
Done when: The export folder holds twelve monthly files.
## Traps
## Evidence
The export folder holds six files.
## References
- [required] artifact: scripts/export_invoices.ts -- the export job
Save it as rough.md. The save goes through:
handsoff save BILL-52 --as review-agent --baton rough.md
accepted as r2
An offer is stricter. It refuses a baton whose True now or Next action is empty or still holds a <placeholder>:
handsoff offer BILL-52 --as review-agent --to build-agent
This baton is not ready to offer; fill the named section.
quality_refused: Next action: empty or unfilled <placeholder>; fix: fill Next action with the current checkpoint
The first line is for you. The second line is for the agent that ran the command.
The service also keeps a warning for each gap it finds. Whoever catches the work sees the warnings first. Here review-agent ends its leg without an offer, so the work drops. You then catch it as dana, your own handle:
handsoff end BILL-52 --as review-agent --reason clean
Ended BILL-52: clean
handsoff continue BILL-52
Caught BILL-52 at r2 from review-agent via drop
Attention
2026-10-05T04:48:10.707333+00:00 refusal: {"act":"offer","detail":"Next action: empty or unfilled <placeholder>","rule":null}
Warning: Traps: empty section
Warning: Next action: unfilled <placeholder>
Warning: References: no work reference
This machine has no pending writes.
…
The refusal line above is the offer that failed. The next carrier sees refusals since the last catch too.
Next, read leases and carriers to see how long you hold the work and how to keep it.