Briefs & the status protocol
The anatomy of a crewmate brief, the status verbs workers write, the steering inbox, and the per-mode definition of done that firstmate independently verifies.
Crewmates are stateless strangers. Everything a worker knows comes from its brief, and everything the first mate knows about the worker comes from its status log, its pane, and the no-mistakes run. This page is the contract between them.
Anatomy of a brief#
bin/fm-brief.sh writes data/<id>/brief.md. The header comment of fm-brief.sh is the canonical spec. There is no separate doc. A ship brief looks like this:
You are a crewmate: an autonomous worker agent managed by firstmate.
Work on your own; do not wait for a human.
# Task
## Captain's intent
<the captain's ask, verbatim — no "Captain:" label>
## Firstmate spec
<only the build instructions the ask requires>
# Herdr lifecycle declaration - NOT ENABLED (or the --herdr-lab isolation contract)
# Setup
Verify isolation before anything else: pwd -P and git rev-parse --show-toplevel
must resolve to the disposable task worktree ...
1. First action: create your branch: git checkout -b fm/<id> --
# Rules
1. <mode-specific "never push to X" rule>
2. Stay inside this worktree; modify nothing outside it.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line: ...
5. If you hit the same obstacle twice, append `blocked [at=<epoch>]: {why}` ...
6. If a decision belongs to a human ... append `needs-decision [at=<epoch>]: {options}` ...
7. Never administer infrastructure that every lane shares.
# Firstmate instruction inbox
# Project memory
# Definition of done
# Home brief additions (only if config/brief-include.md exists)
Key rules:
- Intent is sacred. Only
## Captain's intent(plus later captain words) counts as intent for the no-mistakes--intentcontract. The spec and the worker's own reasoning never do. - Project memory is correct-only. A worker may edit a project's
AGENTS.md/CLAUDE.mdonly to correct wrong facts, never to add to it. - Rule 7 protects the shared no-mistakes daemon and the treehouse pool. One crewmate must not restart infrastructure every other lane depends on.
- Scout briefs say "This is a SCOUT task: the deliverable is a written report, not a PR". Their rule 1 is "never push to any remote and never open a PR".
config/brief-include.mdappends your standing worker instructions to every brief, e.g. "runmake checkbefore reporting done".
Status verbs#
A worker appends one line per event to state/<id>.status:
{verb} [at=<epoch>] [key=<slug>]: {one short line}
| Verb | Meaning | First mate response |
|---|---|---|
working |
A phase change worth knowing about. Not step-by-step narration. | none (not captain-relevant) |
needs-decision |
A human-owned choice; the worker stops and waits | decide, or escalate to you |
blocked |
Stuck; the first mate must act | intervene |
paused |
A known wait expected to clear itself: an external condition, or the worker's own background shell, pipeline run or long command (until <ISO> optional). A worker must declare it before ending a turn on such a wait. |
leave it; after one first-sight stale alert, recheck every 4h |
done |
Mode-specific completion (below) | verify, then report |
failed |
Gave up | report plainly |
resolved [key=...] |
Closes a keyed needs-decision/blocked. Only an exact key match closes it. |
clears the open decision |
note |
Informational (e.g. pipeline summary) | none |
captain-held |
The item was handed to a durable captain hold | treated like paused |
A later done or working never closes an open decision. Only a resolved line carrying the matching key does.
The steering inbox#
bin/fm-send.sh <id> '<text>' writes a sequenced record to state/<id>.inbox/NNN.msg and types a short "doorbell" line into the worker's pane. The worker processes messages in order and acknowledges each by moving it to handled/. The watcher re-rings an unacknowledged message (after 90s, up to 3 times), then escalates.
Lifecycle commands (interrupt, exit, relaunch) never go through the inbox. They use bin/fm-control.sh. See Troubleshooting.
Definition of done, per mode#
bin/fm-dod-lib.sh renders the # Definition of done section for each mode and forge. It also owns the gate that checks the claim:
| Mode | Worker reports | Accepted when |
|---|---|---|
local-only |
done: ready in branch <branch> |
the head is on the project's shared local branch |
direct-PR |
done: PR <url> |
the worker's HEAD is what's pushed to the PR branch; the PR is not a draft |
no-mistakes |
1) done: <summary> → hands off to the pipeline (not gated)2) done: PR <url> checks green |
the named head is reachable outside the worktree, and gh-axi pr view confirms draft: no |
gerrit |
done: PR <change url> published for review |
the change's current patch set carries the worktree's HEAD tree |
a ship
done:is never trusted merely because the worker said so — it must be proven reachable from outside the disposable worktree — fm-dod-lib.sh (paraphrased header)
If the gate fails, fm-crew-state.sh reports the task as blocked, not done. Nobody gets told "PR ready" for a commit that only exists in a worktree about to be deleted.
Reading state back: fm-crew-state.sh#
$ bin/fm-crew-state.sh fix-login-q3
state: working · source: run-step · no-mistakes review (round 2)
It resolves in this order:
state/<id>.meta. A remote secondmate is probed remotely.- A matching no-mistakes run for the branch and head. When one exists it is authoritative: running or fixing maps to
working,awaiting_approvalmaps toparked, passed maps todone, failed maps tofailed. - The status log, reconciled so that open decisions survive unrelated later events.
- The pane's semantic busy state.
unknown.
AGENTS.md is explicit: judge state by this line, "never by shell liveness, the last status event, or a raw run record."
Per-task files at a glance#
| Path | What |
|---|---|
data/<id>/brief.md |
the contract |
data/<id>/report.md |
scout deliverable (survives teardown) |
state/<id>.meta |
worktree=, kind=, harness=, mode=, project=, pr=, pr_head=, backend=, remote_host= ... |
state/<id>.status |
append-only wake events |
state/<id>.inbox/ |
steering messages and handled/ |
state/<id>.busy-state |
semantic busy/idle from harness hooks |
state/<id>.check.sh + .check-trust |
optional custom watcher check, SHA-256 pinned |
state/<id>.git-hooks/ |
per-task hooksPath that strips AI commit trailers |