Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions .agents/skills/stuck-crewmate-recovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ Use `treehouse status` for treehouse-backed tmux, herdr, zellij, or cmux tasks,
Do not sweep another home's endpoints or infer ownership from a matching window label.

Before relaunch, prove that no live agent still owns the recorded task and that the existing worktree remains available.
Preserve its uncommitted changes and commits, keep the same task identity, and resume or relaunch the recorded harness in that existing worktree with the same brief plus a concise progress note.
Preserve its uncommitted changes and commits, keep the same task identity, and resume or relaunch the recorded harness in that existing worktree with the same brief.
The relaunched worker resumes from its own working log at `data/<id>/log.md`, which the brief already points it at, so never delete or rewrite that log and never reconstruct the worker's understanding by hand when the log holds it.
Read the log yourself before relaunching, and append a short progress note to the brief only for what the log cannot hold: a steer sent after the last log entry, or a correction the worker never recorded.
An absent or clearly stale log means the worker was not maintaining it; reconstruct the minimum needed to restart, and steer the relaunched worker to keep the log from then on.
Do not use a fresh generic spawn while the recorded worktree is unaccounted for, because allocating another worktree can split one task across two copies.
If the worktree or ownership cannot be reconciled safely, leave all state intact and report the task failed or blocked with the conflicting evidence.

Expand All @@ -42,7 +45,7 @@ Escalate in order:
2. If the crewmate is waiting on a question its brief already answers, answer in one line via `FM_HOME=<this-firstmate-home> bin/fm-send.sh` from an active firstmate session unless `FM_HOME` is already set to the active firstmate home.
3. If the crewmate is confused or looping, interrupt with the adapter's interrupt key, then redirect with one corrective line.
For example, for a single-Escape adapter: `FM_HOME=<this-firstmate-home> bin/fm-send.sh <window> --key Escape`.
4. If the crewmate is genuinely wedged after redirection, exit the agent with the adapter's exit command and relaunch with the same brief plus a `progress so far` note appended to it.
4. If the crewmate is genuinely wedged after redirection, exit the agent with the adapter's exit command and relaunch with the same brief, letting it resume from its `data/<id>/log.md` under the reconciliation rules above.
Genuine wedging means looping, unresponsive, repeating the same obstacle, or truly dead.
A low context reading is not wedging; modern harnesses auto-compact and keep going.
The worktree and commits persist, so relaunch is cheap.
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ data/ personal fleet records; LOCAL, gitignored as a whole
secondmates.md secondmate routing table; firstmate-private, maintained by fm-home-seed.sh (section 6)
<id>/brief.md per-task crewmate brief, or per-secondmate charter brief when kind=secondmate
<id>/report.md scout task deliverable, written by the crewmate; survives teardown
<id>/log.md crewmate working log, maintained by the crewmate as it works; its resume-from-disk memory, survives teardown, and never wakes firstmate
projects/ cloned repos; gitignored; read-only except under hard rule 1's concrete captain-approved project operation exception
state/ volatile runtime signals; gitignored
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
Expand Down Expand Up @@ -454,6 +455,7 @@ Use its scaffold as the contract, then replace every `{TASK}` placeholder with a
Keep additions task-specific rather than repeating lifecycle instructions, and alter generated sections only when the task genuinely differs from the standard shape.

Every ship brief must retain the worktree-isolation assertion and stop if launched in the primary checkout.
Every ship and scout brief must retain the working-log section: the crewmate's own durable memory at `data/<id>/log.md`, written as it works, is what a relaunched worker resumes from instead of an understanding firstmate reconstructs by hand.
If a ship task touches firstmate's shared tracked material, explicitly require `firstmate-coding-guidelines` before editing.
If a task will drive Herdr lifecycle behavior, scaffold with `--herdr-lab`; if that need appears after an unguarded scaffold, stop and regenerate rather than adding commands by hand.
The generated Herdr contract must use a named non-`default` isolated lab and its guarded helper for every lifecycle action.
Expand Down
34 changes: 30 additions & 4 deletions bin/fm-brief.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
# declared-external-wait verb (FM_CLASSIFY_PAUSED_VERB, default "paused") from
# "blocked:": pause for a known external wait expected to clear on its own,
# blocked when firstmate must act.
# Ship and scout briefs also scaffold an agent-maintained working log at
# data/<task-id>/log.md: the crewmate's own durable memory, written as it works,
# and the resume path when a relaunch discards its context. It survives teardown
# with the task's other data, and unlike state/<task-id>.status it never wakes
# firstmate, so it carries the detail the sparse supervisor channel must not -
# established facts, rejected approaches, and steers received after dispatch.
# Ship tasks include a project-memory section so durable project-intrinsic
# learnings can be committed to AGENTS.md through the project's delivery path;
# it carries the AGENTS.md authoring bar (widely useful knowledge only, pointers
Expand Down Expand Up @@ -247,6 +253,22 @@ EOF
HERDR_SECTION=${HERDR_SECTION%$'\n'}
fi

