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
2 changes: 2 additions & 0 deletions .agents/skills/bearings/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ It never tears down a task, merges a PR, dispatches new work, or mutates any tas
If the command is unavailable, fall back to `bin/fm-fleet-snapshot.sh --json` and `bin/fm-crew-state.sh <id>`; never infer current state from a raw `tail` of `state/<id>.status`, which is append-only wake-event history whose last line goes stale.
For registered secondmates, use the snapshot's structured-home classification and provenance; a parent event or bounded terminal contradiction is fallback evidence, never authority over readable structured home state.
Structured captain-held decisions come from `decision-hold-lifecycle` and appear under `decisions_open`; do not scrape reports or visual-review artifacts to supplement them.
For each one, take the plain-language decision text from `bin/fm-attention.sh`, which is the single captain-facing owner of open decisions and meaningful waits and already renders the choice, the stakes, the options, and the recommendation; a decision with no recorded briefing renders as not yet written, and Captain's Call must say that rather than dressing up its raw note.
A queued item under `gates` only becomes "next work" when its blocker is gone and its time/date gate has arrived; until then it stays queued with the reason.
A declared external delay belongs in Charted Next with what it is waiting for and when it is next checked, both of which `bin/fm-attention.sh` renders; one that stopped clearing on its own is promoted to a decision by that same command and then belongs in Captain's Call.
The `(main-inventory)` gate is an action-free integrity warning rather than queued work: render it under Charted Next with the related `omitted` disclosure, never invent an Underway row from backlog-only state, and never move it into Captain's Call.

2. **Compose the detailed report file around the four-section spine, adding the richer detail the chat leaves out.**
Expand Down
4 changes: 3 additions & 1 deletion .agents/skills/decision-hold-lifecycle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This skill is the single policy owner for unresolved captain decisions discovere
Every unresolved decision that belongs to the captain and is discovered while producing, reading, presenting, or ending an investigation or visual review must become a structured captain-held work item in the authoritative backlog of the home that owns the originating work before that work or review may be treated as complete.
The agent performs the semantic inventory because scripts must not infer decisions from report prose, visual-review artifacts, terminal output, or chat.
Give each distinct unresolved decision a stable privacy-safe key, register it through `bin/fm-decision-hold.sh hold`, and use the same key on retry so registration is idempotent while different decisions retain different durable identities.
Register it with its captain briefing in the same call: the concrete choice, why it matters now, what waiting costs, the real options where they help, and your recommendation.
A title and a one-line reason cannot carry a decision, so a hold without that briefing reaches the captain marked as not yet written; the briefing is what makes the decision answerable without further investigation.
After inventorying the whole report and review surface, run `bin/fm-decision-hold.sh complete` with every unresolved key, or with `--none` only when the reviewed surface contains no unresolved captain decision.
A completed investigation and an ended visual review use this same owner and completion command; a visual tool, including Lavish, never owns a parallel completion policy.
Run the command in the originating work's authoritative `FM_HOME`; main-home work creates main-home holds, and secondmate-owned work creates holds in that secondmate home's backlog rather than copying them into the main backlog.
Expand All @@ -29,7 +31,7 @@ Bearings reads the resulting structured state and must never compensate by scrap

1. Read the complete investigation result and complete the visual review before declaring either complete.
2. Inventory only genuine unresolved choices that require the captain.
3. For each choice, choose a stable key and use the script's `hold` command with a concise title, reason, and repository.
3. For each choice, choose a stable key and use the script's `hold` command with a concise title, reason, repository, and the captain briefing fields.
4. Run the script's `complete` command with the full unresolved-key inventory for that review pass.
5. Relay the choices to the captain as decisions from Bearings' Captain's Call section under `AGENTS.md` section 9; do not use the word hold in captain chat.
6. After the captain decides, record dependent work with normal tasks-axi commands and block it by the hold identity.
Expand Down
24 changes: 21 additions & 3 deletions .opencode/plugins/fm-primary-turnend-guard.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ import { realpathSync } from "node:fs";
import { resolve } from "node:path";
import { encodeFirstmateOperationalInput } from "./lib/fm-operational-input.js";

