From 46e586e8b1ed63594d298b71934059d5b5671664 Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Wed, 7 Oct 2026 13:17:59 +0700 Subject: [PATCH 1/4] Skip the supervision engine's own transcript in the captain-message sweep bin/fm-supervision-host.sh runs a headless branch engine beside main in the same per-home transcript directory. Its working notes (stale wake, seq numbers, MAIN, pane ids) were being swept and shown to the reader as chat. The sweep now identifies that engine's transcript by session identity - the session id state/.supervision-host-engine records for it - excludes it from directory sweeps and payload naming, and forgets it from the remembered named transcripts so a home already carrying the stale state self-heals on its next run. --- bin/fm-captain-message-sweep.py | 30 +++++++++++++++ tests/command-center.test.sh | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/bin/fm-captain-message-sweep.py b/bin/fm-captain-message-sweep.py index bf9986c001a..fa348993bc7 100755 --- a/bin/fm-captain-message-sweep.py +++ b/bin/fm-captain-message-sweep.py @@ -168,6 +168,27 @@ def utc_now(): return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") +def supervision_engine_transcript(home, directory): + """The transcript of this home's current supervision-branch engine + conversation (bin/fm-supervision-host.sh, FM_SUPERVISION_ACTOR=branch), + identified by session identity: the session id state/.supervision-host-engine + records for it, never by what the engine's working notes say. Its own + working notes (stale wake, seq numbers, MAIN, pane ids) are never his + chat, so this file is excluded from a directory sweep and forgotten from + the remembered named transcripts, both without anyone clearing state by + hand. None when this home runs no such conversation right now.""" + if not directory: + return None + try: + with open(os.path.join(home, "state", ".supervision-host-engine"), + encoding="utf-8") as fh: + text = fh.read() + except (OSError, UnicodeDecodeError): + return None + m = re.search(r"(?m)^session=(\S+)$", text) + return os.path.join(directory, m.group(1) + ".jsonl") if m else None + + def default_transcript_dir(home): base = os.environ.get("CLAUDE_CONFIG_DIR") or os.path.expanduser("~/.claude") encoded = re.sub(r"[^A-Za-z0-9]", "-", os.path.abspath(home)) @@ -737,6 +758,12 @@ def sweep(home, since, paths=None, directory=None): and os.path.isfile(os.path.join(directory, f))), key=os.path.getmtime) targets += [p for p in named if p not in targets] + engine_transcript = supervision_engine_transcript(home, directory) + if engine_transcript: + targets = [p for p in targets if p != engine_transcript] + if cursor["files"].pop(engine_transcript, None) is not None: + write_cursor(cursor_path, cursor) + named = [p for p, f in cursor["files"].items() if isinstance(f, dict) and f.get("named")] targets = [p for p in targets if os.path.isfile(p)] if not targets: return {"active": False, "dir": directory, "transcripts": 0, "new": 0, @@ -852,6 +879,9 @@ def main(argv=None): parser.add_argument("--from-payload", action="store_true") args = parser.parse_args(argv) + if os.environ.get("FM_SUPERVISION_ACTOR") == "branch": + return 0 # this turn is the supervision engine's own, never his chat + home = os.path.abspath(args.home or os.path.join(os.path.dirname( os.path.abspath(__file__)), "..")) state_dir = os.path.join(home, "state") diff --git a/tests/command-center.test.sh b/tests/command-center.test.sh index 76c375f58ad..6f84d015fde 100755 --- a/tests/command-center.test.sh +++ b/tests/command-center.test.sh @@ -2369,6 +2369,72 @@ test_a_transcript_the_payload_names_is_captured_wherever_it_lives() { pass "a transcript the payload names is captured wherever it lives" } +# bin/fm-supervision-host.sh runs a headless branch engine beside main in the +# same per-home transcript directory; its own working notes (stale wake, seq +# numbers, MAIN, pane ids) are not his chat. The sweep identifies that +# engine's transcript by session identity - the session id +# state/.supervision-host-engine records for it - never by what it says. +test_the_supervision_engines_own_transcript_is_never_captured() { + local home tdir log at + home="$TMP_ROOT/engine-skip" + tdir="$TMP_ROOT/engine-skip-transcripts" + mkdir -p "$home/state" "$tdir" + at=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) + entry r-main end_turn false "$at" \ + '{"type":"text","text":"The fix landed on main."}' > "$tdir/sess-main.jsonl" + entry r-engine end_turn false "$at" \ + '{"type":"text","text":"stale wake seq 61, holding for jt-metal-process-explained"}' \ + > "$tdir/sess-engine.jsonl" + printf 'engine=claude\nmodel=sonnet\nsession=sess-engine\nkey=k\nturns=3\nconversation_cost=0\n' \ + > "$home/state/.supervision-host-engine" + + python3 "$SWEEP" --home "$home" --transcripts "$tdir" \ + || fail "the directory sweep failed" + log="$home/data/captain-messages.jsonl" + assert_equals 1 "$(wc -l < "$log")" \ + "the supervision engine's own transcript was captured alongside main's" + assert_grep 'fix landed on main' "$log" \ + "the main session's message was lost while excluding the engine's" + assert_not_contains "$(cat "$log")" 'stale wake seq 61' \ + "the supervision engine's working notes were captured as his chat" + + # A payload naming exactly the engine's transcript must never be recorded + # or remembered as a confirmed location either. + printf '{"transcript_path":"%s"}' "$tdir/sess-engine.jsonl" \ + | python3 "$SWEEP" --home "$home" --from-payload \ + || fail "the sweep failed on a payload naming the engine's transcript" + assert_equals 1 "$(wc -l < "$log")" \ + "a payload naming the supervision engine's transcript was captured" + assert_equals 0 "$(jq -r '.named' "$home/state/.captain-message-capture")" \ + "the supervision engine's transcript was remembered as a confirmed location" + + # A payload naming the main session's transcript still records as before. + printf '{"transcript_path":"%s"}' "$tdir/sess-main.jsonl" \ + | python3 "$SWEEP" --home "$home" --from-payload \ + || fail "the sweep failed on a payload naming the main session" + assert_equals 1 "$(jq -r '.named' "$home/state/.captain-message-capture")" \ + "the main session's transcript was not remembered once its payload named it" + + # A transcript already remembered as named before this fix is forgotten on + # the first run afterwards, with no state cleared by hand. + python3 - "$home/state/.captain-message-sweep" "$tdir/sess-engine.jsonl" <<'PYEOF' +import json, sys +path, engine = sys.argv[1], sys.argv[2] +with open(path, encoding="utf-8") as fh: + cursor = json.load(fh) +cursor["files"][engine] = {"off": 0, "named": True} +with open(path, "w", encoding="utf-8") as fh: + json.dump(cursor, fh) +PYEOF + python3 "$SWEEP" --home "$home" --transcripts "$tdir" \ + || fail "the directory sweep failed on a home with a pre-fix named engine transcript" + assert_equals 1 "$(jq -r '.named' "$home/state/.captain-message-capture")" \ + "a supervision engine transcript named before the fix was not forgotten on the first run after it" + assert_equals 1 "$(wc -l < "$log")" \ + "a supervision engine transcript named before the fix was captured once forgotten" + pass "the supervision engine's own transcript is identified by session identity and never captured, named, or remembered" +} + # The server sweeps every few seconds, so it is routinely the one that reads a # turn's bytes first. The hook's run then has nothing new to read - and the # payload it carries is still the only thing that knows where this session @@ -2661,6 +2727,7 @@ test_a_hand_recorded_question_without_its_id_folds_only_when_identical test_a_captured_message_is_labelled_only_by_the_evidence_it_carries test_a_message_recorded_by_hand_is_never_a_second_unlabelled_row test_a_transcript_the_payload_names_is_captured_wherever_it_lives +test_the_supervision_engines_own_transcript_is_never_captured test_a_named_transcript_is_remembered_even_with_nothing_new_to_read test_a_response_read_across_two_sweeps_is_recorded_once test_a_torn_row_costs_itself_and_nothing_after_it From 6fd0bf0d9282a4df5c765dc50e8b9772a56e1e34 Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Wed, 7 Oct 2026 13:29:25 +0700 Subject: [PATCH 2/4] no-mistakes(review): Exclude every launched supervision engine session from sweep --- bin/fm-captain-message-sweep.py | 56 ++++++++++++++++++------------- bin/fm-supervision-host.sh | 3 ++ tests/command-center.test.sh | 33 ++++++++++++++++-- tests/fm-supervision-host.test.sh | 9 +++++ 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/bin/fm-captain-message-sweep.py b/bin/fm-captain-message-sweep.py index fa348993bc7..0e167f625d7 100755 --- a/bin/fm-captain-message-sweep.py +++ b/bin/fm-captain-message-sweep.py @@ -168,25 +168,35 @@ def utc_now(): return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") -def supervision_engine_transcript(home, directory): - """The transcript of this home's current supervision-branch engine - conversation (bin/fm-supervision-host.sh, FM_SUPERVISION_ACTOR=branch), - identified by session identity: the session id state/.supervision-host-engine - records for it, never by what the engine's working notes say. Its own - working notes (stale wake, seq numbers, MAIN, pane ids) are never his - chat, so this file is excluded from a directory sweep and forgotten from - the remembered named transcripts, both without anyone clearing state by - hand. None when this home runs no such conversation right now.""" - if not directory: - return None +def supervision_engine_sessions(home): + """The session ids of every supervision-branch engine conversation this + home has run (bin/fm-supervision-host.sh): the ones + state/.supervision-host-engine-sessions lists, written before each + conversation's first turn starts and never removed, plus the one + state/.supervision-host-engine records. The engine's working notes (stale + wake, seq numbers, MAIN, pane ids) are never his chat, so a transcript + named for one of these sessions is never read or remembered, identified by + session identity and never by what its notes say.""" + state = os.path.join(home, "state") + sessions = set() try: - with open(os.path.join(home, "state", ".supervision-host-engine"), + with open(os.path.join(state, ".supervision-host-engine-sessions"), encoding="utf-8") as fh: - text = fh.read() + sessions.update(fh.read().split()) except (OSError, UnicodeDecodeError): - return None - m = re.search(r"(?m)^session=(\S+)$", text) - return os.path.join(directory, m.group(1) + ".jsonl") if m else None + pass + try: + with open(os.path.join(state, ".supervision-host-engine"), + encoding="utf-8") as fh: + sessions.update(re.findall(r"(?m)^session=(\S+)$", fh.read())) + except (OSError, UnicodeDecodeError): + pass + return sessions + + +def is_engine_transcript(path, sessions): + name = os.path.basename(path) + return name.endswith(".jsonl") and name[:-len(".jsonl")] in sessions def default_transcript_dir(home): @@ -758,10 +768,13 @@ def sweep(home, since, paths=None, directory=None): and os.path.isfile(os.path.join(directory, f))), key=os.path.getmtime) targets += [p for p in named if p not in targets] - engine_transcript = supervision_engine_transcript(home, directory) - if engine_transcript: - targets = [p for p in targets if p != engine_transcript] - if cursor["files"].pop(engine_transcript, None) is not None: + engine_sessions = supervision_engine_sessions(home) + if engine_sessions: + targets = [p for p in targets if not is_engine_transcript(p, engine_sessions)] + remembered = [p for p in cursor["files"] if is_engine_transcript(p, engine_sessions)] + if remembered: + for p in remembered: + del cursor["files"][p] write_cursor(cursor_path, cursor) named = [p for p, f in cursor["files"].items() if isinstance(f, dict) and f.get("named")] targets = [p for p in targets if os.path.isfile(p)] @@ -879,9 +892,6 @@ def main(argv=None): parser.add_argument("--from-payload", action="store_true") args = parser.parse_args(argv) - if os.environ.get("FM_SUPERVISION_ACTOR") == "branch": - return 0 # this turn is the supervision engine's own, never his chat - home = os.path.abspath(args.home or os.path.join(os.path.dirname( os.path.abspath(__file__)), "..")) state_dir = os.path.join(home, "state") diff --git a/bin/fm-supervision-host.sh b/bin/fm-supervision-host.sh index 26555b69633..c2b0a23f624 100755 --- a/bin/fm-supervision-host.sh +++ b/bin/fm-supervision-host.sh @@ -232,6 +232,7 @@ unset FM_WATCH_PREDECESSOR_ARM_PID FM_SUPERVISION_ACTOR FM_BRANCH_REPORT_TURN HOST_RECORD="$STATE/.supervision-host" ENGINE_RECORD="$STATE/.supervision-host-engine" +ENGINE_SESSIONS="$STATE/.supervision-host-engine-sessions" TURN_FILE="$STATE/.supervision-host-turn" RECEIPTS="$STATE/.supervision-host-receipts" PROMPT_FILE="$STATE/.supervision-host-prompt" @@ -948,6 +949,8 @@ handle_wake() { # errors=$(mktemp "$STATE/.supervision-host-errors.XXXXXX") || errors=/dev/null TURN_RESULT=$result TURN_ERRORS=$errors + grep -qxF -- "$ENGINE_SESSION" "$ENGINE_SESSIONS" 2>/dev/null \ + || printf '%s\n' "$ENGINE_SESSION" >> "$ENGINE_SESSIONS" ENGINE_RUNNING=1 # Backgrounded and waited, so a signal to the host is handled at once # instead of after the whole turn; the cleanup stops the engine. diff --git a/tests/command-center.test.sh b/tests/command-center.test.sh index 6f84d015fde..d1f7d6a305f 100755 --- a/tests/command-center.test.sh +++ b/tests/command-center.test.sh @@ -2371,9 +2371,11 @@ test_a_transcript_the_payload_names_is_captured_wherever_it_lives() { # bin/fm-supervision-host.sh runs a headless branch engine beside main in the # same per-home transcript directory; its own working notes (stale wake, seq -# numbers, MAIN, pane ids) are not his chat. The sweep identifies that -# engine's transcript by session identity - the session id -# state/.supervision-host-engine records for it - never by what it says. +# numbers, MAIN, pane ids) are not his chat. The sweep identifies an engine +# transcript by session identity - every session id the host listed in +# state/.supervision-host-engine-sessions before that conversation's first +# turn, plus the one state/.supervision-host-engine records - never by what +# it says. test_the_supervision_engines_own_transcript_is_never_captured() { local home tdir log at home="$TMP_ROOT/engine-skip" @@ -2432,6 +2434,31 @@ PYEOF "a supervision engine transcript named before the fix was not forgotten on the first run after it" assert_equals 1 "$(wc -l < "$log")" \ "a supervision engine transcript named before the fix was captured once forgotten" + + # A conversation the host has only launched - listed, its first turn not + # yet succeeded, or failed - is the engine's from its first byte. + entry r-engine-2 end_turn false "$at" \ + '{"type":"text","text":"stale wake seq 62, a new conversation"}' \ + > "$tdir/sess-engine-2.jsonl" + printf 'sess-engine\nsess-engine-2\n' > "$home/state/.supervision-host-engine-sessions" + python3 "$SWEEP" --home "$home" --transcripts "$tdir" \ + || fail "the directory sweep failed on a listed engine conversation" + assert_equals 1 "$(wc -l < "$log")" \ + "a listed engine conversation was captured before its first turn was recorded" + + # Rotation and a failed turn move or remove the record; every conversation + # once listed stays the engine's. + printf 'engine=claude\nmodel=sonnet\nsession=sess-engine-2\nkey=k\nturns=1\nconversation_cost=0\n' \ + > "$home/state/.supervision-host-engine" + python3 "$SWEEP" --home "$home" --transcripts "$tdir" \ + || fail "the directory sweep failed after the engine record rotated" + assert_equals 1 "$(wc -l < "$log")" \ + "the previous engine conversation was captured once the record rotated" + rm -f "$home/state/.supervision-host-engine" + python3 "$SWEEP" --home "$home" --transcripts "$tdir" \ + || fail "the directory sweep failed after the engine record was removed" + assert_equals 1 "$(wc -l < "$log")" \ + "an engine conversation was captured once a failed turn removed the record" pass "the supervision engine's own transcript is identified by session identity and never captured, named, or remembered" } diff --git a/tests/fm-supervision-host.test.sh b/tests/fm-supervision-host.test.sh index cab4ae040c6..51304570626 100755 --- a/tests/fm-supervision-host.test.sh +++ b/tests/fm-supervision-host.test.sh @@ -70,6 +70,11 @@ n=$(( $(ls "$FM_HOME"/engine-call.* 2>/dev/null | wc -l) + 1 )) printf 'mode=%s\nactor=%s\nholder=%s\nprimary=%s\nturn=%s\n' "$mode" "${FM_SUPERVISION_ACTOR:-}" \ "${FM_LEASE_HOLDER_PID:-}" "${FM_SUPERVISION_PRIMARY_HARNESS:-}" "${FM_BRANCH_REPORT_TURN:-}" for a in "$@"; do printf 'arg=%s\n' "$a"; done + listed=no + for a in "$@"; do + ! grep -qxF -- "$a" "$STATE/.supervision-host-engine-sessions" 2>/dev/null || listed=yes + done + printf 'listed=%s\n' "$listed" } > "$FM_HOME/engine-call.$n" case "$mode" in held|captain-held) printf 'ready\n' > "$FM_HOME/stub-ready" ;; esac # Like Claude, the reported cost is the conversation's running total. @@ -1933,6 +1938,9 @@ test_attended_latch_keeps_closes_on_main_and_records_recovery_off_main() { assert_re '^supervision-host: the supervision session could not take this wake: the engine turn failed \(exit 3\); this wake is yours$' \ "$home/host.out" "the first engine error must hand the wake back with its reason" assert_no_re 'paused' "$home/host.out" "one engine error must not latch the session" + assert_re '^listed=yes$' "$home/engine-call.1" "a conversation's session id must be listed before a turn that then fails" + assert_grep "$(sed -n '/^arg=--session-id$/{n;s/^arg=//p;}' "$home/engine-call.1")" "$home/state/.supervision-host-engine-sessions" \ + "a failed turn's conversation must stay listed as the engine's" main_drain_and_ack "$home" park_again "$home" @@ -2006,6 +2014,7 @@ test_away_wake_is_handled_on_the_engine_and_never_reaches_main() { assert_re '^arg=dontAsk$' "$first" "the engine must never prompt" assert_re '^arg=sonnet$' "$first" "the engine must default to its default model" assert_re '^arg=--session-id$' "$first" "the first turn must open a new conversation" + assert_re '^listed=yes$' "$first" "a new conversation's session id must be listed before its first turn starts" assert_re '^POSTURE: AWAY\.' "$first" "the wake must carry the away tail" assert_grep 'keep the export worker on low effort' "$home/state/.host-mirror.jsonl" "fixture: the captain's dialog was not mirrored" assert_no_re 'MAIN DIALOG MIRROR|low effort' "$first" "an away wake must carry no dialog mirror" From 793c1eb422c48108ca78419dcef88ab750ad32fc Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Wed, 7 Oct 2026 13:32:32 +0700 Subject: [PATCH 3/4] no-mistakes(review): Drop unneeded cursor purge for engine transcripts --- bin/fm-captain-message-sweep.py | 11 ++--------- tests/command-center.test.sh | 30 +----------------------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/bin/fm-captain-message-sweep.py b/bin/fm-captain-message-sweep.py index 0e167f625d7..fa5cd39c39c 100755 --- a/bin/fm-captain-message-sweep.py +++ b/bin/fm-captain-message-sweep.py @@ -175,7 +175,7 @@ def supervision_engine_sessions(home): conversation's first turn starts and never removed, plus the one state/.supervision-host-engine records. The engine's working notes (stale wake, seq numbers, MAIN, pane ids) are never his chat, so a transcript - named for one of these sessions is never read or remembered, identified by + named for one of these sessions is never read, identified by session identity and never by what its notes say.""" state = os.path.join(home, "state") sessions = set() @@ -769,14 +769,7 @@ def sweep(home, since, paths=None, directory=None): key=os.path.getmtime) targets += [p for p in named if p not in targets] engine_sessions = supervision_engine_sessions(home) - if engine_sessions: - targets = [p for p in targets if not is_engine_transcript(p, engine_sessions)] - remembered = [p for p in cursor["files"] if is_engine_transcript(p, engine_sessions)] - if remembered: - for p in remembered: - del cursor["files"][p] - write_cursor(cursor_path, cursor) - named = [p for p, f in cursor["files"].items() if isinstance(f, dict) and f.get("named")] + targets = [p for p in targets if not is_engine_transcript(p, engine_sessions)] targets = [p for p in targets if os.path.isfile(p)] if not targets: return {"active": False, "dir": directory, "transcripts": 0, "new": 0, diff --git a/tests/command-center.test.sh b/tests/command-center.test.sh index d1f7d6a305f..fa6380678ae 100755 --- a/tests/command-center.test.sh +++ b/tests/command-center.test.sh @@ -2400,16 +2400,6 @@ test_the_supervision_engines_own_transcript_is_never_captured() { assert_not_contains "$(cat "$log")" 'stale wake seq 61' \ "the supervision engine's working notes were captured as his chat" - # A payload naming exactly the engine's transcript must never be recorded - # or remembered as a confirmed location either. - printf '{"transcript_path":"%s"}' "$tdir/sess-engine.jsonl" \ - | python3 "$SWEEP" --home "$home" --from-payload \ - || fail "the sweep failed on a payload naming the engine's transcript" - assert_equals 1 "$(wc -l < "$log")" \ - "a payload naming the supervision engine's transcript was captured" - assert_equals 0 "$(jq -r '.named' "$home/state/.captain-message-capture")" \ - "the supervision engine's transcript was remembered as a confirmed location" - # A payload naming the main session's transcript still records as before. printf '{"transcript_path":"%s"}' "$tdir/sess-main.jsonl" \ | python3 "$SWEEP" --home "$home" --from-payload \ @@ -2417,24 +2407,6 @@ test_the_supervision_engines_own_transcript_is_never_captured() { assert_equals 1 "$(jq -r '.named' "$home/state/.captain-message-capture")" \ "the main session's transcript was not remembered once its payload named it" - # A transcript already remembered as named before this fix is forgotten on - # the first run afterwards, with no state cleared by hand. - python3 - "$home/state/.captain-message-sweep" "$tdir/sess-engine.jsonl" <<'PYEOF' -import json, sys -path, engine = sys.argv[1], sys.argv[2] -with open(path, encoding="utf-8") as fh: - cursor = json.load(fh) -cursor["files"][engine] = {"off": 0, "named": True} -with open(path, "w", encoding="utf-8") as fh: - json.dump(cursor, fh) -PYEOF - python3 "$SWEEP" --home "$home" --transcripts "$tdir" \ - || fail "the directory sweep failed on a home with a pre-fix named engine transcript" - assert_equals 1 "$(jq -r '.named' "$home/state/.captain-message-capture")" \ - "a supervision engine transcript named before the fix was not forgotten on the first run after it" - assert_equals 1 "$(wc -l < "$log")" \ - "a supervision engine transcript named before the fix was captured once forgotten" - # A conversation the host has only launched - listed, its first turn not # yet succeeded, or failed - is the engine's from its first byte. entry r-engine-2 end_turn false "$at" \ @@ -2459,7 +2431,7 @@ PYEOF || fail "the directory sweep failed after the engine record was removed" assert_equals 1 "$(wc -l < "$log")" \ "an engine conversation was captured once a failed turn removed the record" - pass "the supervision engine's own transcript is identified by session identity and never captured, named, or remembered" + pass "the supervision engine's own transcript is identified by session identity and never captured" } # The server sweeps every few seconds, so it is routinely the one that reads a From e2ebb71e566bccd16549abb24670c53fbb7ebb89 Mon Sep 17 00:00:00 2001 From: Tejas Shah Date: Wed, 7 Oct 2026 13:50:52 +0700 Subject: [PATCH 4/4] no-mistakes(document): Document supervision engine transcripts skipped by captain-message capture --- .agents/skills/operational-home-layout/SKILL.md | 2 +- bin/fm-captain-message-sweep.py | 2 ++ docs/command-center.md | 1 + docs/supervision-host.md | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.agents/skills/operational-home-layout/SKILL.md b/.agents/skills/operational-home-layout/SKILL.md index 635239c6649..ef62e6194aa 100644 --- a/.agents/skills/operational-home-layout/SKILL.md +++ b/.agents/skills/operational-home-layout/SKILL.md @@ -88,7 +88,7 @@ state/ runtime records and signals; gitignored branch-outcomes.jsonl .branch-outcomes-cursor .branch-outcomes-processed ..branch-outcome-index .branch-outcome-index-ready .branch-outcomes-tail.jsonl Pi supervision-branch durable outcome store, its read cursor, main's processed marker, bounded latest per-task status-coverage caches, their recovery marker, and a bounded display copy of the newest rows; bin/fm-branch-outcome.sh owns the formats branch-session/ .branch-session .branch-mirror-cursor the branch's per-main-session conversations, the pointer to the current one, and the dialog-mirror cursor; extension-owned (docs/pi-supervision-branch.md) .branch-eligible-rows .branch-eligible-owner .main-eligible-rows per-actor wake-row claims and branch-owner evidence; docs/watcher-continuity.md owns the acknowledgement contract - .supervision-host* supervision host process record, engine conversation, current turn scope and report receipts, and bounded ledger of every close and engine turn; bin/fm-supervision-host.sh owns them; never touch + .supervision-host* supervision host process record, engine conversation, every engine session id it has launched (read by bin/fm-captain-message-sweep.py to skip those transcripts), current turn scope and report receipts, and bounded ledger of every close and engine turn; bin/fm-supervision-host.sh owns them; never touch .lease- per-task supervision lease naming which actor (main or branch) may change that task; bin/fm-lease-lib.sh owns the contract the guarded scripts enforce x-watch.check.sh generated Relay poll shim; present only when opted in (section 14) tool-updates.check.sh generated watched-tool update poll shim and its .check-trust binding; present only after bin/fm-tool-update-check.sh arm; its report record .tool-updates is what keeps one pending update from being reported on every poll diff --git a/bin/fm-captain-message-sweep.py b/bin/fm-captain-message-sweep.py index fa5cd39c39c..fcf7a62f82b 100755 --- a/bin/fm-captain-message-sweep.py +++ b/bin/fm-captain-message-sweep.py @@ -17,6 +17,8 @@ # and swept by the directory runs too, and until one has, the capture record # says the directory is only inferred so the page never shows a green band over # a list it cannot vouch for. +# A supervision-host engine conversation can land in the same directory and is +# never swept (supervision_engine_sessions). # # WHO RUNS IT. Two callers, each enough for what it can see: # - bin/fm-captain-message-hook.sh, a Claude Stop hook, right as a turn ends, diff --git a/docs/command-center.md b/docs/command-center.md index dcc81549406..471ca0a0e9f 100644 --- a/docs/command-center.md +++ b/docs/command-center.md @@ -47,6 +47,7 @@ The reply box says which of these it is before you send. Your replies appear under the message, so the exchange reads as a conversation. These are captured automatically: `bin/fm-captain-message-sweep.py` reads the Claude conversation record on disk, which holds every message verbatim, and records every reply firstmate gave - no agent chooses or remembers to record anything. Only what firstmate said to you counts: its working narration between tool calls, a subagent's chatter, and the lines the harness wrote itself are not replies and never become messages. +Neither does anything the [supervision host](supervision-host.md)'s background engine writes: its conversations can sit in the same directory as firstmate's, and the sweep skips every one of them by session id. It runs from two places, and they know different things. The Claude Stop hook (`bin/fm-captain-message-hook.sh`) fires as each turn ends and hands over the hook payload, which NAMES the transcript that session writes; the sweep reads that one file and nothing else, so the hook is bounded to a few seconds and never holds a turn end. This server sweeps on its own poll cadence, over the directory it derives from the home's path plus every transcript a payload has named - that is what catches turns that ended unusually (interrupted, errored, killed) once their session moves on, and what backfills at startup. Until some session has named its transcript, the only thing capture has is that derived directory, which is a guess - a session started from somewhere else writes where nothing is looking - so the Messages list says so rather than showing a green band over a list it cannot vouch for. The messages already in the log are the dedupe record, so the two runners can never record the same message twice. diff --git a/docs/supervision-host.md b/docs/supervision-host.md index 1f693b69935..45f45ee68ad 100644 --- a/docs/supervision-host.md +++ b/docs/supervision-host.md @@ -371,6 +371,8 @@ Today the only verified engine is Claude's print mode, measured on Claude Code 2 - The prompt is the first argument and stdin is `/dev/null`, because an open stdin costs a three-second wait. - The engine runs from the tracked code root. So its session files land in Claude's own project store for that directory and appear in that directory's resume list. +- Before a conversation's first turn starts, the host appends its session id to `state/.supervision-host-engine-sessions` and never removes it. + So the captain-message capture (`bin/fm-captain-message-sweep.py`) never reads an engine conversation as firstmate's, whether its turn failed or the host has since moved to a new conversation. **Result and cost**