Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
514f0ab
fix: restore stock macOS Bash 3.2 brief scaffolding (#1093)
karotkriss Jul 28, 2026
2459f77
test: stabilize tmux teardown conformance baseline (#1209)
kunchenguid Jul 28, 2026
b6e351d
docs: slim quota-array-dispatch to the pace selection core (#1197)
kunchenguid Jul 28, 2026
e5bd082
feat(bin): inherit backend config into secondmate homes (#1219)
kunchenguid Jul 28, 2026
c0c0881
fix(pi): remove Calm's upper version ceiling (#1226)
kunchenguid Jul 29, 2026
7cbb3f6
fix(bin): allow session-local todo tools in the subagent guard (#1204)
danielkuykendall23-boop Jul 29, 2026
a117b41
fix(session-lock): resolve Claude bg-spare ancestry to the outermost …
trillium Jul 29, 2026
a323c2b
fix: conferma l'avvio del watcher su Windows/MSYS (#1212)
Unknownzed Jul 29, 2026
41ffd45
fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (#1195)
lucashalbert Jul 29, 2026
99533c5
fix: preserve dispatch identity across authentication checks (#1233)
kunchenguid Jul 29, 2026
6ec5e08
fix(bin): normalize relative durable paths (#1256)
sparkus Jul 29, 2026
c21bf54
refactor(skills): make Bearings chat-only by default (#1136)
deeto15 Jul 29, 2026
96e027e
Clarify follow-up routing during validation (#1277)
kunchenguid Jul 30, 2026
a24eac1
fix: honor concrete approval for project operations (#1272)
kunchenguid Jul 30, 2026
0bbb27b
fix(skills): route new project intake through secondmate scopes (#1275)
kunchenguid Jul 30, 2026
daf6dce
fix: scope validation corrections by accepted behavior (#1281)
kunchenguid Jul 30, 2026
a2d5f26
test: replace source assertions with behavioral coverage (#1282)
kunchenguid Jul 30, 2026
56a7ac6
fix(watch): escalate busy workers with no completed turn (#1286)
kunchenguid Jul 30, 2026
e595611
fix(gitignore): ignore config/ as a directory, not by exact filename …
karotkriss Jul 30, 2026
a53ffc1
fix(tests): replace source-content .gitignore assertion with behavior…
kunchenguid Jul 30, 2026
79e62b8
feat: bound and consolidate startup memory during stow (#1303)
kunchenguid Jul 30, 2026
f0d7cbe
fix(herdr): place workers in the launching workspace (#1328)
kunchenguid Jul 30, 2026
3a112a1
fix(calm): refine Calm working boat animation (#1339)
kunchenguid Jul 31, 2026
28b02d2
fix(dispatch): preflight candidate auth before quota escalation (#1349)
kunchenguid Jul 31, 2026
5fca47f
feat(x-mode): reconcile promised public replies deterministically (#1…
kunchenguid Jul 31, 2026
96542a4
feat(bin): replace busy heuristics with semantic lifecycle state (#1327)
kunchenguid Jul 31, 2026
621299a
fix: preserve Calm boat continuity across working periods (#1356)
kunchenguid Jul 31, 2026
f7d0d0a
fix: restore evidence-based dispatch eligibility (#1358)
kunchenguid Jul 31, 2026
3772964
docs: define captain instruction precedence (#1362)
kunchenguid Jul 31, 2026
9fdef64
docs: define validation supersession sequence (#1407)
kunchenguid Jul 31, 2026
000c1db
fix: bind backend overrides to exact-task authority (#1413)
kunchenguid Jul 31, 2026
66b0f77
fix(herdr): prevent focus flashes during projected workspace cleanup …
kunchenguid Jul 31, 2026
a805766
fix: prioritize completion runway in quota-aware dispatch (#1431)
kunchenguid Jul 31, 2026
68641a3
fix(bin): preserve full task contract in no-mistakes intent (#1447)
kunchenguid Aug 1, 2026
1e24757
fix(bin): parse punctuated secondmate registry entries safely (#1452)
kunchenguid Aug 1, 2026
8c21b10
feat(bin): add durable process-event supervision (#1483)
kunchenguid Aug 2, 2026
cd73e75
fix(bin): retire terminal process events and surface queued wakes (#1…
kunchenguid Aug 2, 2026
f5ab708
perf: shard portable serial tests across CI runners (#1544)
kunchenguid Aug 2, 2026
88b2a94
fix(bin): correct session lock and attached watcher supervision (#1545)
kunchenguid Aug 2, 2026
33a4287
fix(bin): harden Claude supervision auto-arm recovery (#1495)
kunchenguid Aug 2, 2026
4ee4a0a
feat(bin): require an explicit per-task delivery contract (#1563)
kunchenguid Aug 3, 2026
7809ab9
feat(bin): support remote secondmate homes (#1576)
kunchenguid Aug 3, 2026
976d97f
feat: add per-task trace context propagation (#995)
allstargg Aug 3, 2026
cf95112
fix(bin): harden tmux agent liveness across harnesses (#1577)
kunchenguid Aug 3, 2026
3d9d12d
feat(bin): propagate trace context to remote secondmates (#1609)
kunchenguid Aug 3, 2026
733a504
feat(bin): preflight remote runtime tool paths (#1623)
kunchenguid Aug 4, 2026
e5e8a67
feat: gate remote second mates on Herdr readiness (#1639)
kunchenguid Aug 4, 2026
c8edff3
fix: isolate remote secondmates in shared Herdr session (#1659)
kunchenguid Aug 4, 2026
a83be60
feat: route remote commands through an Aqua job worker (#1660)
kunchenguid Aug 4, 2026
fc3684a
fix: clarify remote doctor bootstrap path (#1691)
kunchenguid Aug 4, 2026
1939785
fix(bin): bound remote SSH dead-peer detection (#1699)
kunchenguid Aug 4, 2026
4a9979a
fix: report stale AXI tools during bootstrap (#1701)
kunchenguid Aug 4, 2026
d0461e4
fix: prevent false watcher-down alarms in Claude sessions (#1661)
karotkriss Aug 4, 2026
1cd97c0
test: prevent fixture temporary directory leaks (#1704)
kunchenguid Aug 4, 2026
3089a57
feat(herdr): enable presentation spaces by default (#1708)
kunchenguid Aug 4, 2026
bb352e7
fix(bin): surface fleet-wide open decisions on every wake drain (#1711)
kunchenguid Aug 5, 2026
7ef26c4
fix(bin): abort parked runs and reap leaked processes before teardown…
kunchenguid Aug 5, 2026
bea3d23
fix: resolve fm-remote-entrypoint.sh SCRIPT_DIR through a PATH symlin…
kunchenguid Aug 5, 2026
71f0b3f
fix(pi): gate Calm built-in overrides by activation state (#1724)
kunchenguid Aug 5, 2026
30b18b9
fix(bin): persist secondmate parent bindings for cleanup (#1727)
kunchenguid Aug 5, 2026
b37e30f
Merge remote-tracking branch 'upstream/main' into fm/kun-sync-scrub-a…
Aug 5, 2026
effce67
fix(spawn): scrub secret baseline env from crew launches
Aug 5, 2026
8b292c8
test(spawn): carry the ship delivery contract in scrub spawn tests
Aug 5, 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
3 changes: 2 additions & 1 deletion .agents/skills/bootstrap-diagnostics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ When any diagnostic needs captain attention, report the plain consequence and re
- `MISSING: <tool> (install: <command>)` - list the missing tools to the captain with a one-line purpose each plus the printed install commands, wait for consent (one approval may cover the list), then run `bin/fm-bootstrap.sh install <approved tools...>`.
For `treehouse`, this also covers an installed version whose `treehouse get` lacks `--lease`; treat it as an upgrade request.
For `no-mistakes`, this also covers an installed version older than 1.31.2, because crewmate validation briefs delegate gate mechanics to no-mistakes' version-matched guidance.
For `tasks-axi`, this also covers an installed build that fails the compatibility probe (`docs/configuration.md` "Backlog backend" owns the definition); `config/backlog-backend=manual` only suppresses the verbose `BOOTSTRAP_INFO: tasks-axi available` fact, not this missing-tool report.
For `gh-axi`, this also covers an installed version below the bootstrap-owned floor; treat it as an upgrade request so non-interactive PR merges keep a working bare `--squash` shorthand.
For `tasks-axi`, this also covers an installed build that fails the compatibility probe (`bin/fm-tasks-axi-lib.sh` owns the definition); `config/backlog-backend=manual` only suppresses the verbose `BOOTSTRAP_INFO: tasks-axi available` fact, not this missing-tool report.
For `quota-axi`, bootstrap requires it because firstmate reads its current output directly before resolving every crew-dispatch profile array; without it, report the missing requirement and do not choose around an unexamined candidate.
- `MISSING_MANUAL: <tool> (instructions: <url>)` - tell the captain why the tool is required and give them the printed instructions URL, but do not pass the tool to `bin/fm-bootstrap.sh install`; wait for the captain to complete the manual installation, then rerun session start to confirm the dependency is present.
- `BACKEND_INVALID: <name> (known: <names>)` - the resolved runtime backend has no verified dependency or lifecycle contract, so do not dispatch work until the invalid `FM_BACKEND` or `config/backend` value is corrected to one of the listed backends.
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The slot stays reserved across restarts until the lease is released.
Release happens only on explicit retirement or seed rollback, never on routine restart or recovery.

`bin/fm-home-seed.sh` copies the charter into the secondmate home as `data/charter.md`.
It also writes the required `.fm-secondmate-home` identity marker, which is gitignored and must remain in place for home validation.
It also writes the gitignored `.fm-secondmate-parent` durable binding before the required `.fm-secondmate-home` identity marker; the parser header in [`bin/fm-secondmate-parent-lib.sh`](../../../bin/fm-secondmate-parent-lib.sh) owns the record contract, and both files must remain in place.
`bin/fm-spawn.sh --secondmate` launches it through the secondmate harness path, resolving `config/secondmate-harness` -> `config/crew-harness` -> the primary's own harness unless an explicit per-spawn harness override is passed.

`config/secondmate-harness` may also pin a concrete model and effort for the secondmate agent, in the SAME file rather than a new one: the format is a single whitespace-separated line `<harness> [<model>] [<effort>]`, with only the first non-empty, non-comment line parsed.
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ jobs:
# worktree acquisition (presentation, workspace-per-home, autodetect).
bin/fm-install-treehouse.sh "$RUNNER_TEMP/bin"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- name: Provision spawn secret baseline
run: |
set -eu
umask 077
printf '%s\n' 'FM_TEST_BASELINE_SECRET=not-a-secret' > "$HOME/.secrets"
- name: Assert Herdr pin and protocol floor
run: |
set -eu
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ data/
.no-mistakes/
.lavish/
.fm-secondmate-home
.fm-secondmate-parent
.DS_Store
__pycache__/
*.pyc
Expand Down
133 changes: 123 additions & 10 deletions .pi/extensions/fm-calm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@
// diagnostic (see installCalmPresentationAdapter below) if a future Pi removes it; Pi
// still exposes no global renderer for arbitrary built-in or custom rows.
// docs/configuration.md owns the home-local Calm preference contract.
//
// Pi has one first-registration-wins ToolDefinition per tool name, with no merge or
// unregister operation. Keep Calm-off registration empty; keep Calm-on load-time
// registration synchronous because restored rows capture the registry before
// session_start; and collision-check only the later first-activation path, when
// getAllTools() is reliable. docs/calm-mode-feasibility.md owns the Pi-source evidence
// and docs/calm.md owns the user-facing behavior and non-retroactive first-toggle bound.
import { randomUUID } from "node:crypto";
import {
mkdirSync,
readFileSync,
realpathSync,
renameSync,
rmSync,
writeFileSync,
Expand All @@ -25,6 +33,7 @@ import type {
ExtensionAPI,
ExtensionUIContext,
ToolDefinition,
ToolInfo,
ToolRenderResultOptions,
} from "@earendil-works/pi-coding-agent";
import {
Expand Down Expand Up @@ -84,6 +93,21 @@ const extensionFile = fileURLToPath(import.meta.url);
const extensionDir = dirname(extensionFile);
const root = resolve(extensionDir, "../..");

// Resolves symlinks before comparing tool-ownership identity below: sourceInfo.path
// values come from independent path-resolution code paths (this module's own
// import.meta.url vs. Pi's extension loader), and macOS alone symlinks /tmp and /var
// to /private/..., so lexical string comparison alone spuriously reads a symlinked
// self-path as a foreign one. Falls back to the raw path for synthetic, non-file
// sourceInfo paths such as "<builtin:read>" or "<inline>", which realpathSync rejects.
const realpathOrSelf = (path: string): string => {
try {
return realpathSync(path);
} catch {
return path;
}
};
const extensionRealFile = realpathOrSelf(extensionFile);

// Each presentation adapter probes the exact Pi API it patches. If a future Pi removes
// that API, only the affected adapter degrades; the rest of Calm keeps working.
function installCalmPresentationAdapter(name: string, install: () => void): void {
Expand Down Expand Up @@ -166,9 +190,9 @@ export default function (pi: ExtensionAPI) {

registerFirstmateSyntheticPresentation(pi);

function registerBuiltIn<TParams extends TSchema, TDetails, TState>(
function wrapBuiltIn<TParams extends TSchema, TDetails, TState>(
factory: DefinitionFactory<TParams, TDetails, TState>,
): void {
): ToolDefinition<TParams, TDetails, TState> {
const definitions = new Map<string, ToolDefinition<TParams, TDetails, TState>>();
const definitionFor = (cwd: string): ToolDefinition<TParams, TDetails, TState> => {
let definition = definitions.get(cwd);
Expand Down Expand Up @@ -220,7 +244,7 @@ export default function (pi: ExtensionAPI) {
return shell;
};

pi.registerTool({
return {
...original,
renderShell: "self",

Expand Down Expand Up @@ -263,18 +287,106 @@ export default function (pi: ExtensionAPI) {
refreshStandardShell(state, theme, context);
return new Container();
},
};
}

// Each wrapBuiltIn() call below has its own concrete TParams/TDetails/TState; the
// array holding all seven has no single sound instantiation, so it is typed the same
// way Pi's own ToolDefinition consumers erase this (any, any, any).
const wrappedBuiltIns: ToolDefinition<any, any, any>[] = [
wrapBuiltIn(createReadToolDefinition),
wrapBuiltIn(createBashToolDefinition),
wrapBuiltIn(createEditToolDefinition),
wrapBuiltIn(createWriteToolDefinition),
wrapBuiltIn(createGrepToolDefinition),
wrapBuiltIn(createFindToolDefinition),
wrapBuiltIn(createLsToolDefinition),
];

// True once this extension has handled built-in registration for its lifetime:
// either all seven synchronously at load, or only the uncontested subset during
// first activation.
let builtInsRegistered = false;

// Gate on Calm already being on at load time. This must stay synchronous and
// unconditional here (see file header): a foreign-claim check is not reachable at
// this point, while deferral would make restored rows capture the wrong definition.
// A Calm-off session or reload registers nothing and creates no collision exposure.
if (loadCalmPreference()) {
for (const tool of wrappedBuiltIns) pi.registerTool(tool);
builtInsRegistered = true;
}

// Which of the 7 built-ins are currently owned by a different, non-builtin
// extension. Only safe to call once every extension has finished loading (see file
// header); never call this during the factory's own synchronous execution above.
function contestedBuiltIns(): ToolDefinition<any, any, any>[] {
let registered: ToolInfo[];
try {
registered = pi.getAllTools();
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
console.error(`Firstmate Calm: built-in ownership check unavailable, claiming every built-in unconditionally. ${reason}`);
return [];
}
return wrappedBuiltIns.filter((tool) => {
const owner = registered.find((info) => info.name === tool.name)?.sourceInfo;
return owner !== undefined && owner.source !== "builtin" && realpathOrSelf(owner.path) !== extensionRealFile;
});
}

registerBuiltIn(createReadToolDefinition);
registerBuiltIn(createBashToolDefinition);
registerBuiltIn(createEditToolDefinition);
registerBuiltIn(createWriteToolDefinition);
registerBuiltIn(createGrepToolDefinition);
registerBuiltIn(createFindToolDefinition);
registerBuiltIn(createLsToolDefinition);
// The first time Calm turns on in a session that started off, claim every
// uncontested built-in and leave each contested tool and its owning extension
// untouched. Tell the user which built-in Calm could not take over, since Calm's
// presentation does not apply to it.
function activateBuiltInsIfNeeded(ui: ExtensionUIContext): void {
if (builtInsRegistered) return;
const contested = contestedBuiltIns();
const contestedNames = new Set(contested.map((tool) => tool.name));
for (const tool of wrappedBuiltIns) {
if (!contestedNames.has(tool.name)) pi.registerTool(tool);
}
builtInsRegistered = true;
if (contested.length === 0) return;
const names = contested.map((tool) => `"${tool.name}"`).join(", ");
const plural = contested.length > 1;
ui.notify(
`Firstmate Calm: the ${names} built-in tool${plural ? "s are" : " is"} already provided by another extension, so Calm may not fully function for ${plural ? "them" : "it"} this session.`,
"warning",
);
for (const tool of contested) {
console.error(`Firstmate Calm: skipped claiming built-in "${tool.name}" because another extension already owns it.`);
}
}

// Backstop for the one case activateBuiltInsIfNeeded cannot reach: Calm registered
// unconditionally at load time because it was already on, without any chance to
// check for a foreign claim first, so it can still silently lose a name to an
// earlier-loaded extension. Runs on every session_start reason because a reload
// rebuilds every extension's registrations from scratch, so last session's clean
// bill of health does not carry over.
function reportBuiltInLosses(): void {
if (!builtInsRegistered) return;
let registered: ToolInfo[];
try {
registered = pi.getAllTools();
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
console.error(`Firstmate Calm: built-in ownership check unavailable. ${reason}`);
return;
}
for (const tool of wrappedBuiltIns) {
const owner = registered.find((info) => info.name === tool.name)?.sourceInfo;
if (owner && owner.source !== "builtin" && realpathOrSelf(owner.path) !== extensionRealFile) {
console.error(
`Firstmate Calm: another extension (${owner.path}) also claimed the built-in "${tool.name}" tool and won; Calm's presentation for it is unavailable this session.`,
);
}
}
}

pi.on("session_start", (_event, ctx) => {
reportBuiltInLosses();
exportRendering = false;
setCalmPresentation(loadCalmPreference());
setCalmStockExportRendering(false);
Expand Down Expand Up @@ -335,6 +447,7 @@ export default function (pi: ExtensionAPI) {
const active = !calmPresentationIsActive();
persistCalmPreference(active);
setCalmPresentation(active);
if (active) activateBuiltInsIfNeeded(ctx.ui);
publishPresentationState();
applyWorkingPresentation(ctx.ui, true);
ctx.ui.setHiddenThinkingLabel(active ? "" : undefined);
Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ config/backlog-backend backlog backend override; LOCAL, gitignored; absent or "
config/backend runtime session-provider backend override for new tasks; LOCAL, gitignored; absent = falls through to runtime auto-detection (the runtime firstmate itself is executing inside), then tmux; tmux is the verified reference backend (docs/tmux-backend.md), while herdr, zellij, orca, and cmux are experimental spawn backends (docs/herdr-backend.md, docs/zellij-backend.md, docs/orca-backend.md, docs/cmux-backend.md) - herdr and cmux can also be selected by runtime auto-detection, zellij and orca never are (always explicit), and codex-app is not accepted; see docs/codex-app-backend.md; inherited by secondmate homes under the primary-authoritative contract in secondmate-provisioning
config/calm Pi Calm presentation preference; LOCAL, gitignored, and not inherited; see docs/configuration.md "Pi Calm preference"
config/startup-memory-budget primary-authoritative per-home startup-memory budget; LOCAL, gitignored, materialized as 7,500 estimated tokens by locked primary bootstrap and inherited into secondmate homes; see docs/configuration.md "Startup memory budget"
config/herdr-presentation-spaces optional presence flag for Herdr's default-off disposable single-task visual projection; LOCAL, gitignored; inherited by secondmate homes; see docs/herdr-backend.md "Optional presentation spaces"
config/herdr-presentation-spaces optional "off" opt-out from Herdr's default-on disposable single-task visual projection; LOCAL, gitignored; inherited by secondmate homes; see docs/herdr-backend.md "Presentation spaces"
config/trace-context optional presence flag enabling default-off native W3C trace-context propagation to spawned agents; LOCAL, gitignored; inherited by secondmate homes; see docs/configuration.md "Trace context propagation" and docs/trace-context.md
config/cmux-socket-password optional cmux control-socket password; LOCAL, gitignored; read fresh on every cmux CLI call and passed through without ever overriding an operator's own ambient CMUX_SOCKET_PASSWORD when absent (docs/cmux-backend.md "Setup")
config/wedge-alarm optional away-mode wedge-alarm active-alert directives; LOCAL, gitignored; absent means auto (macOS Notification Center when available); see docs/wedge-alarm.md
Expand All @@ -92,7 +92,7 @@ state/ volatile runtime signals; gitignored
<id>.grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown
<id>.kimi-turnend-token firstmate-owned Kimi hook registry token for the task; removed by teardown
<id>.meta written by fm-spawn: window=, endpoint_task_id=, worktree=, project=, harness=, model=, effort=, kind=, mode=, yolo=, tasktmp=; an optional traceparent= only when trace context is enabled (docs/configuration.md "Trace context propagation"); kind=secondmate also records home= and projects=, plus remote_host=/remote_root=/remote_backend=/remote_herdr_session=/remote_target= for a remote route; a non-default runtime backend records further backend-specific fields (docs/configuration.md "Runtime backend"; bin/fm-backend.sh, section 8); fm-pr-check, including through fm-pr-merge, records one canonical pr= and the forge's pr_head= when available (GitHub pull requests and GitLab merge requests; docs/gitlab-merge-watch.md); fm-x-link appends x_request=, x_request_ts=, x_followups=, and optional x_platform=/x_reply_max_chars= for an X-mode-originated task (section 14)
<id>.herdr-presentation quarantinable attempt and restart-binding journal for Herdr's optional visual projection; never task or endpoint authority; see docs/herdr-backend.md "Optional presentation spaces"
<id>.herdr-presentation quarantinable attempt and restart-binding journal for Herdr's optional visual projection; never task or endpoint authority; see docs/herdr-backend.md "Presentation spaces"
<id>.check.sh authenticated slow poll; the watcher dispatches validated PR data and the byte-identified X shim through trusted repository scripts, runs registered custom checks from hash-validated private snapshots, and rejects every other state check without execution
<id>.check-trust private content binding created by fm-check-register.sh for an intentional custom check
<id>.pr-poll private validated data sidecar for the byte-static PR merge poll
Expand Down Expand Up @@ -145,6 +145,7 @@ A lock-refused session must not spawn, steer, merge, drain the wake queue, repai
Home-local stale Herdr projection cleanup and the six bootstrap MUTATING sweeps - non-executing legacy PR-check migration, fleet sync, secondmate convergence, secondmate liveness, pending remote handoff retry, and X-mode artifact writes - run only when this session actually holds the lock from step 1.
The secondmate liveness sweep deterministically accounts for every registered secondmate: it relaunches only from the recovery-grade `dead` or `missing` states, preserves ambiguous, unreadable, or unreachable remote targets, and reports skipped or failed guarantees as `SECONDMATE_LIVENESS:` lines (`bin/fm-bootstrap.sh`; `bin/fm-backend.sh`'s `fm_backend_agent_state`; `docs/remote-secondmates.md`).
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.
Every locked drain also prints a bounded fleet-wide `OPEN DECISIONS` section when durable decision records remain open, including when the queue itself is empty; reconcile those entries before continuing.
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.
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.).
Expand Down Expand Up @@ -365,6 +366,7 @@ No turn ends blind while work is under way, including turns described as holding

At the start of every wake-handling turn, drain the durable wake queue before peeking, reading beyond the reason line, steering, or starting work.
Session start is the only exception because its one-shot digest already drained while locked or deliberately left the queue untouched in lock-refused read-only mode.
Treat any `OPEN DECISIONS` section from the drain as actionable reconciliation input even when no wake record was queued.
A status line is a wake event, not current state; use `bin/fm-crew-state.sh` when current state matters, especially before re-escalating an old decision, blocker, or pause.
A declared `paused:` event means a bounded external wait expected to clear on its own, while `blocked:` means firstmate action is needed.

Expand Down
Loading
Loading