# The crewmate's own working memory, shared by ship and scout briefs. It is
# deliberately NOT the status file: status appends wake firstmate and must stay
# sparse, while this log is written for the worker's own future self and can be
# as long as the task needs. A relaunched worker resumes from it instead of from
# an understanding firstmate reconstructs by hand.
IFS= read -r -d '' WORKLOG_SECTION <<WORKLOG || true
# Working log
Keep a working log at \`$DATA/$ID/log.md\` and write it as you go, not at the end.
**If you are resuming this task, read that log first - it is your memory of this task, and it survives a relaunch that your context does not.**
Append to it whenever you establish something that would be expensive to rediscover: the task as you currently understand it, what you have established, what you are doing now, what you tried and rejected and why, and what you have verified and how you verified it.
Rejected approaches matter as much as the live one - a resuming agent that does not know an approach already failed will spend its context repeating it.
When firstmate steers you with a correction, a changed requirement, or a decision, append it to the log before you act on it; a steer that lives only in your context is exactly what a relaunch loses.
The log is yours and appending to it never notifies firstmate, so keep it as long and as candid as it needs to be; the status file in the rules below is the separate sparse supervisor channel and its contract is unchanged.
WORKLOG
WORKLOG_SECTION=${WORKLOG_SECTION%$'\n'}

if [ "$KIND" = scout ]; then
cat > "$BRIEF" <<EOF
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.
Expand All @@ -260,11 +282,13 @@ $HERDR_SECTION
You are in a disposable git worktree of $REPO, at a detached HEAD on a clean default branch.
This is a SCOUT task: the deliverable is a written report, not a PR.
The worktree is your laboratory - install, run, edit, and make scratch commits freely; all of it is discarded at teardown.
The report is the only thing that survives, so anything worth keeping must be in it.
The report is the only deliverable that survives, so anything worth keeping must be in it.

$WORKLOG_SECTION

# Rules
1. Never push to any remote and never open a PR.
2. Stay inside this worktree; the only files you may write outside it are the report and the status file below.
2. Stay inside this worktree; the only files you may write outside it are the report, your working log above, and the status file below.
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\`
Expand Down Expand Up @@ -371,11 +395,13 @@ You are in a disposable git worktree of $REPO, at a detached HEAD on a clean def
The path check is authoritative: \`git rev-parse --git-dir\` and \`git rev-parse --git-common-dir\` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append \`blocked: launched in primary checkout, not an isolated worktree\` to the status file and stop.

1. First action: create your branch: \`git checkout -b fm/$ID\`$SETUP2
1. First action: create your branch: \`git checkout -b fm/$ID\` - if you are resuming and it already exists, check it out instead and read your working log below before doing anything else.$SETUP2

$WORKLOG_SECTION

# Rules
$RULE1
2. Stay inside this worktree; modify nothing outside it.
2. Stay inside this worktree; the only files you may write outside it are your working log above and the status file below.
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\`
Expand Down
5 changes: 5 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ The helper's header owns the exact signal detection, relocated-home limitation,
Ship tasks change projects and ship by project mode (`no-mistakes`, `direct-PR`, or `local-only`); scout tasks leave standalone investigation reports at `data/<id>/report.md` and never push.
The intake and authority contract in `AGENTS.md` owns when separate scout research is warranted.

Both shapes keep two durable channels with deliberately different wake semantics.
`state/<id>.status` is the supervisor channel: sparse, and every append wakes firstmate.
`data/<id>/log.md` is the crewmate's own working log, maintained by the agent as it works and never a wake source, so it can carry the established facts, rejected approaches, and post-dispatch steers that a lost context would otherwise take with it.
It survives teardown with the task's other data, which is what lets a relaunched worker resume from disk rather than from a brief firstmate rebuilds by hand.

## Dispatch profiles

Crewmate and scout dispatch can stay on the static crewmate harness resolved by `config/crew-harness`, or it can use local dispatch profiles in `config/crew-dispatch.json`.
Expand Down
67 changes: 67 additions & 0 deletions tests/fm-brief.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,71 @@ test_ship_project_memory_wording() {
pass "fm-brief.sh: ship project-memory wording carries the AGENTS.md authoring bar"
}

# The worker's own durable memory. Ship and scout briefs must scaffold it, it
# must be discoverable as the resume path, and it must stay distinct from the
# status file: status appends wake firstmate, log appends must not. The
# secondmate charter is a persistent home with its own data and gets no log.
test_ship_and_scout_briefs_scaffold_working_log() {
local home id brief kind
home="$TMP_ROOT/worklog-home"
mkdir -p "$home/data"

for kind in ship scout; do
id="brief-worklog-$kind"
if [ "$kind" = scout ]; then
FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" someproj --scout >/dev/null 2>&1
else
FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" someproj >/dev/null 2>&1
fi
brief="$home/data/$id/brief.md"
assert_present "$brief" "$kind: brief was not scaffolded"
assert_grep "# Working log" "$brief" "$kind brief missing the working-log section"
assert_grep "$home/data/$id/log.md" "$brief" \
"$kind brief did not bind the task's own log path"
assert_grep "write it as you go, not at the end" "$brief" \
"$kind brief did not require continuous logging"
assert_grep "If you are resuming this task, read that log first" "$brief" \
"$kind brief did not name the log as the resume path"
assert_grep "what you tried and rejected and why" "$brief" \
"$kind brief did not require rejected approaches in the log"
assert_grep "append it to the log before you act on it" "$brief" \
"$kind brief did not require steers to be recorded before acting"
assert_grep "appending to it never notifies firstmate" "$brief" \
"$kind brief did not separate the log from the waking status channel"
# The log lives under data/<id>/, outside the worktree, so the
# stay-inside-the-worktree rule must permit it the way it permits the
# status file. Without this a careful worker cannot obey both rules.
assert_grep "the only files you may write outside it are" "$brief" \
"$kind brief forbids writing the working log it requires"
assert_grep "your working log above" "$brief" \
"$kind brief did not except the working log from the worktree-only rule"
assert_no_grep "WORKLOG" "$brief" \
"$kind brief leaked the working-log heredoc delimiter"
assert_no_grep "EOF" "$brief" \
"$kind brief leaked a heredoc EOF marker (unterminated heredoc)"
done

FM_HOME="$home" FM_SECONDMATE_CHARTER='Supervise the alpha domain.' \
"$ROOT/bin/fm-brief.sh" brief-worklog-secondmate --secondmate --no-projects >/dev/null 2>&1
assert_no_grep "# Working log" "$home/data/brief-worklog-secondmate/brief.md" \
"secondmate charter gained a per-task working log"
pass "fm-brief.sh: ship and scout briefs scaffold the agent-maintained working log"
}

# A relaunched ship worker re-reads the brief from the top, where step 1 still
# says to create a branch that now exists. That step must route it to its log
# rather than leave it stuck on a failing checkout.
test_ship_setup_step_is_resume_aware() {
local home brief
home="$TMP_ROOT/worklog-resume-home"
mkdir -p "$home/data"
FM_HOME="$home" "$ROOT/bin/fm-brief.sh" brief-worklog-resume someproj >/dev/null 2>&1
brief="$home/data/brief-worklog-resume/brief.md"
assert_grep "if you are resuming and it already exists, check it out instead and read your working log below" "$brief" \
"ship brief's first setup step is not resume-aware"
pass "fm-brief.sh: ship setup step routes a resuming worker to its working log"
}

test_herdr_lab_contract_is_explicit_and_complete() {
local home id brief
home="$TMP_ROOT/herdr-lab-home"
Expand Down Expand Up @@ -625,6 +690,8 @@ test_ship_modes_generate_clean_briefs
test_faster_paths_use_configured_authority_without_stacked_review
test_no_mistakes_dod_wording
test_ship_project_memory_wording
test_ship_and_scout_briefs_scaffold_working_log
test_ship_setup_step_is_resume_aware
test_herdr_lab_contract_is_explicit_and_complete
test_herdr_lab_contract_quotes_foreign_firstmate_path
test_herdr_lab_omission_is_loud_for_ship_and_scout
Expand Down
Loading