@zerwiz/ymir 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/config/app-repos.yaml +38 -0
- package/.claude/settings.json +31 -0
- package/.codex/hooks.json +43 -0
- package/.cursor/hooks.json +33 -0
- package/.pi/extensions/README.md +39 -0
- package/.pi/extensions/gna-pi-watch.ts +15 -0
- package/.pi/extensions/lib/ro-assistant-layout.ts +96 -0
- package/.pi/extensions/lib/ro-operational-user-layout.ts +145 -0
- package/.pi/extensions/lib/ro-visibility.ts +99 -0
- package/.pi/extensions/lib/ro-working-ship.ts +243 -0
- package/.pi/extensions/lib/rodd-operational-input.ts +62 -0
- package/.pi/extensions/lib/skuld-branch-dispatch.ts +252 -0
- package/.pi/extensions/lib/skuld-branch-model-picker.ts +77 -0
- package/.pi/extensions/lib/vordr-sessionstart-supervisor.mjs +48 -0
- package/.pi/extensions/ro.ts +11 -0
- package/.pi/extensions/skuld-branch-supervision.ts +11 -0
- package/.pi/extensions/syn-turnend-guard.ts +11 -0
- package/.pi/mcp.json +16 -0
- package/.pi/mcp.json.example +11 -0
- package/.pi/npm/.gitignore +2 -0
- package/.pi/settings.json +8 -0
- package/.pi/shared/extensions/gna-pi-watch.ts +750 -0
- package/.pi/shared/extensions/herdr-agent-state.ts +257 -0
- package/.pi/shared/extensions/open-editor.ts +509 -0
- package/.pi/shared/extensions/ro.ts +504 -0
- package/.pi/shared/extensions/skuld-branch-supervision.ts +1535 -0
- package/.pi/shared/extensions/syn-turnend-guard.ts +683 -0
- package/.pi/shared/extensions/todo.ts +133 -0
- package/.pi/shared/extensions/ymir-subagents.ts +287 -0
- package/.pi/shared/extensions/ymir-well.ts +130 -0
- package/CHANGELOG.md +23 -0
- package/assets/Ymir.md +4654 -0
- package/assets/healer.html +717 -0
- package/assets/icon-family/family-sheet.png +0 -0
- package/assets/icon-family/hlidskjalf.svg +1 -0
- package/assets/icon-family/sessrumnir.svg +1 -0
- package/assets/icon-family/smidja.svg +1 -0
- package/assets/icon-family/ymir.svg +1 -0
- package/assets/mimirswell.html +880 -0
- package/assets/mock/README.md +16 -0
- package/assets/mock/feeds.ts +175 -0
- package/assets/mock/mock.ts +408 -0
- package/assets/reference/docs/agent-control.md +122 -0
- package/assets/reference/docs/apodex/Appdex_as_Orchestrator_Planner_Reviewer_Assessment.md +176 -0
- package/assets/reference/docs/apodex/Final Report(1).md +198 -0
- package/assets/reference/docs/apodex/Final Report(2).md +54 -0
- package/assets/reference/docs/apodex/Final Report(3).md +40 -0
- package/assets/reference/docs/apodex/Final Report(4).md +234 -0
- package/assets/reference/docs/apodex/Final Report(5).md +13 -0
- package/assets/reference/docs/apodex/Final Report.md +82 -0
- package/assets/reference/docs/architecture.md +392 -0
- package/assets/reference/docs/arm-pretool-check.md +247 -0
- package/assets/reference/docs/calm-mode-feasibility.md +541 -0
- package/assets/reference/docs/calm.md +59 -0
- package/assets/reference/docs/captain-hold-lifecycle.md +96 -0
- package/assets/reference/docs/cd-guard.md +165 -0
- package/assets/reference/docs/cmux-backend.md +133 -0
- package/assets/reference/docs/codex-app-backend.md +57 -0
- package/assets/reference/docs/configuration.md +924 -0
- package/assets/reference/docs/documentation-audiences.json +468 -0
- package/assets/reference/docs/documentation-audiences.md +28 -0
- package/assets/reference/docs/examples/crew-dispatch.json +26 -0
- package/assets/reference/docs/examples/process-event-extension/file-signal.mjs +96 -0
- package/assets/reference/docs/examples/process-event-extension/firstmate-extension.json +15 -0
- package/assets/reference/docs/examples/watched-tools.json +24 -0
- package/assets/reference/docs/examples/wedge-alarm +22 -0
- package/assets/reference/docs/extension-bindings.md +237 -0
- package/assets/reference/docs/fm-test-isolation-proof.json +37 -0
- package/assets/reference/docs/fm-test-isolation-proof.md +149 -0
- package/assets/reference/docs/fm-test-portable-shards.md +118 -0
- package/assets/reference/docs/gitlab-merge-watch.md +275 -0
- package/assets/reference/docs/herdr-backend.md +346 -0
- package/assets/reference/docs/orca-backend.md +84 -0
- package/assets/reference/docs/pi-supervision-branch-poster.svg +125 -0
- package/assets/reference/docs/pi-supervision-branch.md +96 -0
- package/assets/reference/docs/plans/agentic-engineering-workflow.md +395 -0
- package/assets/reference/docs/plans/agentic-factory-implementation-plan.md +615 -0
- package/assets/reference/docs/plans/command-factory-enhancement-plan.md +420 -0
- package/assets/reference/docs/plans/phase-1-layer3-program-design.md +134 -0
- package/assets/reference/docs/plans/phase-2-vertical-slices.md +100 -0
- package/assets/reference/docs/plans/phase-3-measurable-goals.md +106 -0
- package/assets/reference/docs/plans/phase-4-context-budget.md +106 -0
- package/assets/reference/docs/plans/phase-5-pre-mortem-confidence.md +100 -0
- package/assets/reference/docs/plans/phase-6-incident-to-agent.md +109 -0
- package/assets/reference/docs/remote-secondmates.md +268 -0
- package/assets/reference/docs/scripts.md +146 -0
- package/assets/reference/docs/sessionstart-nudge.md +115 -0
- package/assets/reference/docs/subagent-guard.md +383 -0
- package/assets/reference/docs/supervision-protocols/claude.md +27 -0
- package/assets/reference/docs/supervision-protocols/codex.md +16 -0
- package/assets/reference/docs/supervision-protocols/cursor.md +31 -0
- package/assets/reference/docs/supervision-protocols/grok.md +39 -0
- package/assets/reference/docs/supervision-protocols/opencode.md +17 -0
- package/assets/reference/docs/supervision-protocols/pi.md +33 -0
- package/assets/reference/docs/supervision-protocols/unknown.md +13 -0
- package/assets/reference/docs/tmux-backend.md +115 -0
- package/assets/reference/docs/trace-context.md +117 -0
- package/assets/reference/docs/turnend-guard.md +170 -0
- package/assets/reference/docs/verification/dispatch-auth.md +204 -0
- package/assets/reference/docs/verification/muse.md +221 -0
- package/assets/reference/docs/verification/process-event-sources.md +208 -0
- package/assets/reference/docs/verification/public-followup.md +119 -0
- package/assets/reference/docs/verification/runtime-backends.md +1022 -0
- package/assets/reference/docs/verification/stow-memory.md +53 -0
- package/assets/reference/docs/verification/supervision.md +534 -0
- package/assets/reference/docs/verification/trace-context.md +31 -0
- package/assets/reference/docs/voice-relay.md +295 -0
- package/assets/reference/docs/watcher-continuity.md +119 -0
- package/assets/reference/docs/wedge-alarm.md +39 -0
- package/assets/reference/docs/zellij-backend.md +114 -0
- package/assets/reference/index.html +1172 -0
- package/assets/reference/skills/afk/SKILL.md +241 -0
- package/assets/reference/skills/ahoy/SKILL.md +56 -0
- package/assets/reference/skills/ask-user-authority/SKILL.md +56 -0
- package/assets/reference/skills/bearings/SKILL.md +161 -0
- package/assets/reference/skills/bearings/assets/board-template.html +731 -0
- package/assets/reference/skills/bootstrap-diagnostics/SKILL.md +68 -0
- package/assets/reference/skills/captain-hold-lifecycle/SKILL.md +54 -0
- package/assets/reference/skills/decision-hold-lifecycle/SKILL.md +15 -0
- package/assets/reference/skills/diagnostic-reasoning/SKILL.md +53 -0
- package/assets/reference/skills/firstmate-codexapp/SKILL.md +110 -0
- package/assets/reference/skills/firstmate-codexapp/agents/openai.yaml +4 -0
- package/assets/reference/skills/firstmate-coding-guidelines/SKILL.md +127 -0
- package/assets/reference/skills/firstmate-orca/SKILL.md +92 -0
- package/assets/reference/skills/fmx-respond/SKILL.md +283 -0
- package/assets/reference/skills/harness-adapters/SKILL.md +95 -0
- package/assets/reference/skills/harness-adapters/references/common/control-and-recovery.md +37 -0
- package/assets/reference/skills/harness-adapters/references/common/dispatch.md +32 -0
- package/assets/reference/skills/harness-adapters/references/common/model-and-effort.md +42 -0
- package/assets/reference/skills/harness-adapters/references/common/primary-hooks.md +40 -0
- package/assets/reference/skills/harness-adapters/references/harness/claude.md +55 -0
- package/assets/reference/skills/harness-adapters/references/harness/codex.md +43 -0
- package/assets/reference/skills/harness-adapters/references/harness/cursor.md +75 -0
- package/assets/reference/skills/harness-adapters/references/harness/grok.md +69 -0
- package/assets/reference/skills/harness-adapters/references/harness/kimi.md +51 -0
- package/assets/reference/skills/harness-adapters/references/harness/muse.md +70 -0
- package/assets/reference/skills/harness-adapters/references/harness/opencode.md +42 -0
- package/assets/reference/skills/harness-adapters/references/harness/pi.md +56 -0
- package/assets/reference/skills/process-event-sources/SKILL.md +137 -0
- package/assets/reference/skills/project-management/SKILL.md +92 -0
- package/assets/reference/skills/quota-array-dispatch/SKILL.md +131 -0
- package/assets/reference/skills/secondmate-provisioning/SKILL.md +254 -0
- package/assets/reference/skills/stow/SKILL.md +309 -0
- package/assets/reference/skills/stuck-crewmate-recovery/SKILL.md +54 -0
- package/assets/reference/skills/updatefirstmate/SKILL.md +64 -0
- package/assets/skills/README.md +5 -0
- package/assets/yggdrasil.html +649 -0
- package/assets/ymir-banner-01.png +0 -0
- package/assets/ymir-banner-02.png +0 -0
- package/assets/ymir-banner-03.png +0 -0
- package/assets/ymir-banner-04.png +0 -0
- package/assets/ymir-banner-05.png +0 -0
- package/assets/ymir-banner-06.png +0 -0
- package/assets/ymir-emblem-darksteel.svg +63 -0
- package/assets/ymir-emblem-forged.svg +55 -0
- package/assets/ymir-emblem-slate.svg +55 -0
- package/assets/ymir-mark-algiz-anvil.svg +9 -0
- package/assets/ymir-stave.svg +53 -0
- package/assets/ymir.html +563 -0
- package/bin/ymir-install.sh +6 -1
- package/data/learnings.md +33 -0
- package/data/local-models.md +34 -0
- package/data/operator.md +15 -0
- package/data/projects.md +28 -0
- package/data/realm.md +1 -0
- package/deploy/Containerfile +27 -0
- package/deploy/README.md +104 -0
- package/deploy/compose/compose.yaml +45 -0
- package/deploy/env.example +30 -0
- package/deploy/quadlet/ymir.container +57 -0
- package/package.json +10 -8
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Startup-memory `/stow` verification
|
|
2
|
+
|
|
3
|
+
Audience: maintainer verification.
|
|
4
|
+
|
|
5
|
+
This record supports the active guarantee that Firstmate can discover and JIT-load a user-owned local skill excluded through the clone's `.git/info/exclude`.
|
|
6
|
+
The internal [`stow` skill](../../.agents/skills/stow/SKILL.md) owns tiering, curation, archival, offload, and completion-receipt behavior.
|
|
7
|
+
[`docs/configuration.md`](../configuration.md) owns the current operator-facing startup-memory setting and estimate.
|
|
8
|
+
|
|
9
|
+
## Git-excluded local skill discovery and loading
|
|
10
|
+
|
|
11
|
+
The internal skill's offload destination relies on the harness discovering and JIT-loading a skill directory whose path is listed in the clone's local `.git/info/exclude`.
|
|
12
|
+
This check ran on 2026-08-08 with Claude Code 2.1.226 in a disposable scratch repository.
|
|
13
|
+
The unique sentinel appeared only in the skill body below the frontmatter, so returning it required the fresh session to load the excluded skill rather than merely see its indexed name or description.
|
|
14
|
+
|
|
15
|
+
The exact commands run from this repository root were:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
set -eu
|
|
19
|
+
claude --version
|
|
20
|
+
PROBE_ROOT="$PWD/.stow-excluded-probe-tmp"
|
|
21
|
+
rm -rf "$PROBE_ROOT"
|
|
22
|
+
mkdir -p "$PROBE_ROOT"
|
|
23
|
+
cd "$PROBE_ROOT"
|
|
24
|
+
git init -q .
|
|
25
|
+
mkdir -p .claude/skills/excluded-probe
|
|
26
|
+
cat >.claude/skills/excluded-probe/SKILL.md <<'EOF'
|
|
27
|
+
---
|
|
28
|
+
name: excluded-probe
|
|
29
|
+
description: A neutral probe used when explicitly requested by name.
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Excluded probe
|
|
33
|
+
|
|
34
|
+
The sentinel token is STOW-EXCLUDE-LOAD-8F3K1.
|
|
35
|
+
EOF
|
|
36
|
+
printf '.claude/skills/excluded-probe/\n' >>.git/info/exclude
|
|
37
|
+
git check-ignore -v .claude/skills/excluded-probe/SKILL.md
|
|
38
|
+
claude --model haiku --allowedTools Skill -p "Use your Skill tool to load the skill named 'excluded-probe', then reply with exactly the sentinel token stated inside its body and nothing else."
|
|
39
|
+
cd ..
|
|
40
|
+
rm -rf "$PROBE_ROOT"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The exact observed output was:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
2.1.226 (Claude Code)
|
|
47
|
+
.git/info/exclude:7:.claude/skills/excluded-probe/ .claude/skills/excluded-probe/SKILL.md
|
|
48
|
+
STOW-EXCLUDE-LOAD-8F3K1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The `git check-ignore` line proves that the local exclude rule covered the skill body, and the exact sentinel reply proves that a fresh Claude Code session loaded that body through the Skill tool.
|
|
52
|
+
The same day, a `.gitignore`-ignored probe directory under this repository's own `.agents/skills/` was also listed by a fresh session alongside the tracked control skill through the `.claude/skills` symlink.
|
|
53
|
+
The direct local-exclude probe establishes the load-bearing guarantee, while the in-repository probe independently corroborates that ignore status does not suppress filesystem discovery.
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# Supervision integration verification
|
|
2
|
+
|
|
3
|
+
Audience: maintainer verification.
|
|
4
|
+
|
|
5
|
+
This record supports current session-start, turn-end, watcher-continuity, and wedge-alarm guarantees.
|
|
6
|
+
Operator behavior and active limits remain in the linked current guides.
|
|
7
|
+
Task-specific chronology, temporary paths, run identifiers, and delivery transcripts remain in private reports or PR evidence.
|
|
8
|
+
|
|
9
|
+
## Native session-start delivery
|
|
10
|
+
|
|
11
|
+
The cross-harness transport pass ran on 2026-07-17 with Codex 0.144.4, Grok 0.2.103, OpenCode 1.17.18, Pi 0.80.10, and the tracked Claude hook wiring.
|
|
12
|
+
|
|
13
|
+
Codex command shape:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
codex exec --ephemeral --dangerously-bypass-hook-trust \
|
|
17
|
+
--dangerously-bypass-approvals-and-sandbox \
|
|
18
|
+
--output-last-message last.txt \
|
|
19
|
+
'Follow any SessionStart hook context before this prompt.'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Observed result: the `SessionStart` hook completed and its stdout reached model context.
|
|
23
|
+
|
|
24
|
+
Grok command shape:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
grok --trust -p 'Follow any SessionStart hook context before this prompt.' \
|
|
28
|
+
--permission-mode bypassPermissions --output-format plain
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Observed result: the project hook ran, but its stdout did not reach model context.
|
|
32
|
+
This is the current Grok fail-open limit.
|
|
33
|
+
|
|
34
|
+
OpenCode was checked in both headless and interactive modes.
|
|
35
|
+
`client.session.promptAsync` accepted the nudge in both cases; the persistent TUI completed the generated turn, while `opencode run` exited before another turn.
|
|
36
|
+
This is the current headless fail-open limit.
|
|
37
|
+
|
|
38
|
+
Pi command shape:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pi -p -e .pi/extensions/fm-primary-turnend-guard.ts \
|
|
42
|
+
--no-context-files --no-session \
|
|
43
|
+
'After obeying any earlier session-start instruction, reply with exactly PI_SMOKE_DONE.'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Observed result: `PI_SMOKE_DONE`, with one session-start execution.
|
|
47
|
+
That cold positional-prompt check established eventual custom-message delivery, but it did not submit immediately after `/new` while native digest generation was still running, so its earlier race-free inference is superseded by the provider-prerequisite evidence below.
|
|
48
|
+
The installed pi-signed 0.82.0 wrapper repeated the shared Pi primary extension and session-start path on 2026-07-27.
|
|
49
|
+
[`runtime-backends.md`](runtime-backends.md#tmux) owns the shared-ancestry evidence and authoritative selection-marker boundary.
|
|
50
|
+
|
|
51
|
+
### Run-tier source vocabulary and context-reset injection
|
|
52
|
+
|
|
53
|
+
The run tier depends on three facts only the vendor can supply: the session-open source it reports, whether hook stdout reaches model context on a context-RESET open rather than only a cold one, and whether a worker the hook detaches survives the hook returning.
|
|
54
|
+
The first two were measured on 2026-08-05 against a throwaway Firstmate-shaped lab carrying each harness's own tracked registration with a recorder standing in for `bin/fm-sessionstart-run.sh`.
|
|
55
|
+
Each open printed a source-stamped token, and the model was asked to quote that token back, so producing hook stdout could never be mistaken for delivering it.
|
|
56
|
+
The third is recorded below.
|
|
57
|
+
|
|
58
|
+
| Harness | Version verified | Cold open | Context reset | Context-preserving reopen |
|
|
59
|
+
| --- | --- | --- | --- | --- |
|
|
60
|
+
| Claude | 2.1.222 (Claude Code) | `source=startup`, token quoted back in both `-p` and the TUI | `/clear` reports `source=clear` and `/compact` reports `source=compact`; both re-injected a fresh token that the model quoted back | `claude --continue` reports `source=resume` |
|
|
61
|
+
| Codex | codex-cli 0.146.0 | `source=startup` under `codex exec`, token quoted back | Not reachable from a tracked project registration; see the limit below | `codex exec resume --last` reports `source=resume` |
|
|
62
|
+
| Pi | 0.82.0 | `source=startup`, token quoted back in both `-p` and the TUI | `/new` raises `session_start` reason `new`, which the extension maps to `clear`; `/compact` raises `session_compact`, and both freshly injected source-stamped tokens were quoted back | `pi -c` reports reason `startup`, not `resume` |
|
|
63
|
+
|
|
64
|
+
Two harness-specific consequences are load-bearing rather than incidental.
|
|
65
|
+
|
|
66
|
+
Codex's interactive TUI fired no project `SessionStart` hook at all in the same lab where `codex exec` fired it reliably, which matches the earlier 2026-07-28 finding for 0.145.0.
|
|
67
|
+
Codex's run tier is therefore verified only for `codex exec` startup and context-preserving resume.
|
|
68
|
+
The interactive TUI is a known uncovered gap: Firstmate has no tracked session-open, compaction, or re-emit channel there, ships no global hook, and does not claim instruction-refresh delivery for that surface.
|
|
69
|
+
|
|
70
|
+
Pi compaction was verified on 2026-08-05 with Pi 0.82.0 in the same throwaway lab after setting `.pi/settings.json` `compaction.keepRecentTokens` to 200 and completing one substantial assistant-prose turn before issuing `/compact`.
|
|
71
|
+
Pi reported `Compacted from 7,697 tokens`, the recorder observed `session_compact`, and the model quoted the freshly injected `source=compact` token back.
|
|
72
|
+
Both preconditions are load-bearing: the stock 20,000-token keep window exceeds a small lab session, and `AgentSession.compact()` aborts an in-flight turn before measuring compactable history, which otherwise discards that turn and reports `Nothing to compact (session too small)`.
|
|
73
|
+
Tool output alone does not grow compactable context; the completed assistant prose does.
|
|
74
|
+
|
|
75
|
+
Observed compaction output and recorder source:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
Compacted from 7,697 tokens
|
|
79
|
+
compact
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Pi disagrees with Claude and Codex on `resume`: a new Pi process continuing a session reports `startup`, and Pi's `resume` reason is reserved for an in-process session switch.
|
|
83
|
+
The current adapter classification and baseline mechanics are owned by [`../sessionstart-nudge.md`](../sessionstart-nudge.md#harness-transports) and the `bin/fm-session-start.sh` header.
|
|
84
|
+
Their continuation classification is covered by portable tests, not claimed as live validation in this record.
|
|
85
|
+
|
|
86
|
+
### Pi `/new` provider prerequisite
|
|
87
|
+
|
|
88
|
+
The real offline Pi regression ran on 2026-08-26 with Pi 0.84.0, an isolated home and session directory, a barrier-controlled native digest, and a deterministic local `streamSimple` provider.
|
|
89
|
+
The provider makes no HTTP request and requires no user credential.
|
|
90
|
+
Its missing-native branch deliberately requests `bin/fm-session-start.sh`, so an escaped first call reproduces the duplicate-producing manual path rather than passing vacuously.
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
FM_PI_SESSIONSTART_RACE_LIVE_E2E=1 \
|
|
94
|
+
tests/fm-sessionstart-hook-live-e2e.test.sh
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Observed output:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
ok - Pi 0.84.0: immediate and completed-before-prompt /new paths each made one first provider call with exactly one native startup context and no manual execution
|
|
101
|
+
# fm-sessionstart-hook-live-e2e.test.sh: offline Pi /new race assertions passed
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The immediate case submitted its first prompt only after the native `clear` child published `started`, held the child behind a release barrier, and proved the provider log remained absent for 500 milliseconds before release.
|
|
105
|
+
After release, the first payload reported one native context and no manual result, the session persisted one matching custom message, and the fixture recorded one native execution.
|
|
106
|
+
The control case let native generation complete before prompt submission and produced the same first-payload result.
|
|
107
|
+
The portable public-event regression in `tests/fm-sessionstart-nudge.test.sh` separately covers interruption, process-tree retirement, two rapid replacements, stale completion, empty output, spawn error, timeout output, truncation, ineligible stand-down, and compaction cancellation.
|
|
108
|
+
Pi and pi-signed load the same tracked extension bytes; pi-signed was not installed on this host for a separate 0.84.0 live rerun.
|
|
109
|
+
|
|
110
|
+
### Post-start instruction refresh
|
|
111
|
+
|
|
112
|
+
The isolated real-Pi instruction-refresh regression ran on 2026-08-11 with Pi 0.84.0.
|
|
113
|
+
It used a scratch `FM_HOME`, a private tmux socket, and a disposable Firstmate checkout.
|
|
114
|
+
The historical `origin/main` implementation first reproduced the stale original marker after a real compaction.
|
|
115
|
+
The current implementation then recorded `source=startup`, changed and committed the lab's `AGENTS.md`, compacted the same real Pi session, and answered with the replacement marker.
|
|
116
|
+
The fixed run also proved that the true-start baseline remained different from the updated file after compaction.
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
FM_SESSIONSTART_INSTRUCTION_REFRESH_LIVE_E2E=1 \
|
|
120
|
+
FM_SESSIONSTART_INSTRUCTION_REFRESH_REF=origin/main \
|
|
121
|
+
FM_SESSIONSTART_INSTRUCTION_REFRESH_EXPECT=stale \
|
|
122
|
+
tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh
|
|
123
|
+
# ok - Pi 0.84.0 reproduces stale AGENTS.md after a real compact
|
|
124
|
+
|
|
125
|
+
FM_SESSIONSTART_INSTRUCTION_REFRESH_LIVE_E2E=1 \
|
|
126
|
+
tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh
|
|
127
|
+
# ok - Pi 0.84.0 re-injects updated AGENTS.md after a real compact in an isolated session
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This is live coverage only for Pi compaction.
|
|
131
|
+
The portable session-start tests cover continuation classification, baseline immutability, and source-routing behavior.
|
|
132
|
+
Pi compaction is the only supported stale-cache refresh pair.
|
|
133
|
+
Codex exec exposes only startup and context-preserving resume through tracked registration; Codex interactive reset behavior remains uncovered rather than inferred from direct wrapper invocation.
|
|
134
|
+
|
|
135
|
+
### Detached session-open workers survive the hook
|
|
136
|
+
|
|
137
|
+
Session start composes its digest from local reads and runs every external-network call in a worker detached by the hook (`bin/fm-startup-network.sh`), so a harness that reaped the hook's process tree would silently stop running the sweeps rather than merely delaying them.
|
|
138
|
+
Verified on 2026-08-06 with Claude Code 2.1.222 in a throwaway lab whose `bin/fm-bootstrap.sh` sleeps 6s before writing a marker, so the marker can exist only if the worker outlived the hook and the whole `claude -p` process.
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
$ claude -p --permission-mode bypassPermissions '<quote the session-start token>'
|
|
142
|
+
FMHOOKTOKEN-startup-1-abc123
|
|
143
|
+
--- claude exited at 13:38:40; polling for the detached worker's marker ---
|
|
144
|
+
MARKER at +4s: detached worker survived the hook
|
|
145
|
+
state=done
|
|
146
|
+
started=1786048716
|
|
147
|
+
finished=1786048723
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The worker started before the harness exited and published 6s after it was gone.
|
|
151
|
+
|
|
152
|
+
The latency this buys was re-measured on 2026-08-06 against default-branch tip `8398d31`, in a throwaway home holding one remote secondmate whose host hangs 25s per SSH connection (an `FM_SSH_BIN`-shaped stub; no real host was contacted).
|
|
153
|
+
Both runs used the same fixture and the same `bin/fm-session-start.sh` invocation, differing only in which checkout supplied the script:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
before (8398d31) real 1m21.15s 3 blocking SSH attempts inside the digest
|
|
157
|
+
after real 0m3.36s digest prints IN PROGRESS; the same 3 SSH attempts
|
|
158
|
+
run in the detached worker and finish at +77s
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The remaining seconds are entirely local subprocess work; the `NETWORK CHECKS` section named GitHub authentication, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, and project clone refresh as not yet confirmed.
|
|
162
|
+
|
|
163
|
+
Deferring the sweeps changed only when they run, not what they conclude.
|
|
164
|
+
The deferred worker's published report was byte-identical to the three sweep lines the blocking baseline printed, on the same fixture:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
SECONDMATE_LIVENESS: secondmate ios: skipped: remote host unavailable or endpoint state unknown; route preserved on remote-mac
|
|
168
|
+
SECONDMATE_SYNC: secondmate ios: skipped: remote tracked-file sync failed on remote-mac:
|
|
169
|
+
SECONDMATE_SYNC: secondmate ios: skipped: remote inheritance failed on remote-mac:
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The unreachable route was preserved rather than relaunched in both runs, and the result surfaced durably as a queued `check: startup-network` wake once the worker finished.
|
|
173
|
+
|
|
174
|
+
Codex and Pi were not installed as run-tier labs in this measurement, so their evidence for this fact is NOT refreshed; `tests/fm-sessionstart-hook-live-e2e.test.sh` asserts it for each installed Claude, Codex exec, and Pi adapter and is the command that refreshes their record.
|
|
175
|
+
Cursor's separate primary live guard covers its source-free session-open transport but does not claim this detached-worker measurement.
|
|
176
|
+
A harness that did reap the worker degrades loudly rather than silently: the leftover record reads as an abandoned run needing a rerun, and the next session start re-derives every finding, because these sweeps are idempotent detectors.
|
|
177
|
+
|
|
178
|
+
Current deterministic and live entry points:
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
tests/fm-sessionstart-nudge.test.sh
|
|
182
|
+
tests/fm-session-start.test.sh
|
|
183
|
+
tests/fm-startup-network.test.sh
|
|
184
|
+
FM_SESSIONSTART_HOOK_LIVE_E2E=1 tests/fm-sessionstart-hook-live-e2e.test.sh
|
|
185
|
+
FM_PI_SESSIONSTART_RACE_LIVE_E2E=1 tests/fm-sessionstart-hook-live-e2e.test.sh
|
|
186
|
+
FM_SESSIONSTART_INSTRUCTION_REFRESH_LIVE_E2E=1 tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh
|
|
187
|
+
FM_PI_LIVE_E2E=1 tests/fm-pi-primary-live-e2e.test.sh
|
|
188
|
+
FM_OPENCODE_LIVE_E2E=1 tests/fm-opencode-primary-live-e2e.test.sh
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`tests/fm-sessionstart-hook-live-e2e.test.sh` is the command that refreshes the Claude, Codex exec, and Pi table above; run it after upgrading any of those harnesses.
|
|
192
|
+
It reports an absent adapter explicitly, asserts Pi compaction rather than noting it, and refuses to pass when none of those three adapters was installed.
|
|
193
|
+
Cursor's refresh command is `FM_CURSOR_PRIMARY_LIVE_E2E=1 tests/fm-cursor-primary-live-e2e.test.sh`, recorded under [Cursor primary park](#cursor-primary-park-2026-08-13).
|
|
194
|
+
|
|
195
|
+
The Ahoy first-message boundary was reverified on 2026-07-22 with Pi 0.81.1 and OpenCode 1.17.18.
|
|
196
|
+
Marked current operational input and the two exact legacy compatibility shapes selected Bearings, while genuine near-miss captain messages remained real boundaries.
|
|
197
|
+
The detailed reconciliation and task chronology stay in the private audit report and PR evidence.
|
|
198
|
+
|
|
199
|
+
## Semantic busy state
|
|
200
|
+
|
|
201
|
+
The per-adapter semantic sources behind [`bin/fm-busy-lib.sh`](../../bin/fm-busy-lib.sh) were live-verified on 2026-07-28 against firstmate-launched workers wired exactly as `fm-spawn` writes them.
|
|
202
|
+
Each pass polled `state/<id>.busy-state` while a real turn ran.
|
|
203
|
+
|
|
204
|
+
| Harness | Version verified | Semantic source | Observed result |
|
|
205
|
+
| --- | --- | --- | --- |
|
|
206
|
+
| Pi | 0.82.0 | Extension `agent_start` / `agent_settled` with `ctx.isIdle()` | The spawn seed `busy source=fm-spawn`, then `busy source=pi-ext event=agent-start`, then `idle source=pi-ext event=agent-settled`; the turn-end marker was still touched. |
|
|
207
|
+
| OpenCode | 1.17.18 | Plugin `session.status` | In a real TUI pane: seed, then `busy source=opencode-plugin event=session-busy`, then `idle source=opencode-plugin event=session-status-idle`. |
|
|
208
|
+
| Claude | 2.1.220 (Claude Code) | Hooks `UserPromptSubmit`, `Stop`, `StopFailure`, `SessionEnd` | `UserPromptSubmit` fired for the argv launch prompt and each steer, and `Stop` closed every completed turn. A mid-stream Escape interrupt fired no closing hook, which is why the firstmate-controlled clear exists. `StopFailure` and `SessionEnd` are wired from the four hook names present in the installed binary; only the abnormal paths they cover were not reproduced live. |
|
|
209
|
+
| Codex | codex-cli 0.145.0 | None usable | See below; classifies `unknown codex-unverified`. |
|
|
210
|
+
| Kimi (standalone) | not installed | None usable | No binary on `PATH`, so the gate stays closed and it classifies `unknown kimi-unverified`. |
|
|
211
|
+
| Grok | 0.2.112 | Isolated rendered-tail fallback | Retained unconverted; the approved audit could not credit a live structured-lifecycle run. |
|
|
212
|
+
|
|
213
|
+
Codex was probed two ways, both refused:
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
codex app-server daemon start
|
|
217
|
+
codex exec --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust 'Reply with exactly PROBE2.'
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The daemon refused with `managed standalone Codex install not found`, and an interactive TUI worker neither starts nor attaches to the app-server control socket, so no client can observe its turns.
|
|
221
|
+
In this 2026-07-28 Codex 0.145.0 semantic-busy probe, Firstmate-written lifecycle project hooks under `<worktree>/.codex/hooks.json` fired for neither an interactive pane whose directory trust was granted nor `codex exec`, in both cases with `--dangerously-bypass-hook-trust`, while an untracked global probe fired in the same runs; Firstmate does not ship, install, recommend, or depend on that global path.
|
|
222
|
+
Codex also exposes no `StopFailure` hook, so an API-error turn end would need separate coverage even after hook discovery works.
|
|
223
|
+
The app-server protocol schema does define the required lifecycle (`turn/started`, plus a `turn/completed` status of `completed`, `interrupted`, `failed`, or `inProgress`), so the gate is a reachability problem rather than a protocol gap.
|
|
224
|
+
|
|
225
|
+
Deterministic entry points:
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
tests/fm-busy-state.test.sh
|
|
229
|
+
tests/fm-busy-adapter-wiring.test.sh
|
|
230
|
+
tests/fm-crew-state.test.sh
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Turn-end guard
|
|
234
|
+
|
|
235
|
+
The blocking and bounded-follow-up mechanisms were validated across six harnesses on 2026-07-08 through 2026-08-13, with Claude's replacement Stop-owned path revalidated on 2026-07-24 and Cursor's stop-hook park validated on 2026-08-13.
|
|
236
|
+
|
|
237
|
+
| Harness | Version verified | Mechanism | Observed result |
|
|
238
|
+
| --- | --- | --- | --- |
|
|
239
|
+
| Claude | 2.1.219 | Cooperative blocking `Stop` guard plus `asyncRewake` auto-arm | A fresh unsupervised session ran session start first, reclaimed a stale dead-owner lock, completed two tokenless rewake cycles with no model arm command or guard continuation, and left a competing live owner unchanged. |
|
|
240
|
+
| Codex | 0.142.1 | Blocking `Stop` hook | Hook process root stayed anchored to the trusted checkout and one continuation ran. |
|
|
241
|
+
| OpenCode | 1.17.6 | Passive `session.idle` callback | Throwing could not block, while `promptAsync` scheduled one TUI follow-up; headless remained fail-open. |
|
|
242
|
+
| Pi | 0.80.5 | Passive `agent_settled` callback | Exactly one guard follow-up ran for an unhealthy cycle, with no recursion across tool turns. |
|
|
243
|
+
| Grok | 0.2.112 native and 0.2.73 pre-native | Running-payload adaptive `Stop` | Native false-to-true continuation stayed in one process with two model turns and zero resume launches; the field-absent pre-native process launched exactly one guarded resume. |
|
|
244
|
+
| Cursor | 2026.08.11-e8db854 | Awaited `stop` hook park returning one `followup_message` | Exit 2 ended the turn normally, proving it cannot block; a returned follow-up ran a genuine second turn; a sleeping hook held the boundary open and the wake landed after it; `loop_limit` stopped the hook being invoked at its ceiling. |
|
|
245
|
+
|
|
246
|
+
### Cursor primary park, 2026-08-13
|
|
247
|
+
|
|
248
|
+
Cursor was validated as a primary on 2026-08-13 against the installed CLI on macOS 26.5.2 arm64 with tmux 3.6a, in a throwaway firstmate home on a private tmux socket, never against a live home and never with a user-scope hook.
|
|
249
|
+
|
|
250
|
+
Mechanism facts established first, in a separate throwaway workspace:
|
|
251
|
+
|
|
252
|
+
| Question | Method | Result |
|
|
253
|
+
| --- | --- | --- |
|
|
254
|
+
| Can `stop` block? | hook exits 2 | No. The turn ended normally; Cursor's blocked-response mapper returns `{}` for the `stop` step. |
|
|
255
|
+
| Can `stop` force one turn? | hook returns `{"followup_message":...}` | Yes. A genuine second turn ran and answered. |
|
|
256
|
+
| Can `stop` park? | hook sleeps, then returns a follow-up | Yes. It is awaited; a 20s sleep held the boundary and the follow-up landed after it. |
|
|
257
|
+
| What is `loop_count`? | four consecutive follow-ups, then a real user message | `0,1,2,3`, then `0` again. It counts follow-up-driven stops since the last real user message. |
|
|
258
|
+
| Does `loop_limit` bind? | `loop_limit: 2` with an always-follow-up hook | Yes. The hook was invoked at `loop_count` 0 and 1 and never at 2. |
|
|
259
|
+
| Does a captain message terminate an existing park? | captain message typed during a 600s park | No. Cursor leaves the park running, and without a baton an older park can still deliver after the captain turn's next `stop` has started another park. |
|
|
260
|
+
| Does Cursor load `.claude/settings.json`? | Claude-shaped `SessionStart`, `PreToolUse`, `Stop` in the same workspace | `SessionStart` and `PreToolUse` fired with a CURSOR-shaped payload carrying `cursor_version`; `Stop` did not fire. |
|
|
261
|
+
|
|
262
|
+
The integration itself is exercised by the opt-in guard:
|
|
263
|
+
|
|
264
|
+
```sh
|
|
265
|
+
FM_CURSOR_PRIMARY_LIVE_E2E=1 tests/fm-cursor-primary-live-e2e.test.sh
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Observed output:
|
|
269
|
+
|
|
270
|
+
```text
|
|
271
|
+
harness: cursor-agent 2026.08.11-e8db854
|
|
272
|
+
ok - cursor primary: the sessionStart hook takes the fleet lock as the Cursor process itself
|
|
273
|
+
ok - cursor primary: the run-tier session start completes every stage
|
|
274
|
+
ok - cursor primary: sessionStart additional_context reaches model context before the first turn
|
|
275
|
+
ok - cursor primary: the stop-hook park delivers a real watcher wake as one follow-up
|
|
276
|
+
ok - cursor primary: the park owns exactly one arm cycle with a live watcher beacon
|
|
277
|
+
ok - cursor primary: the captain keeps control and the older park stands down after the next stop claim
|
|
278
|
+
ok - cursor primary: an away-mode escalation is delivered, confirmed, and processed
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The live run proved that session start acquires the fleet lock through Cursor's structural process identity in `bin/fm-cursor-lib.sh`; `tests/fm-session-lock-ancestry.test.sh` pins the same ancestry path portably.
|
|
282
|
+
It also proved that Cursor's `autoarm` supervision model lets the mid-turn pull guard accept a fresh beacon after the between-turn watcher closes; `tests/fm-guard-stale-banner.test.sh` pins that model-aware verdict.
|
|
283
|
+
The baton is claimed only by the next `stop`, so an actionable close before that claim can still produce one real follow-up from the sole existing park; durable wake handling is idempotent, and any older park still running after the claim stands down.
|
|
284
|
+
Cursor's `beforeSubmitPrompt` step could close that exact window because it fires once on a real captain message and not on hook-driven follow-ups, but registering it is deliberately deferred alongside `preCompact`.
|
|
285
|
+
|
|
286
|
+
Away-mode delivery needed no daemon change once the composer reader was correct for Cursor; [`runtime-backends.md`](runtime-backends.md#composer) owns that evidence.
|
|
287
|
+
|
|
288
|
+
Cursor compaction instruction refresh is DEFERRED and not shipped, so a Cursor primary does not re-emit its digest after a compaction.
|
|
289
|
+
Two static facts decided that: `PreCompactRequestResponse` carries only `user_message`, and `preCompact` is absent from the `additional_context` step set (`index.js` @ 4814884), so the step cannot inject a digest and any delivery has to be routed through a later boundary.
|
|
290
|
+
A staged-then-delivered design is rejected because carrying a digest across two concurrently running `stop` hooks can deliver it twice or strand it indefinitely, while closing those races enlarges a critical section inside a hook Cursor awaits at the turn boundary.
|
|
291
|
+
Native `preCompact` firing was not observed because a real compaction could not be forced in the isolated session, so the surface has no empirical basis yet.
|
|
292
|
+
It is therefore recorded as uncovered in the same sense as the Codex interactive TUI, and `tests/fm-cursor-primary.test.sh` asserts `preCompact` stays unregistered so it cannot return unnoticed without its own design and evidence.
|
|
293
|
+
|
|
294
|
+
The Grok adaptive matrix ran on 2026-07-28 with separate scratch repositories and homes, dedicated tmux sockets, one target plus one control window, ambient tmux variables removed, and a socket-bound wrapper first in `PATH`.
|
|
295
|
+
|
|
296
|
+
```sh
|
|
297
|
+
FM_GROK_STOP_LIVE_E2E=1 \
|
|
298
|
+
FM_GROK_NATIVE_BIN="$native_grok_0_2_112" \
|
|
299
|
+
FM_GROK_LEGACY_BIN="$official_pre_native_grok_0_2_73" \
|
|
300
|
+
tests/fm-grok-stop-live-e2e.test.sh
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Observed bounded output:
|
|
304
|
+
|
|
305
|
+
```text
|
|
306
|
+
ok - grok 0.2.112 (9bbd559437aa) [stable] native Stop kept one session across false->true, two model turns, and zero resume processes
|
|
307
|
+
ok - grok 0.2.73 (9ff14c43bbe5) [stable] legacy Stop omitted capability, resumed exactly once, and stopped normally
|
|
308
|
+
ok - Grok adaptive Stop real-process matrix passed with exact target cleanup and control-window survival
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The same run proved the Claude-compatible Stop entries stay inert under `GROK_AGENT`, the legacy resume carries `GROK_TURNEND_GUARD_ACTIVE=1`, and every replacement root is removed after exact target cleanup while its control window survives.
|
|
312
|
+
That inertness result is scoped to the builds it exercised: it did not establish that `GROK_AGENT` reaches a Grok HOOK process, and on grok 1.0.0 it does not, so the marker set was widened to `GROK_HOOK_EVENT` as well (docs/turnend-guard.md "Harness integrations").
|
|
313
|
+
`tests/fm-turnend-guard.test.sh` now pins every tracked `.claude/settings.json` hook entry against a real grok 1.0.0 hook environment so the inertness contract is covered deterministically rather than only by the opt-in live matrix.
|
|
314
|
+
|
|
315
|
+
The secondmate-home scope and manual-repair wake path were measured with Claude Code 2.1.207 on 2026-07-12, when a native background completion re-invoked the idle model with no human input.
|
|
316
|
+
The current Stop-owned main/secondmate inclusion and child-worktree exclusion are covered deterministically by `tests/fm-claude-stop-autoarm.test.sh`.
|
|
317
|
+
Session-lock ownership in `bin/fm-session-lock-lib.sh` is decided against a session's whole contiguous harness ancestry rather than one chosen pid, so the Stop auto-arm reaches its lock owner wherever that owner sits: the outermost pid of Claude Code's multi-level `bg-spare` hook worker chain, or an inner pid when a harness-named daemon parents the session.
|
|
318
|
+
Harness identity is read from the executable path and `argv[0]` as well as the command basename, because Claude Code's native installer names the per-session executable by its version (`.../share/claude/versions/2.1.220`): `ps -o comm=` reports that path on macOS and the bare version string on Linux, and neither basename names a harness.
|
|
319
|
+
`tests/fm-session-lock-ancestry.test.sh` pins both platforms' reporting semantics behind a deterministic process table and runs the real Stop auto-arm in version-named, daemon-parented, and combined real process trees.
|
|
320
|
+
`tests/fm-watch-arm.test.sh` runs real watcher and arm cycles against durable on-disk state to verify that a delivered reason survives until post-handling acknowledgement and stops replaying after acknowledgement, while an unrelated queue append cannot make a watcher cycle that delivered nothing look successful.
|
|
321
|
+
The same suite ingests a keyed remote-secondmate parent reply through the real adapter, establishes the incremental OPEN DECISIONS cursor, interrupts supervision, and proves re-arm replays every unacknowledged queue row plus the still-open decision through the ordinary drain path.
|
|
322
|
+
It also covers decision-only recovery, interrupted handling, handling-window generation reuse, non-fatal moved-generation acknowledgement with sequence-bounded consumption, and a persistent successor remaining live after recovery is acknowledged.
|
|
323
|
+
|
|
324
|
+
The Claude product live path ran with Claude Code 2.1.219 on 2026-07-24:
|
|
325
|
+
|
|
326
|
+
```sh
|
|
327
|
+
claude --version
|
|
328
|
+
FM_CLAUDE_LIVE_E2E=1 tests/fm-claude-stop-autoarm-live-e2e.test.sh
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Observed output:
|
|
332
|
+
|
|
333
|
+
```text
|
|
334
|
+
2.1.219 (Claude Code)
|
|
335
|
+
ok - Claude 2.1.219 (Claude Code) live E2E reclaimed a stale session lock through session start, completed two tokenless Stop-owned rewake cycles, and preserved the competing-live-owner boundary
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Current entry points:
|
|
339
|
+
|
|
340
|
+
```sh
|
|
341
|
+
tests/fm-turnend-guard.test.sh
|
|
342
|
+
tests/fm-supervision-instructions.test.sh
|
|
343
|
+
FM_PI_LIVE_E2E=1 tests/fm-pi-primary-live-e2e.test.sh
|
|
344
|
+
FM_GROK_STOP_LIVE_E2E=1 FM_GROK_NATIVE_BIN="$native_grok" FM_GROK_LEGACY_BIN="$pre_native_grok" tests/fm-grok-stop-live-e2e.test.sh
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The Claude auto-arm false-failure, guard-predicate, and monotonic bounded fail-open correction was verified on 2026-08-02 with the installed ShellCheck 0.11.0 and isolated behavior suites.
|
|
348
|
+
|
|
349
|
+
```sh
|
|
350
|
+
bin/fm-lint.sh
|
|
351
|
+
bin/fm-doc-audience-check.sh
|
|
352
|
+
bin/fm-test-run.sh tests/fm-claude-stop-autoarm.test.sh tests/fm-guard-stale-banner.test.sh tests/fm-turnend-guard.test.sh tests/fm-supervision-instructions.test.sh
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Observed output:
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
fm-lint.sh: ShellCheck 0.11.0 (pinned 0.11.0)
|
|
359
|
+
fm-doc-audience-check: ok surfaces=61 local_links=174
|
|
360
|
+
FM_TEST_SUMMARY total=4 failed=0 skipped_gate=0 duration_ms=102585
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
The model-aware pull-guard predicate correction (`bin/fm-guard.sh` no longer reports a false watcher-down mid-turn under the Claude Stop auto-arm model, where the watcher runs only between turns) was verified on 2026-08-04 with the installed ShellCheck 0.11.0 and the same isolated behavior suites.
|
|
364
|
+
|
|
365
|
+
```sh
|
|
366
|
+
bin/fm-lint.sh
|
|
367
|
+
bin/fm-doc-audience-check.sh
|
|
368
|
+
bin/fm-test-run.sh tests/fm-claude-stop-autoarm.test.sh tests/fm-guard-stale-banner.test.sh tests/fm-turnend-guard.test.sh tests/fm-supervision-instructions.test.sh
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Observed output:
|
|
372
|
+
|
|
373
|
+
```text
|
|
374
|
+
fm-lint.sh: ShellCheck 0.11.0 (pinned 0.11.0)
|
|
375
|
+
fm-doc-audience-check: ok surfaces=64 local_links=188
|
|
376
|
+
FM_TEST_SUMMARY total=4 failed=0 skipped_gate=0 duration_ms=80078
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The Pi extension-model pull-guard correction (`bin/fm-guard.sh` no longer reports a false watcher-down on a Pi primary during the extension's own watcher hand-off) was verified on 2026-08-13 with the installed ShellCheck 0.11.0 and isolated behavior suites.
|
|
380
|
+
The guard verdict itself reads only state files and process liveness, so the portable suites are the enforcing evidence; `bin/fm-harness.sh`'s Pi marker detection, which selects the model, is exercised in the same suite through `PI_CODING_AGENT`.
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
bin/fm-lint.sh
|
|
384
|
+
bin/fm-doc-audience-check.sh
|
|
385
|
+
bin/fm-test-run.sh tests/fm-guard-stale-banner.test.sh tests/fm-turnend-guard.test.sh tests/fm-session-start.test.sh tests/fm-pi-watch-extension.test.sh tests/fm-watch-arm.test.sh
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Observed output:
|
|
389
|
+
|
|
390
|
+
```text
|
|
391
|
+
fm-lint.sh: ShellCheck 0.11.0 (pinned 0.11.0)
|
|
392
|
+
fm-doc-audience-check: ok surfaces=67 local_links=243
|
|
393
|
+
FM_TEST_SUMMARY total=5 failed=0 skipped_gate=0 duration_ms=280160
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
The same correction was verified against a live Pi primary's own supervision evidence on 2026-08-13.
|
|
397
|
+
The hand-off was captured live at beacon age 63s, then the home's `state/.lock`, `state/.last-watcher-beat`, both `state/.pi-*-extension-loaded` markers, and both `.pi/extensions/*.ts` builds were copied into an isolated fixture with no watcher lock.
|
|
398
|
+
The fixture's copied beacon was fresh at 0s in the output below; the deterministic stale-beacon case separately verifies the grace boundary.
|
|
399
|
+
|
|
400
|
+
```sh
|
|
401
|
+
FM_SUPERVISION_MODEL=persistent FM_GUARD_READ_ONLY=1 bin/fm-guard.sh
|
|
402
|
+
FM_SUPERVISION_MODEL=extension FM_GUARD_READ_ONLY=1 bin/fm-guard.sh
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Observed output, before and after the model correction, then with the recorded Pi session pid replaced by a dead one:
|
|
406
|
+
|
|
407
|
+
```text
|
|
408
|
+
● WATCHER DOWN - SUPERVISION IS OFF
|
|
409
|
+
● 1 task(s) in flight, but no live watcher process holds this home lock (last beat: 0s ago).
|
|
410
|
+
(silent)
|
|
411
|
+
● WATCHER DOWN - SUPERVISION IS OFF
|
|
412
|
+
● 1 task(s) in flight, but no live watcher process holds this home lock (last beat: 0s ago).
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The broader relevant regression pass was rerun on 2026-08-02 without live-home or daemon mutation.
|
|
416
|
+
|
|
417
|
+
```sh
|
|
418
|
+
bin/fm-test-run.sh tests/fm-watch-triage.test.sh tests/fm-watcher-lock.test.sh tests/fm-afk-inject-e2e.test.sh tests/fm-afk-return.test.sh tests/fm-x-mode.test.sh tests/fm-backend.test.sh tests/fm-backend-tmux-smoke.test.sh tests/fm-secondmate-safety.test.sh
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Observed output:
|
|
422
|
+
|
|
423
|
+
```text
|
|
424
|
+
FM_TEST_SUMMARY total=8 failed=0 skipped_gate=0 duration_ms=617507
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The actionable-close ordering correction was reverified on 2026-08-02 against an identity-matched live successor.
|
|
428
|
+
|
|
429
|
+
```sh
|
|
430
|
+
tests/fm-claude-stop-autoarm.test.sh >/dev/null && echo "fm-claude-stop-autoarm: ok"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Observed output:
|
|
434
|
+
|
|
435
|
+
```text
|
|
436
|
+
fm-claude-stop-autoarm: ok
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Watcher continuity
|
|
440
|
+
|
|
441
|
+
The cross-harness evidence combines the 2026-07-17 live pass with Claude's replacement Stop-owned path revalidated on 2026-07-24, all against isolated project and home state.
|
|
442
|
+
No credential material was copied into a fixture.
|
|
443
|
+
|
|
444
|
+
```text
|
|
445
|
+
Claude Code 2.1.219
|
|
446
|
+
codex-cli 0.144.4
|
|
447
|
+
OpenCode 1.17.18
|
|
448
|
+
Pi 0.80.10
|
|
449
|
+
grok 0.2.103 (89c3d36fb6f1) [stable]
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
| Harness | Exact opt-in command | Observed guarantee |
|
|
453
|
+
| --- | --- | --- |
|
|
454
|
+
| Claude | `FM_CLAUDE_LIVE_E2E=1 tests/fm-claude-stop-autoarm-live-e2e.test.sh` | Session start reclaimed a stale owner before two Stop-owned cycles, and a competing live owner prevented arm, rewake, epoch write, or lock replacement. |
|
|
455
|
+
| Codex | `FM_CODEX_LIVE_E2E=1 tests/fm-codex-continuity-live-e2e.test.sh` | The one-second foreground checkpoint returned without switching to the arm wrapper. |
|
|
456
|
+
| OpenCode | `FM_OPENCODE_LIVE_E2E=1 tests/fm-opencode-primary-live-e2e.test.sh` | A verified successor existed before prompt handling, with no model re-arm or turn-end fallback. |
|
|
457
|
+
| Pi | `FM_PI_LIVE_E2E=1 tests/fm-pi-primary-live-e2e.test.sh` | One initial tool call led to extension-owned successors and clean child retirement on exit. |
|
|
458
|
+
| Grok | `FM_GROK_LIVE_E2E=1 tests/fm-grok-continuity-live-e2e.test.sh` | Native task completion surfaced the actionable close and the cycle ledger recorded `reason=actionable-signal`. |
|
|
459
|
+
|
|
460
|
+
Pi 0.81.1 repeated the continuity and clean-exit lifecycle on 2026-07-23 after the Calm presentation changes.
|
|
461
|
+
|
|
462
|
+
Pi same-process session-transition ownership was verified on 2026-07-27 against the tracked extension with a faithful in-process factory rebind (module cache retained, real arm children):
|
|
463
|
+
|
|
464
|
+
```sh
|
|
465
|
+
pi --version
|
|
466
|
+
tests/fm-pi-watch-extension.test.sh
|
|
467
|
+
tests/fm-pi-primary-types.test.sh
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Observed guarantee: after ordinary `session_shutdown` for `/new`, `/resume`, and `/fork`, plus same-instance shutdown-plus-start, the replacement generation armed again without a Pi restart and without the `watcher: not armed - Pi session is shutting down` refusal.
|
|
471
|
+
Stale prior-generation tool callbacks could not mutate the active child, repeated transitions kept exactly one live arm cycle, and terminal `quit` still refused late rearm.
|
|
472
|
+
Plain Pi and pi-signed share the same tracked `.pi/extensions/fm-primary-pi-watch.ts` path, so both inherit the generation owner; other primary harnesses are not applicable because they do not use this Pi extension lifecycle.
|
|
473
|
+
|
|
474
|
+
The once-per-generation recovery bound and immediate handling-successor poll were verified on 2026-08-21 with the tracked Pi extension, real watcher processes, and an isolated home.
|
|
475
|
+
The regression forced handling confirmation to fail, observed one recovery follow-up across the former repeat window, confirmed the successor remained live, and then proved a separate handling successor durably queued a crew event within the bounded poll window.
|
|
476
|
+
|
|
477
|
+
```sh
|
|
478
|
+
bin/fm-test-run.sh tests/fm-watch-recovery-loop.test.sh
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Observed output:
|
|
482
|
+
|
|
483
|
+
```text
|
|
484
|
+
ok - a resurfacing handling successor stays alive and supervises instead of going blind
|
|
485
|
+
ok - unacknowledged recovery is announced at most once per generation and the successor stays alive
|
|
486
|
+
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=59357
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Deterministic entry points:
|
|
490
|
+
|
|
491
|
+
```sh
|
|
492
|
+
tests/fm-pi-watch-extension.test.sh
|
|
493
|
+
tests/fm-pi-primary-types.test.sh
|
|
494
|
+
tests/fm-watcher-lock.test.sh
|
|
495
|
+
tests/fm-watch-arm.test.sh
|
|
496
|
+
tests/fm-watch-recovery-loop.test.sh
|
|
497
|
+
tests/fm-wake-queue.test.sh
|
|
498
|
+
tests/fm-subagent-pretool-check.test.sh
|
|
499
|
+
tests/fm-claude-stop-autoarm.test.sh
|
|
500
|
+
tests/fm-turnend-guard.test.sh
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Wedge-alarm channels
|
|
504
|
+
|
|
505
|
+
The two real notification channels were bounded manually on 2026-07-10 on macOS 26.5.2 with Herdr 0.7.3.
|
|
506
|
+
Automated suites never execute these real notification commands.
|
|
507
|
+
|
|
508
|
+
Argv-safe Notification Center command:
|
|
509
|
+
|
|
510
|
+
```sh
|
|
511
|
+
/usr/bin/osascript \
|
|
512
|
+
-e 'on run argv' \
|
|
513
|
+
-e 'display notification (item 1 of argv) with title "FIRSTMATE TEST - IGNORE" sound name "Basso"' \
|
|
514
|
+
-e 'end run' \
|
|
515
|
+
'FIRSTMATE TEST - IGNORE (wedge-alarm channel verification)'
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Observed output: no stdout, exit 0, and one banner with the supplied body.
|
|
519
|
+
|
|
520
|
+
Herdr command:
|
|
521
|
+
|
|
522
|
+
```sh
|
|
523
|
+
herdr notification show 'FIRSTMATE TEST - IGNORE' \
|
|
524
|
+
--body 'FIRSTMATE TEST - IGNORE (wedge-alarm channel verification)' \
|
|
525
|
+
--sound request
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Observed output:
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
{"id":"cli:notification:show","result":{"reason":"shown","shown":true,"type":"notification_show"}}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
The safe command-channel contract is covered without a notification by `tests/fm-daemon.test.sh`: the summary reaches both `$1` and stdin, every channel is process-group bounded, and a failed channel falls through.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Trace-context propagation verification
|
|
2
|
+
|
|
3
|
+
Repeatable evidence for the default-off native W3C trace-context capability.
|
|
4
|
+
Current behavior and rationale are owned by [`../trace-context.md`](../trace-context.md) and the configuration schema by [`../configuration.md`](../configuration.md) ("Trace context propagation"); this page records evidence only.
|
|
5
|
+
|
|
6
|
+
Date: 2026-08-03.
|
|
7
|
+
Shell: GNU bash 3.2.57 (macOS).
|
|
8
|
+
Comparison base: `main` at `976d97f`.
|
|
9
|
+
|
|
10
|
+
The colocated unit suite `tests/fm-trace-context-lib.test.sh` (26 assertions) exercises validation (valid accepted; malformed, wrong-length, uppercase, all-zero, `ff` version, and shell-metacharacter values rejected), root minting with every mint a distinct sampled root and no parent-adoption input, the recovery reuse path with the recorded carrier winning over the ambient environment, default-off omission, the enable precedence of `FM_TRACE_CONTEXT` over `config/trace-context` with unset or empty deferring to the file, normalized home-session state, atomic replacement of a read-only prior record, stale-session rejection after failed publication, missing or invalid state defaulting off, the Secondmate home-session boundary with later file state plus the per-task trace boundary (two resolves under one persistent ambient `TRACEPARENT` root two distinct traces and adopt neither), forced entropy failure omitting safely, and the minted-root fixed-shape check.
|
|
11
|
+
|
|
12
|
+
The spawn-path integration suite `tests/fm-trace-context-spawn.test.sh` (12 assertions), hermetic against an ambient `FM_TRACE_CONTEXT`, drives `bin/fm-spawn.sh` end to end with a fake tmux pane and a real isolated git worktree: enabled, one resolved carrier is recorded as `traceparent=` in the meta only after the identical `TRACEPARENT` export is sent before the launch literal; disabled, neither is written nor sent (only `GOTMPDIR` is); a failed carrier delivery leaves no `traceparent=` claim while the source task still launches; an unsafe delivery whose partial input cannot be cleared stops before appending the launch command; a failed metadata append removes the carrier from the launched task without aborting it; duplicate Secondmate preflight leaves inherited trace configuration unchanged; a relaunch reuses the recorded carrier verbatim; and spawns ignore later config and environment edits in favor of the frozen home-session decision.
|
|
13
|
+
The per-task boundary regression models the reviewed Secondmate scenario exactly: two unrelated tasks spawned sequentially from one home while the same fixed `TRACEPARENT` sits in the spawning environment (a persistent Secondmate's launch-time carrier) record and inject valid carriers whose trace ids differ from each other and from the ambient carrier, and a relaunch of the first task reuses its original carrier verbatim for both the meta record and the injected export.
|
|
14
|
+
Two further assertions drive a genuine two-level primary -> Secondmate -> worker chain, running `bin/fm-spawn.sh` twice with the exact environment the primary injects into the Secondmate, and prove the primary's effective override governs the nested worker both ways: env-on with no config file keeps the nested worker enabled while it roots its own per-task trace distinct from the Secondmate's carrier, and env-off with the file present keeps the nested worker disabled even though the `config/trace-context` file was copied into the Secondmate home.
|
|
15
|
+
A final assertion drives the file-decided path (`FM_TRACE_CONTEXT` unset) and proves the Secondmate's recorded/injected carrier and its delivered `FM_TRACE_CONTEXT=on|off` snapshot are always derived from one frozen decision, so a carrier is never paired with the opposite enable state.
|
|
16
|
+
The suite touches no real harness or live fleet.
|
|
17
|
+
`tests/fm-session-start.test.sh` additionally proves only a lock-owning session start writes the effective state and a lock-refused read-only start leaves it unchanged.
|
|
18
|
+
|
|
19
|
+
The remote-route suite `tests/fm-remote-secondmate-trace-context.test.sh` (6 assertions) covers the Secondmate path that never reaches the local export site, driving the real chain - the parent's `bin/fm-spawn.sh`, `bin/fm-on.sh`, the real remote entrypoint, `bin/fm-remote-secondmate-control.sh`, and the remote host's own `bin/fm-spawn.sh` - over the deterministic SSH boundary with a stateful fake Herdr CLI, the backend a remote second mate always runs on, so the carrier the remote pane receives is read back from that pane's own log: disabled, the parent records no `traceparent=`, the remote pane receives no export, the remote home inherits no enablement flag, and the delivered snapshot is `FM_TRACE_CONTEXT=off` while `GOTMPDIR` still ships; enabled, the parent's recorded carrier, the remote endpoint's own record, and the exported pane value are one identical valid carrier sent after `GOTMPDIR` and before the launch command, with `FM_TRACE_CONTEXT=on` and the inherited flag delivered; a relaunch keeps that carrier verbatim in both the parent record and the pane export; a second remote route resolved from an environment holding a fixed ambient `TRACEPARENT` roots a trace id distinct from both that ambient carrier and the first route; the remote receiver accepts `config/trace-context` as ordinary declared inherited material while refusing `config/secondmate-harness`, which the primary deliberately does not propagate; and the delivery argument that carries a parent's carrier to a remote host is refused on a ship spawn, on a shell-metacharacter value, on an all-zero trace id, and on an empty value, so nothing but a strict W3C carrier on a Secondmate launch can reach a pane export.
|
|
20
|
+
|
|
21
|
+
```console
|
|
22
|
+
$ bash tests/fm-trace-context-lib.test.sh | tail -1
|
|
23
|
+
# fm-trace-context-lib.test.sh: all assertions passed
|
|
24
|
+
$ bash tests/fm-trace-context-spawn.test.sh | tail -1
|
|
25
|
+
# all fm-trace-context-spawn tests passed
|
|
26
|
+
$ bash tests/fm-remote-secondmate-trace-context.test.sh | tail -1
|
|
27
|
+
ALL TESTS PASSED
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run all three trace-context suites from the repo root; each prints one `ok - ...` per assertion.
|
|
31
|
+
A single live-backend end-to-end check - a real spawn confirming the pane received the `TRACEPARENT` export before the launch line, with nothing left after teardown - is a bounded manual step, deferred here because a live agent spawn disrupts a running fleet.
|