// The shared guard has two independent stops and says which one fired in its own
// banner (bin/fm-turnend-guard.sh owns both headlines). A captain decision that
// has never been shown to the captain is not a supervision lapse, so the passive
// follow-up must not claim the watcher is down.
const CAPTAIN_CALL_HEADLINE = "TURN WOULD END WITHOUT TELLING THE CAPTAIN";

function turnEndPrefix(stderr) {
if (typeof stderr === "string" && stderr.includes(CAPTAIN_CALL_HEADLINE)) {
return (
"TURN WOULD END WITHOUT TELLING THE CAPTAIN. " +
"A decision is waiting on him that he has never been shown. Relay it in plain language before ending the turn.\n\n"
);
}
return (
"TURN WOULD END BLIND - supervision is off. " +
"The watcher cycle is missing, failed, or unhealthy. Follow the harness recovery instruction below before ending the turn.\n\n"
);
}


const COORDINATOR_KEY = "__firstmateOpenCodeWatchArm";

let skipNextIdle = false;
Expand Down Expand Up @@ -78,9 +98,7 @@ export const FmPrimaryTurnendGuard = async ({ client, directory, worktree }) =>
const text = await encodeFirstmateOperationalInput(
root,
"turn-end-guard",
"TURN WOULD END BLIND - supervision is off. " +
"The watcher cycle is missing, failed, or unhealthy. Follow the harness recovery instruction below before ending the turn.\n\n" +
result.stderr,
turnEndPrefix(result.stderr) + result.stderr,
);
await client.session.promptAsync({
path: { id: sessionID },
Expand Down
23 changes: 20 additions & 3 deletions .pi/extensions/fm-primary-turnend-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ function runSessionstartNudge(): string {
return result.stdout.trim();
}

// The shared guard has two independent stops and says which one fired in its own
// banner (bin/fm-turnend-guard.sh owns both headlines). A captain decision that
// has never been shown to the captain is not a supervision lapse, so the passive
// follow-up must not claim the watcher is down.
const CAPTAIN_CALL_HEADLINE = "TURN WOULD END WITHOUT TELLING THE CAPTAIN";

function turnEndPrefix(stderr: string): string {
if (stderr.includes(CAPTAIN_CALL_HEADLINE)) {
return (
"TURN WOULD END WITHOUT TELLING THE CAPTAIN. " +
"A decision is waiting on him that he has never been shown. Relay it in plain language before ending the turn.\n\n"
);
}
return (
"TURN WOULD END BLIND - supervision is off. " +
"The watcher cycle is missing, failed, or unhealthy. Follow the harness recovery instruction below before ending the turn.\n\n"
);
}

function runGuard(): Promise<{ code: number; stderr: string }> {
return new Promise((resolveResult) => {
const child = spawn(`${root}/bin/fm-turnend-guard.sh`, {
Expand Down Expand Up @@ -148,9 +167,7 @@ export default function (pi: ExtensionAPI) {
try {
const content = encodeFirstmateOperationalInput(
"turn-end-guard",
"TURN WOULD END BLIND - supervision is off. " +
"The watcher cycle is missing, failed, or unhealthy. Follow the harness recovery instruction below before ending the turn.\n\n" +
result.stderr,
turnEndPrefix(result.stderr) + result.stderr,
);
await pi.sendUserMessage(content, { deliverAs: "followUp" });
} catch {
Expand Down
14 changes: 11 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ state/ volatile runtime signals; gitignored
x-poll.error x-poll.claim-error generated X-mode relay and offer-claim diagnostic dedupe markers
.wake-queue durable queued wakes: epoch<TAB>seq<TAB>kind<TAB>key<TAB>payload
.afk durable away-mode flag; present = sub-supervisor may inject escalations (set by /afk, cleared on user return)
.captain-attention digest of the open decision/wait set most recently rendered to a captain-facing surface; bounds the interrupt, never the ledger (bin/fm-attention-lib.sh)
.watch.lock .wake-queue.lock watcher singleton and queue serialization locks
.claude-autoarm.lock .claude-autoarm-epoch .turnend-claude-blocks Claude Stop auto-arm single-flight, epoch, and guard-budget records; never touch
.hash-* .count-* .stale-* .stale-since-* .paused-* .wedge-escalations-* .seen-* .hb-surfaced-* .last-* .heartbeat-streak watcher internals; never touch
Expand Down Expand Up @@ -142,11 +143,13 @@ A lock-refused session must not spawn, steer, merge, drain the wake queue, repai
The secondmate liveness sweep deterministically accounts for every registered secondmate: it relaunches only from the recovery-grade `dead` or `missing` states, preserves ambiguous or unreadable targets, and reports skipped or failed guarantees as `SECONDMATE_LIVENESS:` lines (`bin/fm-bootstrap.sh`; `bin/fm-backend.sh`'s `fm_backend_agent_state`).
3. **Wake queue** - when locked, drains the durable wake queue and prints the raw records prominently as this turn's first work queue; a bounded, clearly labeled historical status-event annotation may follow a valid `signal` record but never replaces it or current-state reconciliation, and a lapsed watcher chain still surfaces here via the same guard alarm.
When the lock could not be acquired and verified, the queue is left untouched because no session mutation is authorized, and the guard's tangle/watcher-liveness alarms still print in read-only advisory mode without drain, supervision repair, or checkout repair commands.
4. **Context digest** - the full contents of `data/projects.md`, `data/secondmates.md`, `data/captain.md`, `data/captain-shared.md`, and `data/learnings.md`, each clearly delimited.
4. **Captain's call** - every open captain decision and meaningful wait, printed as this turn's second work queue before the context and the supervision block, so the first captain-facing reply carries them.
`bin/fm-attention.sh` renders it and `docs/captain-attention.md` is the operator reference; a locked session records the surfaced set, a read-only session does not.
5. **Context digest** - the full contents of `data/projects.md`, `data/secondmates.md`, `data/captain.md`, `data/captain-shared.md`, and `data/learnings.md`, each clearly delimited.
A file that does not exist prints an explicit `ABSENT` marker, never confused with an empty-but-present file: absence is meaningful (`captain.md` absent means use the firstmate repo's built-in defaults, `projects.md` absent means rebuild it from the clones under `projects/`, etc.).
5. **Fleet-state digest** - the compact backlog listing owned by `bin/fm-session-start.sh`; every `state/<id>.meta`; a bounded tail of each task's `state/<id>.status` (labeled as wake-EVENT history, not current state, with the full log path printed for a deeper read); the `state/.afk` flag; and one cheap alive/dead read of each task's recorded backend endpoint.
6. **Fleet-state digest** - the compact backlog listing owned by `bin/fm-session-start.sh`; every `state/<id>.meta`; a bounded tail of each task's `state/<id>.status` (labeled as wake-EVENT history, not current state, with the full log path printed for a deeper read); the `state/.afk` flag; and one cheap alive/dead read of each task's recorded backend endpoint.
That liveness line is a fast presence check only, not a full state read - when you need a crew's actual current state (a run-step, not just "is the pane there"), read it with `bin/fm-crew-state.sh <id>` as before; the digest deliberately skips that deeper, slower read for every task so it stays fast and bounded.
6. **Supervision operating instructions and next step** - after the wake queue and before context, the digest emits exactly one operating block for the detected primary harness.
7. **Supervision operating instructions and next step** - after the captain's call and before context, the digest emits exactly one operating block for the detected primary harness.
The closing reminder points back to that emitted block and preserves only the lock, afk, X-mode, and read-once reminders.
The script itself never starts supervision; the emitted harness protocol owns the exact wait or wake mechanism.

Expand Down Expand Up @@ -420,6 +423,11 @@ Reach the captain immediately for:
- Anything destructive, irreversible, or security-sensitive.
- A needed credential or login.

Never let the captain discover a stop by noticing that nothing moved.
`bin/fm-attention.sh` is the one place you read every open decision and meaningful wait from, and it prints them already translated; relay its content, and never send a reply that leaves an open decision unstated.
A decision must carry the concrete choice, why it matters now, what waiting costs, the real options where they help, and your recommendation; a wait must say what is being awaited and when it is next checked.
Record that plain language durably when you register the decision, through the briefing fields of `bin/fm-decision-hold.sh hold`, so it survives the session that discovered it.

Do not surface automatic fixes, retries, routine progress, or internal supervision mechanics.
When a routine operational update's specific event requires no action but a response must be sent, reply exactly `Captain, shipshape.` without characterizing the visible session's unrelated decisions.
Batch non-urgent updates into the next natural reply.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Launching a supported harness inside it instantiates your first mate - and makes
- **Explicit project modes** - each project ships via `no-mistakes`, `direct-PR`, or `local-only`, with an optional `+yolo` autonomy flag.
- **Optional secondmates** - opt in to persistent second mates that run from isolated firstmate homes with their own `FM_HOME`, state, projects, and session lock, supervising project clones or a project-less firstmate-repo domain, kept on the primary firstmate version by guarded local fast-forwards and checked for live agent processes at session start.
- **Event-driven, zero-token supervision** - a bash watcher sleeps on the fleet and wakes the first mate only when something needs you; verified primary harnesses also get a turn-end backstop that blocks or follows up on a blind stop when work is under way and supervision is not live.
- **Nothing waits silently** - every open decision and meaningful wait is collected in one captain-facing place; decisions carry the concrete choice, what waiting costs, and a recommendation, while waits say what is being awaited and when it is next checked.
Each one is surfaced once, stays listed until it is answered or clears, and a home holding only an unanswered decision is no longer treated as idle.
- **Optional X mode** - opt in with one local `.env` token so firstmate can answer your public `@myfirstmate` mentions, act on normal reversible mention requests through the same lifecycle as chat requests, acknowledge spawned work, and post up to three public-safe completion follow-ups within seven days for genuine milestones and the final outcome without changing non-X behavior; dry-run preview records would-be replies and dismissals locally before go-live.
- **Guarded by construction** - the first mate is read-only over your projects except for the guarded paths authorized by [hard rule 1](AGENTS.md#1-identity-and-prime-directives), with fleet sync's safe branch pruning remaining part of the fleet-sync exception; crewmates make every project change behind the configured merge authority.
- **Restart-proof** - all state lives on disk and in the active session backend (tmux by hard default, herdr or cmux when selected or auto-detected, zellij/orca when explicitly selected); kill the session anytime and the next one reconciles, including confirmed-dead secondmate agents, and carries on.
Expand Down Expand Up @@ -203,6 +205,7 @@ Firstmate's skills live in two separate places with different audiences:
- [docs/verification/runtime-backends.md](docs/verification/runtime-backends.md) - active maintainer verification for runtime backend guarantees.
- [docs/gitlab-merge-watch.md](docs/gitlab-merge-watch.md) - maintainer verification for GitLab merge watching on arbitrary instances.
- [docs/turnend-guard.md](docs/turnend-guard.md) - the primary session's current "no turn ends blind" backstop, scope, loop safety, and compatibility limits.
- [docs/captain-attention.md](docs/captain-attention.md) - how open decisions and waits are recorded, surfaced once, and cleared.
- [docs/verification/supervision.md](docs/verification/supervision.md) - active maintainer verification for session-start, guard, continuity, and wedge integrations.
- [docs/supervision-protocols/](docs/supervision-protocols/) - rendered primary-harness watcher protocols for Claude, Codex, OpenCode, Pi and `pi-signed`, Grok, and unknown harness fallback.
- [docs/scripts.md](docs/scripts.md) - the `bin/` toolbelt reference.
Expand Down
Loading
Loading