Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
5789feb
fix(brief): forbid agent co-author commit trailer in every ship mode
NicholasACTran Sep 12, 2026
7992c99
no-mistakes(review): make co-author trailer rule mode-aware, loosen b…
NicholasACTran Sep 12, 2026
41a3e0e
no-mistakes(review): give each ship mode its own co-author check point
NicholasACTran Sep 13, 2026
860e7c5
no-mistakes(review): make no-mistakes co-author remedy escalate inste…
NicholasACTran Sep 13, 2026
dd9e5b9
no-mistakes(review): phase no-mistakes co-author remedy by branch own…
NicholasACTran Sep 13, 2026
723bd8a
no-mistakes(review): share non-pipeline attribution remedy, declare n…
NicholasACTran Sep 13, 2026
50583e6
no-mistakes(review): keep note: lines nonterminal in wedge classifica…
NicholasACTran Sep 13, 2026
8b0e36f
no-mistakes(review): demote attribution heading, carry trailer ban in…
NicholasACTran Sep 13, 2026
cddfc93
no-mistakes(review): give worker pipeline lever, scout-specific trail…
NicholasACTran Sep 13, 2026
4d65ee4
no-mistakes(review): move intent exception onto restricting sentence,…
NicholasACTran Sep 13, 2026
63e9a67
no-mistakes(review): carry attribution ban through the intent overlay
NicholasACTran Sep 13, 2026
edd4771
no-mistakes(review): carry trailer ban into secondmate charter and ov…
NicholasACTran Sep 13, 2026
3af1cd3
no-mistakes(review): scope pipeline clause to the no-mistakes arm
NicholasACTran Sep 13, 2026
0ccf7d0
no-mistakes(review): drop duplicate pipeline-coverage sentence from n…
NicholasACTran Sep 13, 2026
40f8908
no-mistakes(document): record note verb classification and attributio…
NicholasACTran Sep 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions bin/fm-brief.sh
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ On startup and restart, run normal firstmate bootstrap and recovery through \`bi
When you have no assigned or in-flight work after that reconciliation, go idle and wait silently for the main firstmate to route you a task.
An empty queue is a healthy resting state, not a cue to invent work: never spawn a survey, audit, or any self-directed "find work" task on your own initiative.
If this charter cannot be carried out, append \`blocked: {why}\` or \`failed: {why}\` to the main status file and stop.

$(fm_commit_attribution_block "$ID" secondmate)
EOF
if [ "$SECONDMATE_CHARTER" = "{TASK}" ]; then
echo "scaffolded: $BRIEF (secondmate charter; replace {TASK})"
Expand Down Expand Up @@ -380,7 +382,8 @@ The report is the only thing that survives, so anything worth keeping must be in
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
\`echo "{state}: {one short line}" >> $STATUS_FILE\`
States: working, needs-decision, blocked, $PAUSED_VERB, done, failed.
States: working, needs-decision, blocked, $PAUSED_VERB, done, failed, note.
Use \`note: {fact}\` for a supervisor-actionable fact that is not a state change; it never replaces a state line.
Each append wakes firstmate, so report sparingly: only phase changes a supervisor
would act on and the needs-decision/blocked/paused/done/failed states. No step-by-step
FYI progress lines; firstmate reads your pane for that.
Expand Down Expand Up @@ -420,6 +423,8 @@ $LAVISH_LINE
Before reporting done, read and follow \`$FM_ROOT/.agents/skills/captain-hold-lifecycle/SKILL.md\` and pass its shared completion gate for the report and any visual review.
When the report is complete, append \`done: {one-line conclusion}\` to the status file and stop.
If your findings reveal work that should ship (e.g. you reproduced a bug and the fix is clear), say so in the report; firstmate may promote this task in place, and you would then receive mode-specific ship instructions as a follow-up message.

$(fm_commit_attribution_block "$ID" scout)
EOF
echo "scaffolded: $BRIEF (scout; replace {TASK} and {FIRSTMATE_SPEC})"
exit 0
Expand Down Expand Up @@ -469,7 +474,8 @@ $RULE1
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
\`echo "{state}: {one short line}" >> $STATUS_FILE\`
States: working, needs-decision, blocked, $PAUSED_VERB, done, failed.
States: working, needs-decision, blocked, $PAUSED_VERB, done, failed, note.
Use \`note: {fact}\` for a supervisor-actionable fact that is not a state change; it never replaces a state line.
Each append wakes firstmate, so report sparingly: only phase changes a supervisor
would act on (setup done, bug reproduced, fix implemented, validation passed) and the
needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
Expand Down
11 changes: 7 additions & 4 deletions bin/fm-classify-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,19 @@ status_is_terminal_verb() {

# 0 if the given (last) status line matches a captain-relevant verb.
# Verb-aware by default: terminal verbs always match; nonterminal progress verbs
# (working, resolved, captain-held) and paused never match from free-text prose;
# only lines without those leading verbs may still match free-text tokens for
# legacy bare lines such as "merged" or "PR ready".
# (working, resolved, captain-held, note) and paused never match from free-text
# prose; only lines without those leading verbs may still match free-text tokens
# for legacy bare lines such as "merged" or "PR ready".
# `note:` is an informational disclosure with its own unread-status surface
# (status_line_is_unread_surface), never a state change, so a note whose prose
# happens to say "merged" must not clear a pane's possible-wedge aging.
status_is_captain_relevant() {
local line=$1 verb
[ -n "$line" ] || return 1
status_is_paused "$line" && return 1
verb=$(status_line_verb "$line")
case "$verb" in
working|resolved|captain-held|"${FM_CLASSIFY_PAUSED_VERB:-$FM_CLASSIFY_PAUSED_VERB_DEFAULT}")
working|resolved|captain-held|note|"${FM_CLASSIFY_PAUSED_VERB:-$FM_CLASSIFY_PAUSED_VERB_DEFAULT}")
return 1
;;
esac
Expand Down
70 changes: 69 additions & 1 deletion bin/fm-dod-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ fm_brief_intent_overlay() { # <captain-intent>
# Current no-mistakes intent contract
This section supersedes every earlier brief instruction about constructing `--intent`, but not later clarifications actually supplied by the captain.
Use the serialized captain intent below plus any later words the captain actually supplied as `--intent`; never include Firstmate specification or other mixed Task content.
The commit-attribution ban is the one standing exception to that supersession: carry into `--intent` the rule that no commit on this branch may carry an agent name as a co-author trailer (for example `Co-Authored-By: Claude ... <noreply@anthropic.com>`), because the pipeline commits on your branch and only you can tell it that rule.

## Captain intent authorized for --intent
EOF
Expand Down Expand Up @@ -181,6 +182,66 @@ fm_brief_task_content_valid() { # <file>
[ -n "$(printf '%s' "$task" | tr -d '[:space:]')" ]
}

# fm_commit_attribution_block <task-id> <mode> prints the hard, standing
# prohibition on an agent co-author commit trailer. Sourced into fm_dod_block
# below so every rendered mode - including a promoted scout's ship instructions
# from bin/fm-promote.sh - carries the same text; this is the single owner, so a
# change here reaches both callers without a second copy to drift.
# The prohibition is identical in every mode; only the check point, the remedy
# and the pipeline clause differ, because branch ownership does. Only
# no-mistakes runs a pipeline that commits on the worker's behalf, so only its
# arm carries the ban to that pipeline. no-mistakes mode is the one that
# changes hands mid-flight, so its arm is phased: the worker strips the trailer
# while the branch is still its own, must not touch the branch while a run owns
# it, and reports rather than rewriting a pushed PR head afterwards.
# Never touching the default branch is absolute in every mode.
# A scout sits at a detached HEAD and a secondmate supervises a domain; neither
# has a branch to ship and neither drives the pipeline, so each gets its own
# subject line covering every commit it writes, with no branch clause, no
# pipeline clause and no check point or remedy.
fm_commit_attribution_block() { # <task-id> <mode>
local id=$1 mode=$2 checkpoint=''
cat <<'EOF'
## Commit attribution - HARD RULE, no exceptions
EOF
if [ "$mode" = scout ]; then
cat <<'EOF'
NEVER put an agent name as a commit co-author trailer (for example `Co-Authored-By: Claude ... <noreply@anthropic.com>`) on any commit you write here, including the scratch commits discarded at teardown.
EOF
elif [ "$mode" = secondmate ]; then
cat <<'EOF'
NEVER put an agent name as a commit co-author trailer (for example `Co-Authored-By: Claude ... <noreply@anthropic.com>`) on any commit you write, including a merge you perform yourself under standing merge authority.
EOF
else
cat <<EOF
NEVER put an agent name as a commit co-author trailer (for example \`Co-Authored-By: Claude ... <noreply@anthropic.com>\`) on any commit on this branch.
EOF
case "$mode" in
no-mistakes)
cat <<EOF
The pipeline commits on your behalf, so carry this ban to it through the two channels you already drive: state it in the \`--intent\` you pass \`no-mistakes axi run\`, and restate it in every fix instruction you send with \`no-mistakes axi respond\`.
Before you start a no-mistakes run, while \`fm/$id\` is still yours alone, check every commit on this branch for that trailer; if you find one, rewrite ONLY this task's own unmerged branch to strip it, and say in your report that you did.
While a run is active the pipeline owns \`fm/$id\`: never rebase, amend, filter, force-push, or hand-commit on it, not even to strip a trailer.
Once the run is terminal, check the branch again and report a surviving trailer instead of rewriting the pushed PR head: append a \`note:\` line naming those commits immediately before your terminal \`done:\` line, leave that \`done:\` line in its exact required shape, and let firstmate decide before merge.
EOF
;;
direct-PR)
checkpoint='Before you push this branch and before you open or update its PR' ;;
local-only)
checkpoint='Before you report this branch ready for the merge authority' ;;
esac
fi
if [ -n "$checkpoint" ]; then
cat <<EOF
$checkpoint, check this branch's commits for that trailer.
If you find one, rewrite ONLY this task's own unmerged branch (\`fm/$id\`) to strip it, and say in your report that you did.
EOF
fi
cat <<'EOF'
Never rewrite a commit that has already reached the default branch; that is the captain's call, not yours.
EOF
}

fm_ask_user_escalation_block() { # <data-dir> <task-id>
local data=$1 id=$2
cat <<EOF
Expand All @@ -201,6 +262,8 @@ This task ships **direct-PR**: you raise the PR yourself, without the no-mistake
The task is complete only when committed on your branch.
When it is implemented and committed, push your branch and open a PR with \`gh-axi\`, then append \`done: PR {url}\` to the status file and stop.
Do NOT run /no-mistakes. The configured merge authority decides whether to merge the PR; firstmate relays the outcome.

$(fm_commit_attribution_block "$id" "$mode")
EOF
;;
local-only)
Expand All @@ -212,19 +275,24 @@ The task is complete only when committed on your branch \`fm/$id\`. Do NOT push,
Keep your branch a clean fast-forward onto the current default branch - if \`main\` has advanced, rebase onto it so the eventual merge stays a fast-forward.
When it is implemented and committed, append \`done: ready in branch fm/$id\` to the status file and stop.
The configured merge authority approves the ready branch, then firstmate merges it into local \`main\` through the guarded fast-forward path.

$(fm_commit_attribution_block "$id" "$mode")
EOF
;;
no-mistakes)
cat <<EOF
# Definition of done
Delivery contract: mode=no-mistakes
The task is complete only when committed on your branch.

$(fm_commit_attribution_block "$id" "$mode")

When you believe it is complete, append \`done: {summary}\` to the status file and stop.
Firstmate will then instruct you to run /no-mistakes to validate and ship a PR.

You drive no-mistakes by responding to its gates, not by implementing fixes.
Follow the guidance no-mistakes itself provides for the mechanics: it loads when you invoke /no-mistakes, and \`no-mistakes axi run --help\` plus the \`help\` lines in each \`axi\` response are authoritative and version-matched to the installed binary.
When starting no-mistakes, pass \`--intent\` as only this brief's \`## Captain's intent\` subsection plus any later words the captain actually said.
When starting no-mistakes, pass \`--intent\` as only this brief's \`## Captain's intent\` subsection, plus any later words the captain actually said, plus the standing commit-attribution ban above, which belongs in every run's intent because the pipeline commits on your branch and only you can tell it that rule.
For a legacy brief with no such subsection, include only words explicitly labeled \`Captain:\`, \`Captain's words:\`, \`Captain's ask:\`, or \`Captain's intent:\`; never copy its mixed \`# Task\` wholesale. If it has no provenance-marked captain words, stop and ask firstmate instead of starting no-mistakes.
Do not include \`## Firstmate spec\`, later Firstmate build constraints, or your own decisions and tradeoffs.
The \`--intent\` string you pass must be self-sufficient: that string plus the codebase must let a reader reconstruct roughly the same specification, without depending on a separate report, a PR, or context that lives only in this conversation.
Expand Down
1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ On Pi and pi-signed the away daemon is no longer launched: the ordinary supervis
A presence-gated sub-supervisor (`bin/fm-supervise-daemon.sh`) still extends this for walk-away supervision on the other harnesses: the `/afk` skill starts it through the tracked foreground helper `bin/fm-afk-start.sh` once the record exists, after which the watcher reverts to daemon-managed one-shot mode and the daemon self-handles routine wakes in bash.
The watcher and daemon share `bin/fm-classify-lib.sh` for captain-relevant status verbs, declared-wait vocabulary (a `paused:` external wait and a verified `captain-held` transfer alike, through one combined predicate), and status-scan primitives.
Terminal verbs remain captain-relevant, while a nonterminal progress verb cannot become terminal merely because its prose contains a legacy free-text token such as `merged`; bare legacy free-text lines remain compatible.
An informational `note:` line is classified the same way, so a disclosure whose prose happens to say `merged` neither escalates as captain-relevant nor clears a pane's possible-wedge aging.
Both supervisors classify the status bytes appended since they last classified that log, never its last line alone, and report every actionable event through the captured endpoint before committing that position.
The watcher's `.seen-*` and `.hb-surfaced-<task>` markers and the daemon's `.subsuper-seen-status-<task>` marker independently track reported file state and successfully classified position, so an unchanged unreadable state reports once without advancing past unread content, while a changed state retries and an unusable position re-reads the whole log.
A keyed `needs-decision` or `blocked` transition accepted by the whole-file decision fold is retired only when that fold proves the exact opening closed, while a reserved-key transition the fold rejects surfaces as a reconciliation signal without becoming an open decision.
Expand Down
2 changes: 1 addition & 1 deletion docs/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize
| `fm-captain-hold.sh` | Hold tasks for the captain, record the captain's answers, gate investigation completion, and report record divergence between the status log and the backlog |
| `fm-decision-hold.sh` | One-release compatibility shim mapping the retired decision commands onto fm-captain-hold.sh |
| `fm-brief.sh` | Scaffold ship (explicit `--mode`), scout, secondmate-charter, and Herdr-lab briefs, with Captain's intent and Firstmate spec subsections on ship/scout |
| [`fm-dod-lib.sh`](../bin/fm-dod-lib.sh) | Own ship/scout worker role scope, ship definitions of done, and the no-mistakes `--intent` contract |
| [`fm-dod-lib.sh`](../bin/fm-dod-lib.sh) | Own ship/scout worker role scope, ship definitions of done, the no-mistakes `--intent` contract, and the commit-attribution ban rendered into every brief kind |
| `fm-herdr-lab.sh` | Provision and guardedly operate an isolated, never-default Herdr lab session |
| `fm-herdr-lab-viewer.py` | The pty engine behind `fm-herdr-lab.sh viewer`: one real foreground Herdr client on a non-zero window grid |
| `fm-install-herdr.sh` | Install CI's exact-version Herdr pin with official asset URL, SHA-256, and protocol checks |
Expand Down
Loading