The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\m9-wave1-progress.md has been updated successfully. (file state is current in your context — no need to Read it back)

---
name: m9-wave1-progress
description: M9 status — three of four gates closed (G1/G2/Gateway-WAN); only Wave 3/G3 remains
metadata:
  node_type: memory
  type: project
  originSessionId: cac905c4-0186-45cc-8462-8b34fe78904b
---

M9 "Adapter customization & session surfaces". Executor todlando, reviewer doyle. CONTEXT = NORMATIVE (deviation = STOP-and-ping, never code-around). Stacking branches; **merge-to-main ONLY for a public release** (operator policy 2026-06-13) — PRs are CI/gate vehicles. No tag rides M9.

**THREE GATES CLOSED (2026-06-12 → 06-13):**
- **G1 — Wave 1 `m9-adapter-custom` (PR #7):** adapter profiles + strings + hints. Doc-pin b189513 (skip-seen hint tiebreak ratified). CI green both runners. doyle re-ran + reviewed survey/engine/seams.
- **G2 — Wave 2 `m9-user-surfaces` (PR #8):** user-msg identity gate + Gateway acceptance + live-role.md. Blocker (reconcile must STRUCTURALLY exclude live-role.md, not by-rarity) fixed 77e0fb1 + verified (RoleExcluded before reconcile_file; artifact preserved; failer-runtime test). Win RED was hfenduleam disk-full not code — see [[hfenduleam-target-bloat-ci-red]]. CI green.
- **Gateway-WAN — `m9-gateway-wan` (PR #9, REQ-MSG-6):** cross-node Gateway user-msg. `Instance.endpoint_type` advert (additive/N-1) + `receive_wan` resolves via instances_of(from) filtered to QUIC-proven origin_node, is_gateway only. **Trust posture (operator-ratified 2026-06-13): subnet membership = trust boundary; no anti-forge defense; user-surface-set VETOED.** Matrix + wanmsg + xtask green. doyle authored the doc/spec (CONTEXT §Gateway + ROADMAP + JIT plan, commit 30fea7f) → directed todlando → gated PASS.

**Tracked condition — LOGGED (58f034f on PR #9):** T5 twohost HONORED-path int marked REQUIRED-BEFORE-LECTURN on REQ-MSG-6 + ROADMAP ("activate int when adding it"). Negative/security path already E2E (wanmsg funnel); positive honored path has no exerciser until a real cross-node Gateway. Don't re-flag — it's logged.

**LAST M9 PIECE = Wave 3 / G3 — LAUNCHED 2026-06-13 (branch `m9-digest-logs` off main, lands as one PR):** digest log-projection engine + `api digest-entry` → RETIRE the PTY-parse half + REQ-TERM-4 retitle/re-point in the SAME commit (never momentarily un-evidenced). Plan = `M9-WAVE3-JIT.md` (doyle GREENLIT, §2 RULED). Watch: history-grain fallback pre-authorized but MUST be flagged at G3 if taken. Zero PTY-parse remnants; CC-fixture digest E2E; docs sweep. Don't re-plan G1/G2/Gateway-WAN — closed.

**§2 RULING (doyle, the one design decision — Approach A):** digest projects a SMALL spt-core-PUBLISHED record contract (`role`∈{input,agent,tool} / `text` / optional `tool{name,arg}`; consecutive `role:"tool"` collapsed; malformed→skip). NOT "adapters already emit it" — it's a published surface adapters target. CRITICAL: native history stays adapter-OPAQUE (echo/resume carry `raw` verbatim, REQ-SEAM-HISTORY) → digest contract is an OVERLAY, NOT the universal native shape. ONE schema, no `r`→`role` mapping layer; the stray native test fixture (history.rs:249 `{"r":"u"}`) MIGRATES to the digest shape. Contract published in CONTEXT.md 334-343 + MANIFEST (REPLACES `[pty_digest]`, not just deletes). §3 retire is SURGICAL: keep Digest/Turn/DigestEntry/ToolUse + delta machinery + digesthub; kill only parser/PtyDigest/byte-feed. Carry-forwards: Path-A-coarse→B-first fallback flagged-not-silent; E2E filename flagged at G3.

**owl send gotcha:** owl `send` HANGS if body is passed as a positional FROM arg — pipe body via stdin heredoc (`$OWL send <target> <<'EOF'…EOF`). todlando hit this; messages stalled silently.

-description: M9 status — three of four gates closed (G1/G2/Gateway-WAN); only Wave 3/G3 remains

+description: M9 COMPLETE — all four gates closed (G1/G2/Gateway-WAN/G3). PR #10 (Wave 3) held, merge=operator-only


Edit

**M9 COMPLETE — ALL FOUR GATES CLOSED (2026-06-12 → 06-13).** G4=G3 stamped PASS by doyle 2026-06-13 (PR #10, branch `m9-digest-logs`, 5 commits + title-fix 1fc6236). doyle independently verified: CC E2E `harness_hosted_digest_projects_and_pushes_deltas` 1-pass, workspace 0-fail (56 bins), clippy -D, --no-default-features, xtask OK, traceable EXIT=0, PTY-parse grep-clean. **DO NOT MERGE PR #10** — merge-to-main = operator/release-gated only; no tag rides M9; PRs are gate vehicles. Next move = operator's.**

**FOUR GATES CLOSED (2026-06-12 → 06-13):**


**M9 COMPLETE — ALL FOUR GATES CLOSED (2026-06-12 → 06-13).** G4=G3 stamped PASS by doyle 2026-06-13 (PR #10, branch `m9-digest-logs`, 5 commits + title-fix 1fc6236). doyle independently verified: CC E2E `harness_hosted_digest_projects_and_pushes_deltas` 1-pass, workspace 0-fail (56 bins), clippy -D, --no-default-features, xtask OK, traceable EXIT=0, PTY-parse grep-clean. **DO NOT MERGE PR #10** — merge-to-main = operator/release-gated only; no tag rides M9; PRs are gate vehicles. Next move = operator's.**

**FOUR GATES CLOSED (2026-06-12 → 06-13):**

---
name: m9-wave1-progress
description: M9 COMPLETE — all four gates closed (G1/G2/Gateway-WAN/G3). PR #10 (Wave 3) held, merge=operator-only
metadata:
  node_type: memory
  type: project
  originSessionId: cac905c4-0186-45cc-8462-8b34fe78904b
---

M9 "Adapter customization & session surfaces". Executor todlando, reviewer doyle. CONTEXT = NORMATIVE (deviation = STOP-and-ping, never code-around). Stacking branches; **merge-to-main ONLY for a public release** (operator policy 2026-06-13) — PRs are CI/gate vehicles. No tag rides M9.

**THREE GATES CLOSED (2026-06-12 → 06-13):**
- **G1 — Wave 1 `m9-adapter-custom` (PR #7):** adapter profiles + strings + hints. Doc-pin b189513 (skip-seen hint tiebreak ratified). CI green both runners. doyle re-ran + reviewed survey/engine/seams.
- **G2 — Wave 2 `m9-user-surfaces` (PR #8):** user-msg identity gate + Gateway acceptance + live-role.md. Blocker (reconcile must STRUCTURALLY exclude live-role.md, not by-rarity) fixed 77e0fb1 + verified (RoleExcluded before reconcile_file; artifact preserved; failer-runtime test). Win RED was hfenduleam disk-full not code — see [[hfenduleam-target-bloat-ci-red]]. CI green.
- **Gateway-WAN — `m9-gateway-wan` (PR #9, REQ-MSG-6):** cross-node Gateway user-msg. `Instance.endpoint_type` advert (additive/N-1) + `receive_wan` resolves via instances_of(from) filtered to QUIC-proven origin_node, is_gateway only. **Trust posture (operator-ratified 2026-06-13): subnet membership = trust boundary; no anti-forge defense; user-surface-set VETOED.** Matrix + wanmsg + xtask green. doyle authored the doc/spec (CONTEXT §Gateway + ROADMAP + JIT plan, commit 30fea7f) → directed todlando → gated PASS.

**Tracked condition — LOGGED (58f034f on PR #9):** T5 twohost HONORED-path int marked REQUIRED-BEFORE-LECTURN on REQ-MSG-6 + ROADMAP ("activate int when adding it"). Negative/security path already E2E (wanmsg funnel); positive honored path has no exerciser until a real cross-node Gateway. Don't re-flag — it's logged.

**LAST M9 PIECE = Wave 3 / G3 — LAUNCHED 2026-06-13 (branch `m9-digest-logs` off main, lands as one PR):** digest log-projection engine + `api digest-entry` → RETIRE the PTY-parse half + REQ-TERM-4 retitle/re-point in the SAME commit (never momentarily un-evidenced). Plan = `M9-WAVE3-JIT.md` (doyle GREENLIT, §2 RULED). Watch: history-grain fallback pre-authorized but MUST be flagged at G3 if taken. Zero PTY-parse remnants; CC-fixture digest E2E; docs sweep. Don't re-plan G1/G2/Gateway-WAN — closed.

**§2 RULING (doyle, the one design decision — Approach A):** digest projects a SMALL spt-core-PUBLISHED record contract (`role`∈{input,agent,tool} / `text` / optional `tool{name,arg}`; consecutive `role:"tool"` collapsed; malformed→skip). NOT "adapters already emit it" — it's a published surface adapters target. CRITICAL: native history stays adapter-OPAQUE (echo/resume carry `raw` verbatim, REQ-SEAM-HISTORY) → digest contract is an OVERLAY, NOT the universal native shape. ONE schema, no `r`→`role` mapping layer; the stray native test fixture (history.rs:249 `{"r":"u"}`) MIGRATES to the digest shape. Contract published in CONTEXT.md 334-343 + MANIFEST (REPLACES `[pty_digest]`, not just deletes). §3 retire is SURGICAL: keep Digest/Turn/DigestEntry/ToolUse + delta machinery + digesthub; kill only parser/PtyDigest/byte-feed. Carry-forwards: Path-A-coarse→B-first fallback flagged-not-silent; E2E filename flagged at G3.

**owl send gotcha:** owl `send` HANGS if body is passed as a positional FROM arg — pipe body via stdin heredoc (`$OWL send <target> <<'EOF'…EOF`). todlando hit this; messages stalled silently.

+**M9 COMPLETE — ALL FOUR GATES CLOSED (2026-06-12 → 06-13).** G4=G3 stamped PASS by doyle 2026-06-13 (PR #10, branch `m9-digest-logs`, 5 commits + title-fix 1fc6236). doyle independently verified: CC E2E `harness_hosted_digest_projects_and_pushes_deltas` 1-pass, workspace 0-fail (56 bins), clippy -D, --no-default-features, xtask OK, traceable EXIT=0, PTY-parse grep-clean. **DO NOT MERGE PR #10** — merge-to-main = operator/release-gated only; no tag rides M9; PRs are gate vehicles. Next move = operator's.**


Update MEMORY.md index line to match.


- [M9 status](m9-wave1-progress.md) — THREE gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Only Wave3/G3 left (digest log-projection + REQ-TERM-4 re-point). Merge-to-main = release-only (operator). Don't re-plan closed waves

- [M9 status](m9-wave1-progress.md) — M9 COMPLETE, ALL 4 gates closed: G1(PR#7) · G2(PR#8) · Gateway-WAN(PR#9,REQ-MSG-6) · G3(PR#10, Wave3 digest log-projection + REQ-TERM-4 re-point, doyle-stamped 2026-06-13). PRs HELD — merge-to-main=operator/release-only, no tag rides M9. Don't re-plan, don't merge


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


1	- [M9 status](m9-wave1-progress.md) — gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Merge-to-main = release-only (operator). Don't re-plan closed waves
2	- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); IMPLEMENTED, PR#10 open, awaiting doyle G3 gate. On-demand projection (topology-independent, no PTY); PTY-parse engine retired; published digest-record contract; autonomous-freshness+Option-C deferred. After G3 → M9 close
3	- [hfenduleam target bloat → CI RED](hfenduleam-target-bloat-ci-red.md) — Win-runner LNK1318/os-error-112 can be disk-full from cargo target bloat (396GB seen), NOT a code regression; cargo clean + re-run; check disk before treating a Windows RED as a code defect

- [M9 status](m9-wave1-progress.md) — gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Merge-to-main = release-only (operator). Don't re-plan closed waves
- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); IMPLEMENTED, PR#10 open, awaiting doyle G3 gate. On-demand projection (topology-independent, no PTY); PTY-parse engine retired; published digest-record contract; autonomous-freshness+Option-C deferred. After G3 → M9 close
- [hfenduleam target bloat → CI RED](hfenduleam-target-bloat-ci-red.md) — Win-runner LNK1318/os-error-112 can be disk-full from cargo target bloat (396GB seen), NOT a code regression; cargo clean + re-run; check disk before treating a Windows RED as a code defect


Two M9 index lines (line 1 wave1, line 2 wave3 — separate file I didn't have). Update both to M9-complete.


- [M9 status](m9-wave1-progress.md) — gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Merge-to-main = release-only (operator). Don't re-plan closed waves
- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); IMPLEMENTED, PR#10 open, awaiting doyle G3 gate. On-demand projection (topology-independent, no PTY); PTY-parse engine retired; published digest-record contract; autonomous-freshness+Option-C deferred. After G3 → M9 close

- [M9 status](m9-wave1-progress.md) — M9 COMPLETE, ALL 4 gates closed: G1(PR#7) · G2(PR#8) · Gateway-WAN(PR#9,REQ-MSG-6) · G3(PR#10). Merge-to-main = release-only (operator); PRs HELD; no tag rides M9. Don't re-plan, don't merge
- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); G3 STAMPED PASS by doyle 2026-06-13, PR#10 held. On-demand projection (topology-independent, no PTY); PTY-parse retired; published digest-record contract; autonomous-freshness+Option-C deferred to consuming-frontend milestone (DEFERRED.md:44)


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\MEMORY.md has been updated successfully. (file state is current in your context — no need to Read it back)

- [M9 status](m9-wave1-progress.md) — M9 COMPLETE, ALL 4 gates closed: G1(PR#7) · G2(PR#8) · Gateway-WAN(PR#9,REQ-MSG-6) · G3(PR#10). Merge-to-main = release-only (operator); PRs HELD; no tag rides M9. Don't re-plan, don't merge
- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); G3 STAMPED PASS by doyle 2026-06-13, PR#10 held. On-demand projection (topology-independent, no PTY); PTY-parse retired; published digest-record contract; autonomous-freshness+Option-C deferred to consuming-frontend milestone (DEFERRED.md:44)

- [M9 status](m9-wave1-progress.md) — gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Merge-to-main = release-only (operator). Don't re-plan closed waves
- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); IMPLEMENTED, PR#10 open, awaiting doyle G3 gate. On-demand projection (topology-independent, no PTY); PTY-parse engine retired; published digest-record contract; autonomous-freshness+Option-C deferred. After G3 → M9 close
- [hfenduleam target bloat → CI RED](hfenduleam-target-bloat-ci-red.md) — Win-runner LNK1318/os-error-112 can be disk-full from cargo target bloat (396GB seen), NOT a code regression; cargo clean + re-run; check disk before treating a Windows RED as a code defect
- [v0.4.1 Linux respawn blocker](v041-linux-respawn-blocker.md) — v0.4.1 PUBLIC (counter 8); fleet roll found Linux brain-respawn bug (current_exe follows apply-rename → brain runs OLD bytes, records applied:8). hfenduleam(Win) GREEN, kitsubito(Linux) RED. RESOLVED by v0.4.2 — see [[v042-published]]
- [v0.4.2 published](v042-published.md) — PUBLIC 2026-06-11 (counter 9); FIXES v0.4.1 Linux respawn blocker (respawn via canonical install path not current_exe + bytes-assert→auto-rollback; REQ-HAZARD-BRAIN-RESPAWN-PATH + real-rename E2E). deployah drove full merge→tag→sign→publish solo. hashes linux 500a9e7b… win b9ec8ddd…. ASYMMETRY: v0.4.2 apply still rides buggy v0.4.0 broker → 1 final manual Linux bounce loads fix; seamless proof on NEXT update via debug-channel. todlando driving fleet roll + restoration close
- [pump-IPC-deadline fix](pump-ipc-deadline-fix.md) — DONE: MERGED to main @ 4d8b810 (PR #3), doyle-approved, CI green both runners; reader-thread+channel carrier (nonblock dead on Win named pipes), both amendments folded, no tag (rides v0.4.1). Don't re-plan
- [CI self-host migration](ci-selfhost-migration.md) — DONE: runners live on kitsubito + hfenduleam (gravity retired 2026-06-07); twohost jobs gated by [twohost] commit tag; don't re-plan
- [which process is the listener](spt-daemon-is-live-infra.md) — owl.exe (plugin) = live listener, hands off; target\debug\spt.exe daemon = dev/test artifact, killable
- [self-clear skill API-blocked](self-clear-skill-api-blocked.md) — never invoke /self-clear; usage-policy classifier refuses the turn; continue in-context / user-manual /clear instead
- [Adapter glue-model boundary](adapter-glue-model-boundary.md) — adapters = manifest + binary only; real adapters in own repos; no adapter-aware source in spt-core
- [M6 shipped v0.1.0](m6-shipped-v010.md) — v0.1.0 public 2026-06-05; release procedure = RELEASE-RUNBOOK.md; releases-repo/ is repo-face truth; don't re-plan M6
- [Gravity deploy procedure](gravity-deploy-procedure.md) — ssh david@100.100.62.43; build in ~/spt-core-deploy NOT runner workspace; setsid daemon; ~1 min first-sync after restart
- [Linux SPT_HOME no-bug](linux-spt-home-no-bug.md) — unix spawn_detached inherits env (no env_clear), forwards SPT_HOME; only Win CreateEnvironmentBlock rebuilds/drops it (f99dc25). Default-home daemon = launched w/o the var, not a propagation bug; don't reopen
- [kitsubito Linux rig](kitsubito-linux-rig.md) — ssh reavus@kitsubito (key auth); cargo at ~/.cargo/bin; deploy checkout origin=local runner (NOT GitHub) so unpushed commits transfer via git bundle; live spt-daemon user unit, --user ops need no sudo; build-in-scratch leaves ~/.local/bin/spt untouched
- [Use $OWL send for messaging](owl-send-not-legacy-spt-send.md) — `$OWL send` works (legacy = where live perches run); `spt send` is the NEW spt-core binary but no perches on it yet → bounces NO_PERCH; don't read `$?` after piping send through tail (use PIPESTATUS)
- [daemon service-detection gotcha](daemon-service-detection-gotcha.md) — service detection reading GLOBAL OS state is blind on a single-home non-elevated dev box; gate Linux service routing on SPT_HOME-unset, Windows run-refuse on would-vanish (not is_elevated); push→CI BEFORE tagging a release (v0.3.0 tagged early, CI caught 2 regressions, retagged)
- [v0.3.2 platform-safe update](v032-platform-safe-update.md) — REQ-UPD-8 PUBLISHED + rig-verified 2026-06-09 (counter 6): fetch stages multi-platform update-set, apply refuses unstamped/wrong-OS single. Fleet kitsubito+hfenduleam=0.3.2 (ENLYZEAM TODO). GOTCHA: fetch/pump floor=staged_version → can't re-stage same version single→set; clear `$SPT_HOME/releases` first (Linux ~/.spt-core, Win %LOCALAPPDATA%\spt-core). CORRECTION: release-publish never built a SignedUpdateSet pre-v0.3.2 (was debug_rollout)
- [boot-race self-heal](boot-race-self-heal.md) — v0.3.1: daemon self-heals a net-less boot start (NetHost retry+attach to OnceLock broker); systemd-USER network-online ordering does NOT reliably prevent the race under linger (field-proven kitsubito) — the self-heal is the real guarantee, unit ordering is belt
- [v0.4.0 published + fleet rolled](v040-published-fleet-rolled.md) — restoration release PUBLIC 2026-06-10 (counter 7); D7-4 bytes-match done both nodes (brain.ready exe_hash == artifact); v0.3.2→v0.4.0 apply needed last manual bounce (in-process can't self-relaunch); seamless no-bounce proof + enlyzeam catch-up + milestone close all deferred to v0.4.1
- [v0.4.1 published](v041-published.md) — PUBLIC 2026-06-11 (counter 8); deployah drove full merge→tag→sign→publish solo; D7 SEAMLESS no-bounce fleet-verify payload (apply must roll bytes live w/ pid change, no manual bounce); acceptance hashes linux 35c9544c… win 728a1bfc…; hfenduleam(Win) GREEN no-bounce, kitsubito(Linux) RED → CLOSE BLOCKED: v0.4.0 broker respawns via current_exe() which follows apply-rename to .old-8=OLD bytes (Linux only); artifacts SOUND don't yank; v0.4.2 fix = respawn by canonical path + bytes-assert, needs manual Linux broker bounce; deployah takes counter 9 publish
- [hfenduleam disk-full CI](hfenduleam-disk-full-ci.md) — Win CI LNK1318/os-error-112 = disk full not code; cargo clean dev target (was 396GB), rerun job
- [M9 gateway-wan progress](m9-gateway-wan-progress.md) — REQ-MSG-6 cross-node Gateway WAN user-msg; PR #9 open (base m9-user-surfaces), impl complete + local sweep green, HOLDING on CI; PR #8 G2 now green post disk-fix

-- [M9 status](m9-wave1-progress.md) — gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Merge-to-main = release-only (operator). Don't re-plan closed waves

-- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); IMPLEMENTED, PR#10 open, awaiting doyle G3 gate. On-demand projection (topology-independent, no PTY); PTY-parse engine retired; published digest-record contract; autonomous-freshness+Option-C deferred. After G3 → M9 close

+- [M9 status](m9-wave1-progress.md) — M9 COMPLETE, ALL 4 gates closed: G1(PR#7) · G2(PR#8) · Gateway-WAN(PR#9,REQ-MSG-6) · G3(PR#10). Merge-to-main = release-only (operator); PRs HELD; no tag rides M9. Don't re-plan, don't merge

+- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); G3 STAMPED PASS by doyle 2026-06-13, PR#10 held. On-demand projection (topology-independent, no PTY); PTY-parse retired; published digest-record contract; autonomous-freshness+Option-C deferred to consuming-frontend milestone (DEFERRED.md:44)


Update the wave3 memory body so it's not read as "awaiting gate."


1	---
2	name: m9-wave3-digest-progress
3	description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); PR
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: 86f6382c-e3ce-4405-a345-5f5ce8b948e7
8	---
9
10	**M9 Wave 3 (G3) — session digest re-founded on session logs** (ADR-0008 amendment). doyle-vetted plan `M9-WAVE3-JIT.md`; greenlit 2026-06-13. Branch `m9-digest-logs` off the M9 stack tip (`m9-gateway-wan` @ 58f034f). **PR #10 open** (base m9-gateway-wan), awaiting doyle's **G3 gate**.
11
12	5 commits, all green (clippy -D workspace, cargo test --workspace, xtask check, traceable EXIT=0):
13	- 82ead73 T7a `spt-term::projection` — published **digest-record contract** (`{role∈input|agent|tool, text, tool{name,arg}}`) + project/project_lines + 8 units
14	- 6033326 T7a `api digest-entry` door + `spt_store::append_digest_entry` → separate `digest.log`
15	- 5a09577 T7b-core on-demand projector (`project_endpoint_digest`) + `DigestHub::project_and_publish` + `reproject` verb; removed dead `run_digest_feed`; CC-fixture E2E
16	- ba8e3c1 T7b retire+re-point: deleted DigestParser/PtyDigest/[pty_digest] (schema re-blessed, regex dep dropped); RETITLED REQ-TERM-4 honest log-source title + impl/unit/int tags on projection+E2E; migrated native fixture; full docs sweep
17
18	**KEY ARCHITECTURE:** digest is now an **on-demand projection** of normalized session logs, NOT a PTY byte-parse. **Topology-independent** — harness-hosted (Claude-Code) endpoint with no broker PTY gets a digest. The byte-feed (`run_digest_feed`) had **zero production callers** — never live-wired — so retire was clean. History subsystem stays opaque; the digest-record contract is spt-core's published overlay on the line content (one schema, no mapping layer). digest rides `[history]`, no manifest seam.
19
20	**doyle RULINGS folded in:** §2 = approach A (spt-core PUBLISHES the contract, not "adapters already emit it"; migrate the stray fixture). T7b scope = snapshot-pull on-demand projection IS the G3 bar; **autonomous file-watch freshness + Option-C persistence re-home DEFERRED** (tracked in `docs/DEFERRED.md`, consuming-frontend milestone — no consumer yet: Shell M10 / GUI pane / Gateway window). Delta-stream CONTRACT ships, deltas driven by pull/digest-entry pushes. G3-note wording locked: "autonomous file-watch freshness deferred to consuming-frontend milestone; snapshot-pull + push-driven deltas delivered."
21
22	Side-check done: T5 twohost honored-path int already logged REQUIRED-BEFORE-LECTURN on REQ-MSG-6 + ROADMAP:81 (commit 58f034f) — nothing to add.
23
24	**G3 GATE: PASS — doyle STAMPED 2026-06-13.** Independently verified end-to-end: title honest (1fc6236 dropped the Option-C overclaim), evidence doc+impl+unit+int on real code, no orphan tags, retire grep-clean (his one hit a false positive: "em-pty_digest" substring in test name), CC-fixture E2E 1/0 topology-independence proven, workspace zero-fail + clippy -D + --no-default-features + xtask check, docs sweep + BOTH deferrals logged. ONE HOLD during gate (title overclaimed "opt-in Path-B log" = deferred Option-C, no evidence) — FIXED 1fc6236, doyle re-verified, cleared. doyle praised the byte-feed-never-live discovery + the second Option-C deferral catch.
25
26	**M9 ("Adapter customization & session surfaces") COMPLETE** — all four gates closed: G1 ✓ G2 ✓ Gateway-WAN ✓ G3 ✓ (see [[m9-gateway-wan-progress]]). **DO NOT MERGE PR #10** — operator/release-gated only (policy 2026-06-13); PR stays open as the gate vehicle, no tag rides M9. STAND DOWN on M9; next move is the operator's.
27

---
name: m9-wave3-digest-progress
description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); PR
metadata:
  node_type: memory
  type: project
  originSessionId: 86f6382c-e3ce-4405-a345-5f5ce8b948e7
---

**M9 Wave 3 (G3) — session digest re-founded on session logs** (ADR-0008 amendment). doyle-vetted plan `M9-WAVE3-JIT.md`; greenlit 2026-06-13. Branch `m9-digest-logs` off the M9 stack tip (`m9-gateway-wan` @ 58f034f). **PR #10 open** (base m9-gateway-wan), awaiting doyle's **G3 gate**.

5 commits, all green (clippy -D workspace, cargo test --workspace, xtask check, traceable EXIT=0):
- 82ead73 T7a `spt-term::projection` — published **digest-record contract** (`{role∈input|agent|tool, text, tool{name,arg}}`) + project/project_lines + 8 units
- 6033326 T7a `api digest-entry` door + `spt_store::append_digest_entry` → separate `digest.log`
- 5a09577 T7b-core on-demand projector (`project_endpoint_digest`) + `DigestHub::project_and_publish` + `reproject` verb; removed dead `run_digest_feed`; CC-fixture E2E
- ba8e3c1 T7b retire+re-point: deleted DigestParser/PtyDigest/[pty_digest] (schema re-blessed, regex dep dropped); RETITLED REQ-TERM-4 honest log-source title + impl/unit/int tags on projection+E2E; migrated native fixture; full docs sweep

**KEY ARCHITECTURE:** digest is now an **on-demand projection** of normalized session logs, NOT a PTY byte-parse. **Topology-independent** — harness-hosted (Claude-Code) endpoint with no broker PTY gets a digest. The byte-feed (`run_digest_feed`) had **zero production callers** — never live-wired — so retire was clean. History subsystem stays opaque; the digest-record contract is spt-core's published overlay on the line content (one schema, no mapping layer). digest rides `[history]`, no manifest seam.

**doyle RULINGS folded in:** §2 = approach A (spt-core PUBLISHES the contract, not "adapters already emit it"; migrate the stray fixture). T7b scope = snapshot-pull on-demand projection IS the G3 bar; **autonomous file-watch freshness + Option-C persistence re-home DEFERRED** (tracked in `docs/DEFERRED.md`, consuming-frontend milestone — no consumer yet: Shell M10 / GUI pane / Gateway window). Delta-stream CONTRACT ships, deltas driven by pull/digest-entry pushes. G3-note wording locked: "autonomous file-watch freshness deferred to consuming-frontend milestone; snapshot-pull + push-driven deltas delivered."

Side-check done: T5 twohost honored-path int already logged REQUIRED-BEFORE-LECTURN on REQ-MSG-6 + ROADMAP:81 (commit 58f034f) — nothing to add.

**G3 GATE: PASS — doyle STAMPED 2026-06-13.** Independently verified end-to-end: title honest (1fc6236 dropped the Option-C overclaim), evidence doc+impl+unit+int on real code, no orphan tags, retire grep-clean (his one hit a false positive: "em-pty_digest" substring in test name), CC-fixture E2E 1/0 topology-independence proven, workspace zero-fail + clippy -D + --no-default-features + xtask check, docs sweep + BOTH deferrals logged. ONE HOLD during gate (title overclaimed "opt-in Path-B log" = deferred Option-C, no evidence) — FIXED 1fc6236, doyle re-verified, cleared. doyle praised the byte-feed-never-live discovery + the second Option-C deferral catch.

**M9 ("Adapter customization & session surfaces") COMPLETE** — all four gates closed: G1 ✓ G2 ✓ Gateway-WAN ✓ G3 ✓ (see [[m9-gateway-wan-progress]]). **DO NOT MERGE PR #10** — operator/release-gated only (policy 2026-06-13); PR stays open as the gate vehicle, no tag rides M9. STAND DOWN on M9; next move is the operator's.


File body already fully reflects G3 PASS (lines 24-26) — a parallel write beat me to it. Only the truncated description line is stale. Fix it.


description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); PR

description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); G3 STAMPED PASS, PR#10 held


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\m9-wave3-digest-progress.md has been updated successfully. (file state is current in your context — no need to Read it back)

---
name: m9-wave3-digest-progress
description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); PR
metadata:
  node_type: memory
  type: project
  originSessionId: 86f6382c-e3ce-4405-a345-5f5ce8b948e7
---

**M9 Wave 3 (G3) — session digest re-founded on session logs** (ADR-0008 amendment). doyle-vetted plan `M9-WAVE3-JIT.md`; greenlit 2026-06-13. Branch `m9-digest-logs` off the M9 stack tip (`m9-gateway-wan` @ 58f034f). **PR #10 open** (base m9-gateway-wan), awaiting doyle's **G3 gate**.

5 commits, all green (clippy -D workspace, cargo test --workspace, xtask check, traceable EXIT=0):
- 82ead73 T7a `spt-term::projection` — published **digest-record contract** (`{role∈input|agent|tool, text, tool{name,arg}}`) + project/project_lines + 8 units
- 6033326 T7a `api digest-entry` door + `spt_store::append_digest_entry` → separate `digest.log`
- 5a09577 T7b-core on-demand projector (`project_endpoint_digest`) + `DigestHub::project_and_publish` + `reproject` verb; removed dead `run_digest_feed`; CC-fixture E2E
- ba8e3c1 T7b retire+re-point: deleted DigestParser/PtyDigest/[pty_digest] (schema re-blessed, regex dep dropped); RETITLED REQ-TERM-4 honest log-source title + impl/unit/int tags on projection+E2E; migrated native fixture; full docs sweep

**KEY ARCHITECTURE:** digest is now an **on-demand projection** of normalized session logs, NOT a PTY byte-parse. **Topology-independent** — harness-hosted (Claude-Code) endpoint with no broker PTY gets a digest. The byte-feed (`run_digest_feed`) had **zero production callers** — never live-wired — so retire was clean. History subsystem stays opaque; the digest-record contract is spt-core's published overlay on the line content (one schema, no mapping layer). digest rides `[history]`, no manifest seam.

**doyle RULINGS folded in:** §2 = approach A (spt-core PUBLISHES the contract, not "adapters already emit it"; migrate the stray fixture). T7b scope = snapshot-pull on-demand projection IS the G3 bar; **autonomous file-watch freshness + Option-C persistence re-home DEFERRED** (tracked in `docs/DEFERRED.md`, consuming-frontend milestone — no consumer yet: Shell M10 / GUI pane / Gateway window). Delta-stream CONTRACT ships, deltas driven by pull/digest-entry pushes. G3-note wording locked: "autonomous file-watch freshness deferred to consuming-frontend milestone; snapshot-pull + push-driven deltas delivered."

Side-check done: T5 twohost honored-path int already logged REQUIRED-BEFORE-LECTURN on REQ-MSG-6 + ROADMAP:81 (commit 58f034f) — nothing to add.

**G3 GATE: PASS — doyle STAMPED 2026-06-13.** Independently verified end-to-end: title honest (1fc6236 dropped the Option-C overclaim), evidence doc+impl+unit+int on real code, no orphan tags, retire grep-clean (his one hit a false positive: "em-pty_digest" substring in test name), CC-fixture E2E 1/0 topology-independence proven, workspace zero-fail + clippy -D + --no-default-features + xtask check, docs sweep + BOTH deferrals logged. ONE HOLD during gate (title overclaimed "opt-in Path-B log" = deferred Option-C, no evidence) — FIXED 1fc6236, doyle re-verified, cleared. doyle praised the byte-feed-never-live discovery + the second Option-C deferral catch.

**M9 ("Adapter customization & session surfaces") COMPLETE** — all four gates closed: G1 ✓ G2 ✓ Gateway-WAN ✓ G3 ✓ (see [[m9-gateway-wan-progress]]). **DO NOT MERGE PR #10** — operator/release-gated only (policy 2026-06-13); PR stays open as the gate vehicle, no tag rides M9. STAND DOWN on M9; next move is the operator's.

-description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); PR

+description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); G3 STAMPED PASS, PR#10 held


**G3 PASS — M9 COMPLETE.**

Verified end-to-end before stamping:
- CC E2E `harness_hosted_digest_projects_and_pushes_deltas` — 1/0, topology-independence proven (no broker/PTY)
- Workspace zero-fail (56 bins) · clippy -D · --no-default-features · xtask OK · traceable EXIT=0
- PTY-parse grep-clean (sole "hit" a false-positive substring in a test name)
- Title HOLD I caught mid-gate (`opt-in Path-B log` overclaimed deferred Option-C) → todlando fixed 1fc6236 → re-verified clean
- DEFERRED.md logs both deferrals; ADR-0008 debt discharged; T5 side-check confirmed logged

All four M9 gates closed: **G1 · G2 · Gateway-WAN · G3.** Memory synced.

**PR #10 held — merge-to-main is operator/release-gated, no tag rides M9.** Standing down on M9. Next move is the operator's.


# Autonomous loop check

You're being invoked on a timer while the user is away or occupied. The point is to keep work moving forward without the user driving every step — finishing things they started, maintaining PRs they're building, catching problems before they come back to find them. You're a steward, not an initiator. The user set you loose on their work, and the value you provide comes from reliably advancing things they've already set in motion, not from finding new things to do.

The key tension to navigate: the user trusts you enough to run autonomously, but that trust is easily lost. Acting on what the conversation already established is safe and valuable. Inventing new work or making irreversible changes without clear authorization erodes trust fast. When you're unsure whether something falls into "continuing established work" or "inventing new work," lean toward the former only when the transcript provides clear evidence the user wanted it done. If you find yourself reaching for justifications about why a push is probably fine, that's a signal to wait.

## What to act on

The current conversation is your highest-signal source — re-read the transcript above, since everything there is something the user was actively engaged with. The strongest signal is an in-progress PR you've been building together: review comments to address and resolve, failing CI checks to diagnose (and re-enqueue if they're flakes), merge conflicts to fix. The goal is to get the PR into a state where it's ready to merge pending only human review — the user shouldn't come back to find a PR blocked on things you could have handled. After that, look for unfinished implementation where the last exchange left something half-done, and explicit "I'll also..." or "next I'll..." commitments the conversation made and didn't honor. Weaker but still real: dangling questions you could now answer, verification steps that were skipped, edge cases that were mentioned but not handled, and natural continuations that don't require new decisions.

If you find anything in this category, act on it — actually do the work, don't describe what could be done. Run the tests, don't say "you could run the tests." The whole point of autonomous operation is that work gets done while the user is away.

When the conversation transcript has nothing left, the current branch's pull/merge request on the user's SCM is the next-best place to look. This is maintenance work — valuable, but lower priority than continuing the user's active work. Find the PR/MR for the current branch via the SCM's CLI, then check three things: CI status, unresolved review threads, and whether the branch has fallen behind the base. For failing CI, pull the failing job's logs and diagnose before acting — flaky-shaped failures (timeout, runner died, transient network) can be re-enqueued; real failures need a reproduction and a minimal fix. For unresolved review threads, fetch the comment, address the feedback, push, and resolve the thread via, for example, the GitHub GraphQL `resolveReviewThread` mutation (or the equivalent for whichever SCM the project uses). Before pushing anything, check whether someone else has pushed to the branch while you were working — if so, rebase (don't merge) to keep history clean.

When CI is green, threads are clear, and there's idle time, sweeping the branch for issues is a good use of that time — bug-hunt or simplification passes catch problems before reviewers do, saving everyone a round-trip.

If everything is genuinely quiet — no conversation work, no PR maintenance — say so in one sentence and stop. No summary of what you checked, no list of what you might do later. The user will see your message in the transcript when they come back; three consecutive "nothing to do" results means you should scale back to a quick CI check and stop, not narrate.

## Repeated invocations

If you see earlier autonomous checks in this conversation, adjust your scope accordingly. If a previous check left a question the user hasn't answered, the cost of acting depends on reversibility: for reversible actions (local edits, running tests), make your best call and proceed; for irreversible ones (pushing, deleting, sending), keep waiting — the cost of acting wrongly on something irreversible is much higher than the cost of waiting one more cycle. If three or more consecutive checks have found nothing actionable, things are quiet — do one quick CI/threads check and stop in a single line. Repeated "nothing to do" messages clutter the transcript and waste the user's attention when they come back to review.

Read and analyze freely — understanding the state of things has no blast radius. Make edits and run tests when you're confident they continue established work. Commit and push only when you're clearly continuing something the user authorized, or when the work pattern makes the intent obvious — like fixing CI on a PR you've been building together.

---

# Autonomous loop tick (dynamic pacing)

Run the autonomous check using the loop instructions established earlier in this conversation. If you cannot find them, treat this as a no-op tick.

You scheduled this tick via the ScheduleWakeup tool (not a recurring cron). To keep the loop alive, call ScheduleWakeup again at the end of this turn with `prompt` set to the literal sentinel `<<autonomous-loop-dynamic>>` — otherwise the loop ends after this tick.

If a Monitor is armed (check TaskList), keep `delaySeconds` at 1200–1800s — the Monitor is the wake signal and this is only the fallback heartbeat. If you were woken by a `<task-notification>`, handle the event before rescheduling. To stop the loop, also TaskStop the monitor (use TaskList to find its task ID if no longer in context).


Claude resuming /loop wakeup (Jun 13 6:26am)


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


Autonomous tick. Conversation work done (M9 complete, G3 stamped). PR #10 held/operator-gated — I'm reviewer not author. Quick CI check so operator/todlando finds it clean, no pushing to todlando's branch.


Bash

gh pr checks 10 2>&1 | head -30; echo "=== mergeable ==="; gh pr view 10 --json mergeable,mergeStateStatus,reviewDecision 2>&1

Check PR #10 CI + merge state


n1-gate (self-hosted, Windows, hfenduleam)	pass	1m37s	https://github.com/SaberMage/spt-core/actions/runs/27467556872/job/81192498767
traceability	pass	9s	https://github.com/SaberMage/spt-core/actions/runs/27467556872/job/81192498799
twohost-a	skipping	0	https://github.com/SaberMage/spt-core/actions/runs/27467556872/job/81192945794
twohost-b	skipping	0	https://github.com/SaberMage/spt-core/actions/runs/27467556872/job/81192945863
n1-gate (self-hosted, Linux, kitsubito)	pass	1m18s	https://github.com/SaberMage/spt-core/actions/runs/27467556872/job/81192498790
test (self-hosted, Linux, kitsubito)	pass	6m16s	https://github.com/SaberMage/spt-core/actions/runs/27467556872/job/81192498746
test (self-hosted, Windows, hfenduleam)	pass	7m34s	https://github.com/SaberMage/spt-core/actions/runs/27467556872/job/81192498765
=== mergeable ===
{"mergeStateStatus":"CLEAN","mergeable":"MERGEABLE","reviewDecision":""}


PR #10 fully green — both runners pass, traceability pass, twohost skipping (no `[twohost]` tag, expected), MERGEABLE/CLEAN. Merge is operator-gated. Nothing actionable; quiet.


ScheduleWakeup

M9 closed, PR #10 green/held for operator; owl Monitor is primary wake, this is fallback heartbeat.

Prompt: <<autonomous-loop-dynamic>>


Next wakeup scheduled for 06:57:00 (in 1839s). Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.


All quiet — M9 complete, PR #10 green and held for operator merge.


# Autonomous loop tick (dynamic pacing)

Run the autonomous check using the loop instructions established earlier in this conversation. If you cannot find them, treat this as a no-op tick.

You scheduled this tick via the ScheduleWakeup tool (not a recurring cron). To keep the loop alive, call ScheduleWakeup again at the end of this turn with `prompt` set to the literal sentinel `<<autonomous-loop-dynamic>>` — otherwise the loop ends after this tick.

If a Monitor is armed (check TaskList), keep `delaySeconds` at 1200–1800s — the Monitor is the wake signal and this is only the fallback heartbeat. If you were woken by a `<task-notification>`, handle the event before rescheduling. To stop the loop, also TaskStop the monitor (use TaskList to find its task ID if no longer in context).


Claude resuming /loop wakeup (Jun 13 6:57am)


Bash

gh pr checks 10 2>&1 | grep -vE "pass|skipping" | head; echo "exit-state: $(gh pr view 10 --json mergeStateStatus -q .mergeStateStatus 2>&1)"

Quick PR #10 regression check


exit-state: CLEAN


No non-passing checks, still CLEAN. Quiet.


ScheduleWakeup

Second quiet tick; PR #10 green/held for operator. Owl Monitor primary wake, this fallback.

Prompt: <<autonomous-loop-dynamic>>


Next wakeup scheduled for 07:28:00 (in 1848s). Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.


Quiet — PR #10 clean and held.


Claude resuming /loop wakeup (Jun 13 7:28am)


Third quiet tick — scaling back per loop rules.


Bash

gh pr view 10 --json mergeStateStatus,statusCheckRollup -q '.mergeStateStatus + " / failing:" + ([.statusCheckRollup[]|select(.conclusion=="FAILURE")]|length|tostring)' 2>&1

One-line PR #10 health


CLEAN / failing:0


Quiet — PR #10 CLEAN, 0 failing.


ScheduleWakeup

Third quiet tick; loop stays alive for todlando/operator pings via owl Monitor, this is fallback.

Prompt: <<autonomous-loop-dynamic>>


Next wakeup scheduled for 07:59:00 (in 1849s). Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.


Quiet.


Claude resuming /loop wakeup (Jun 13 7:59am)


Quiet — nothing changed; owl Monitor will wake me on real events.


Fourth quiet tick; M9 closed, PR held. Owl Monitor is the real wake signal; fallback heartbeat only.

Prompt: <<autonomous-loop-dynamic>>


Next wakeup scheduled for 08:30:00 (in 1853s). Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.


Claude resuming /loop wakeup (Jun 13 8:29am)


Quiet. Reschedule.


Fifth quiet tick; M9 closed, PR held. Owl Monitor is real wake; fallback heartbeat only.

Prompt: <<autonomous-loop-dynamic>>


Next wakeup scheduled for 09:00:00 (in 1801s). Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.


Claude resuming /loop wakeup (Jun 13 8:59am)


Sixth quiet tick; M9 closed, PR held. Owl Monitor is real wake; fallback heartbeat only.

Prompt: <<autonomous-loop-dynamic>>


Next wakeup scheduled for 09:31:00 (in 1857s). Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.


Claude resuming /loop wakeup (Jun 13 9:31am)


Seventh quiet tick; M9 closed, PR held. Owl Monitor is real wake; fallback heartbeat only.

Prompt: <<autonomous-loop-dynamic>>


Next wakeup scheduled for 10:02:00 (in 1856s). Nothing more to do this turn — the harness re-invokes you when the wakeup fires or a task-notification arrives.


Claude resuming /loop wakeup (Jun 13 10:02am)


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


Eight quiet ticks. Owl Monitor (task bg7cqpvni) is the real wake signal — every todlando/operator event this session came through it, independent of this heartbeat. The fallback loop adds nothing now. Ending it; owl Monitor stays armed and will wake me on any real message.

Loop ended — standing by via owl perch.


<command-message>improve-codebase-architecture</command-message>
<command-name>/improve-codebase-architecture</command-name>
<command-args>--> update based on the current state. the main thing i care to understand is whether any outstanding improvements should be made before building `spt-claude-code`, or if the codebase is in a good place for that side project.</command-args>


Base directory for this skill: C:\Users\decid\.ccs\instances\bigscreen\skills\improve-codebase-architecture

# Improve Codebase Architecture

Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability.

## Glossary

Use these terms exactly in every suggestion. Consistent language is the point — don't drift into "component," "service," "API," or "boundary." Full definitions in [LANGUAGE.md](LANGUAGE.md).

- **Module** — anything with an interface and an implementation (function, class, package, slice).
- **Interface** — everything a caller must know to use the module: types, invariants, error modes, ordering, config. Not just the type signature.
- **Implementation** — the code inside.
- **Depth** — leverage at the interface: a lot of behaviour behind a small interface. **Deep** = high leverage. **Shallow** = interface nearly as complex as the implementation.
- **Seam** — where an interface lives; a place behaviour can be altered without editing in place. (Use this, not "boundary.")
- **Adapter** — a concrete thing satisfying an interface at a seam.
- **Leverage** — what callers get from depth.
- **Locality** — what maintainers get from depth: change, bugs, knowledge concentrated in one place.

Key principles (see [LANGUAGE.md](LANGUAGE.md) for the full list):

- **Deletion test**: imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.
- **The interface is the test surface.**
- **One adapter = hypothetical seam. Two adapters = real seam.**

This skill is _informed_ by the project's domain model. The domain language gives names to good seams; ADRs record decisions the skill should not re-litigate.

## Process

### 1. Explore

Read the project's domain glossary and any ADRs in the area you're touching first.

Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction:

- Where does understanding one concept require bouncing between many small modules?
- Where are modules **shallow** — interface nearly as complex as the implementation?
- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)?
- Where do tightly-coupled modules leak across their seams?
- Which parts of the codebase are untested, or hard to test through their current interface?

Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want.

### 2. Present candidates as an HTML report

Write a self-contained HTML file to the OS temp directory so nothing lands in the repo. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `<tmpdir>/architecture-review-<timestamp>.html` so each run gets a fresh file. Open it for the user — `xdg-open <path>` on Linux, `open <path>` on macOS, `start <path>` on Windows — and tell them the absolute path.

The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual.

For each candidate, the same template as before, but rendered as a card:

- **Files** — which files/modules are involved
- **Problem** — why the current architecture is causing friction
- **Solution** — plain English description of what would change
- **Benefits** — explained in terms of locality and leverage, and how tests would improve
- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening
- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge

End the report with a **Top recommendation** section: which candidate you'd tackle first and why.

**Use CONTEXT.md vocabulary for the domain, and [LANGUAGE.md](LANGUAGE.md) vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service."

**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids.

See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance.

Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?"

### 3. Grilling loop

Once the user picks a candidate, drop into a grilling conversation. Walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive.

Side effects happen inline as decisions crystallize:

- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as `/grill-with-docs` (see [CONTEXT-FORMAT.md](../grill-with-docs/CONTEXT-FORMAT.md)). Create the file lazily if it doesn't exist.
- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there.
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [ADR-FORMAT.md](../grill-with-docs/ADR-FORMAT.md).
- **Want to explore alternative interfaces for the deepened module?** See [INTERFACE-DESIGN.md](INTERFACE-DESIGN.md).

ARGUMENTS: --> update based on the current state. the main thing i care to understand is whether any outstanding improvements should be made before building `spt-claude-code`, or if the codebase is in a good place for that side project.


/improve-codebase-architecture --> update based on the current state. the main thing i care to understand is whether any outstanding improvements should be made before building `spt-claude-code`, or i…


User question: is spt-core ready to build `spt-claude-code` (the Claude Code harness adapter) against, or are there seam improvements to make first? The crux: spt-claude-code would be the **first real adapter** — seams so far proven only by mock/test adapters. "Two adapters = real seam." Need to assess depth + stability of every adapter-facing seam.

Let me explore the adapter-facing surface in parallel + read the governing ADRs myself.


Agent

Map adapter manifest seam

Prompt:
Explore the spt-core codebase (C:\Users\decid\Documents\projects\spt-core) and report on the ADAPTER MANIFEST / HARNESS CONTRACT seam — the interface a harness adapter (like a future `spt-claude-code`) must satisfy. This is for an architecture-readiness review.

Read CONTEXT.md (the domain glossary — use its exact vocabulary in your report) and any docs/adr/*.md + docs/MANIFEST.md that govern the adapter/manifest contract. Then walk the code.

Focus on these questions:
1. What must a `kind="harness"` adapter DECLARE in its manifest.toml? Enumerate every field/section spt-core reads (adapter metadata, [history], profiles, strings, hints, echo-commune command, any digest seam, launch/hosting fields). Cite file:line.
2. Where does spt-core PARSE + VALIDATE a manifest (spt-runtime manifest.rs, registry)? Is `Manifest::validate` the single gate, or is validation scattered?
3. Apply the DEPTH lens: is the manifest interface DEEP (small surface, lots of behavior behind it) or SHALLOW (caller must know as much as the implementation)? Is it a real seam (multiple adapters) or hypothetical (only mock/test adapters exercise it)?
4. Where would a REAL first adapter (spt-claude-code) hit friction or under-specified contract? E.g. fields that exist but are never exercised by a real adapter, validation gaps, things a harness adapter needs that the manifest can't express yet.
5. Grep for how many DISTINCT adapters exist today (mock/test fixtures vs real). List them.

Report conc: per finding give file:line evidence, the friction, and whether it blocks or merely inconveniences a real harness-adapter author. Do NOT propose solutions — just map the seam + its gaps. Use CONTEXT.md domain terms.


Map content seams (history/echo/digest)

Prompt:
Explore the spt-core codebase (C:\Users\decid\Documents\projects\spt-core) and report on the CONTENT SEAMS a harness adapter feeds: the history subsystem, echo-commune, and the session-digest. This is for an architecture-readiness review ahead of building a real `spt-claude-code` adapter.

Read CONTEXT.md entries for "live activity buffer (session digest)", "echo commune", history subsystem, and docs/adr/0008* + docs/MANIFEST.md history section. Use CONTEXT.md exact vocabulary.

Focus:
1. The `[history]` seam: strategies (locate_normalize / native / fetcher). What exactly must an adapter provide (locate_template, normalize_command, etc.)? Files: crates/spt-live/src/history.rs. Cite file:line. Is HistoryRecord still opaque ("carries raw verbatim")?
2. The echo-commune consumer: crates/spt-live/src/echo.rs — how it pipes raw history to an adapter summarizer. What command/role must the adapter declare?
3. The session-digest content seam (just re-founded in M9): the PUBLISHED digest-record contract (`{role∈input|agent|tool, text, tool{name,arg}}`) — where is it defined + documented? crates/spt-term/src/projection.rs, crates/spt-term/src/digest.rs. The `api digest-entry` push door (crates/spt/src/api/reporting.rs, crates/spt-store/src/history.rs append_digest_entry → separate digest.log). The on-demand projector (crates/spt-daemon/src/digest.rs project_endpoint_digest).
4. KEY READINESS QUESTION: for a real Claude Code adapter whose per-session JSONL is the log source — can it feed BOTH echo-commune AND the digest from its logs through these seams TODAY? Where's the friction? Does the digest-record contract require the adapter's normalized lines to match a specific shape, and is that shape documented well enough for an external adapter author? Is there tension between echo (wants opaque raw) and digest (wants the {role,text,tool} contract) when both read the SAME [history] source?
5. Apply DEPTH + DELETION-TEST lens. Note where only MOCK/TEST adapters exercise these seams (hypothetical vs real seam).

Report concise: file:line evidence per finding, the friction, blocks-vs-inconveniences a real adapter author. Do NOT propose solutions — map the seams + gaps.


Map harness hosting/lifecycle seam

Prompt:
Explore the spt-core codebase (C:\Users\decid\Documents\projects\spt-core) and report on how spt-core HOSTS a harness-hosted endpoint — the lifecycle/launch/registration seam a real `spt-claude-code` adapter must integrate with. Architecture-readiness review.

Read CONTEXT.md entries on term-daemon, session surface, live agent / endpoint, adapter, info.json/perch, and endpoint_type (the M9 gateway-wan seam). Use CONTEXT.md exact vocabulary.

Focus:
1. How is a harness-hosted endpoint ESTABLISHED? What does spt-core need to know to host/launch a Claude-Code session — the adapter registry (crates/spt-runtime/src/registry.rs), perch info.json (crates/spt-store/src/info.rs, perch.rs), the `adapter` field, `endpoint_type`. Cite file:line. Look at how the CC-fixture E2E (crates/spt-daemon/tests/digest.rs) establishes a harness endpoint — `establish_cc_endpoint` — that's the closest thing to a real CC integration; what does it stub vs require?
2. The session surface seam (spt-term SessionSurface): does a harness adapter run INSIDE a supervised PTY, or is a harness-hosted endpoint (no broker/PTY, per the M9 digest work) a DIFFERENT hosting path? Is that second path (harness-hosted, log-sourced, no PTY) fully built or partially? This matters: CC is harness-hosted.
3. What's the actual LAUNCH story for a harness adapter — how does spt-core start a Claude Code session, or does the adapter self-host and just register? Grep for how kind="harness" vs native sessions differ in hosting.
4. READINESS: list concretely what spt-claude-code must implement/declare to be hosted + addressable + echo/digest-capable. Flag anything HALF-BUILT (a path the mock E2E stubs but no production code drives — e.g. the M9 work noted run_digest_feed had zero production callers; are there similar never-live-wired harness-hosting paths?).
5. DEPTH + DELETION-TEST lens on the hosting seam. Hypothetical-vs-real (only mock adapters?).

Report concise: file:line evidence, friction, blocks-vs-inconveniences. Do NOT propose solutions — map the seam + gaps.


<owl_messages>
<owl_message from="doyle-w361" priority="info">[WORKING_PERCH_NOTICE] Subagent working perch created.
Perch: doyle-w361
Agent type: Explore
To message it: $OWL send doyle-w361 doyle</owl_message>
</owl_messages>

[OWL SYSTEM] Informational owl messages above — no acknowledgment needed. Continue your current task.

<owl_reachability_notice>
You have an active owl perch. Launch this Agent with run_in_background: true so you stay reachable for incoming messages while it works.
</owl_reachability_notice>


Perfect. Now I have enough information to provide a comprehensive report. Let me compile the findings:

---

## ARCHITECTURE-READINESS REPORT: spt-core Content Seams for Claude Code Adapter

### EXECUTIVE SUMMARY

The three foundational content seams for a real `spt-claude-code` adapter are **architecture-complete and topology-independent**, but **friction exists at the adapter-author guidance boundary** — specifically the digest-record contract documentation and tension between opaque-history (echo) and contract-shaped-records (digest). A real adapter can feed all three seams TODAY, but with caveats.

---

### 1. THE `[history]` SEAM — Three Strategies, One Normalized Output

**Governing principle:** HistoryRecord stays **opaque** — spt-core carries verbatim, never parses.

#### Location & Shape
- **Definition:** `crates/spt-live/src/history.rs:30-35` — `HistoryRecord { raw: String }`
- **Strategy enum:** `crates/spt-runtime/src/manifest.rs:284-293` — `HistoryStrategy::{Fetcher, LocateNormalize, Native}`
- **Manifest schema:** `docs/MANIFEST.md` lines 81-89

#### What an Adapter Must Provide (file:line evidence)

**Path A (Pull-based):**
1. **Fetcher strategy** (`history.rs:92-101`)
   - Manifest field: `fetcher = "command-template"`
   - spt-core substitutes keys (`session_id`, etc.) then runs the command
   - stdout: newline-delimited normalized records (one per line)
   - Adapter owns: the command + the per-line schema
   - **spt-core owns:** substitution keys (which ones are available — see `history.rs:87` keys parameter)

2. **Locate+Normalize strategy** (`history.rs:103-124`)
   - Manifest fields: `locate_template`, `normalize_command`
   - Locate template: filled with keys, resolved to a file path
   - Normalize command: reads raw transcript on stdin, emits normalized records on stdout
   - Raw line encoding: opaque to spt-core (the command reads it directly; `history.rs:121` uses `std::fs::read`)

**Path B (Push-based, native store):**
3. **Native strategy** (`history.rs:126-134`)
   - Manifest field: `strategy = "native"` (no other fields required)
   - Adapter uses: `api history-log` command to push already-normalized records
   - spt-core stores in: `<perch>/history.log` (appended, newline-delimited)
   - Implementation: `crates/spt-store/src/history.rs:24-25` — `append_record(perch_path, record)`

#### The Opaque Contract Guarantee
- **Line 30-31, history.rs:** "One normalized transcript record — an opaque line. The adapter defines its schema; spt-core carries it verbatim."
- **Line 17:** "history subsystem only delimits, never parses"
- **Test proof** (`history.rs:244-274`): native test writes raw JSON lines unchanged; projection later parses them for digest

#### What's NOT Provided to Adapters
- **spt-core does NOT know** which keys to substitute (adapter must declare them in manifest `keys` field)
- **spt-core does NOT validate** the output shape — any non-empty lines are accepted
- **Timeout coverage:** all three strategies (fetcher, normalize command, path lookup) are bounded; `DIGEST_FETCH_TIMEOUT = 30s` in `crates/spt-daemon/src/digest.rs:49`; spawned commands timeout at `run_bounded_command` boundary

---

### 2. THE ECHO-COMMUNE CONSUMER — How It Pipes Raw History

**File:** `crates/spt-live/src/echo.rs`

#### The Seam Contract
- **Run the echo-commune** (`echo.rs:91-123` `run_echo_commune`)
  - Input: `&[HistoryRecord]` (the opaque raw lines from history subsystem)
  - Feed format: each record's `.raw` joined with newlines on stdin (`echo.rs:99-104`)
  - Runtime: `runtime.run_bounded_stdin(ECHO_COMMUNE_ROLE, keys, timeout, Some(stdin))`
  - **Role constant** (`echo.rs:35`): `"echo_commune"` — the adapter declares `[session.echo_commune]` template under this role name

#### What an Adapter Must Declare
- **Manifest field** (`docs/MANIFEST.md:48, :65-69`):
  ```toml
  [session.echo_commune]
  command = "cheap-model-invocation --id {id}"
  recursion_guard_env = "GUARD_VAR_NAME"  # optional but recommended
  keys = ["id"]
  ```
  - `command`: opaque — spt-core never parses the model/flags
  - `recursion_guard_env`: env var set on the summarizer child; the adapter's hooks check it to bail (prevent echo-of-echo) (`echo.rs:137-142` test)
  - `keys`: which substitution values spt-core must fill

#### Critical Contracts
1. **Input shape is opaque-raw** (`echo.rs:27`, `HistoryRecord`): the echo child sees verbatim records as emitted by the history subsystem's strategy. **If an adapter uses Path A (locate+normalize), the echo consumes whatever the normalizer emits.** No schema enforcement.

2. **Single writer of drop-file** (`echo.rs:90`, line comment): spt-core writes `<id>-commune.md` atomically; the mind never does. (KNOWN-HAZARD 6.4)

3. **Provenance stamping** (`echo.rs:83-85`): spt-core stamps `Source: echo-commune\n` header; the summarizer's body is appended unchanged.

4. **No injection channel guarantee** (`echo.rs:106`): the echo child is spawned via `runtime.run_bounded_stdin()` with no expectation of injection capability on this role — it is pure summarizer-in, drop-file-out.

#### Test Evidence
- `echo.rs:167-194`: mock adapter passes through history records with no validation; provenance stamped by spt-core
- `crates/spt/tests/contract_e2e.rs:77-214`: integration test loads mock manifest, spawns mock-session with mock echo_commune role

---

### 3. THE SESSION-DIGEST SEAM — Published Record Contract + Projection

This is **the most complex and newly re-founded (M9)** seam. Two orthogonal pieces:

#### Part A: The Published Digest-Record Contract (spt-core OWNED)
**Files:**
- `crates/spt-term/src/projection.rs:16-48` — the contract definition
- `docs/MANIFEST.md:113-150` — authoritative documentation
- `crates/spt-term/src/digest.rs:38-54` — output types (Digest, DigestEntry, ToolUse, Turn)

**The Contract Shape** (immutable):
```json
{"role": "input",  "text": "user input"}
{"role": "agent",  "text": "agent output"}
{"role": "tool",   "tool": {"name": "Write", "arg": "src/file.rs"}}
```
- `role`: closed set `input | agent | tool` (enforced by serde enum, `projection.rs:58-67`)
- `text`: input/agent span (empty/omitted for tool)
- `tool`: `{name, arg}` — present iff `role == "tool"`
- **Forward-compat:** unknown fields ignored; malformed lines skipped (not fatal) — `projection.rs:102-104`

**Extraction vs Presentation Split** (CRITICAL for adapter authors):
- **Extraction** (adapter's job): format each history line as the contract (JSON object)
- **Presentation** (spt-core's job): window depth (~3 turns), tool-arg truncation (~25 chars), tool sprint collapse — `crates/spt-term/src/digest.rs:22-27` `DigestConfig`

#### Part B: The Projection Pipeline (spt-core OWNED)
**Flow:**
1. **Source resolution** (`crates/spt-daemon/src/digest.rs:80-121` `project_endpoint_digest`):
   - Resolve adapter by perch's recorded `adapter_name` (`info.json`)
   - Try `[history]` strategy first (Path A locate+normalize / fetcher, or Path B native store)
   - Fall back to `digest.log` (the `api digest-entry` push sink) if no history strategy

2. **Record fetching** (`spt-daemon/digest.rs:90-121` `endpoint_digest_records`):
   - Calls `spt_live::fetch_history()` — all three history strategies
   - **Returns the opaque `.raw` strings** from HistoryRecord
   - **spt-daemon then parses them via the contract** (`spt-term::projection::project_lines`)

3. **Projection** (`crates/spt-term/src/projection.rs:170-176` `project_lines`):
   - Parse each line via `record_to_tagged()` (serde JSON deserialization)
   - Skip unparseable lines (forward-compat graceful degradation)
   - Fold parsed records into structured Turn/DigestEntry hierarchy
   - Apply window cutoff (keep last N turns)

#### Part C: Push Door for Log-less Adapters
**File:** `crates/spt/src/api/reporting.rs:256-289` `cmd_digest_entry`

**Command:** `spt api digest-entry <id>` (stdin carries one JSON record)

**Validation** (`reporting.rs:276-289` `ingest_digest_entry`):
- Parse JSON to validate it matches contract (serde deserialization)
- Reject if: empty, not valid JSON, unknown `role`, tool record missing `tool` object
- On success: append to `<perch>/digest.log`

**Storage:** `crates/spt-store/src/history.rs:34-36` `append_digest_entry` — **separate from history.log** (no cross-pollution)

**Test evidence** (`reporting.rs:728-752`): rejects unknown roles (`whisper`), malformed JSON, validates contract

---

### 4. KEY READINESS QUESTION: Can a Real Claude Code Adapter Feed Both Seams?

**SHORT ANSWER: YES, but with friction at the guidance boundary.**

#### Both Seams from the Same Source — How It Works

A Claude Code adapter's **per-session JSONL** can be Path A:
```toml
[history]
strategy = "locate_normalize"
locate_template = "~/.claude/projects/{session_id}.jsonl"
normalize_command = "claude-spt-normalize"  # reads CC's JSONL, emits contract JSON
```

**What happens:**
1. **History subsystem** (`fetch_history`): locates the JSONL, pipes to normalizer, gets back opaque HistoryRecord lines
2. **Echo-commune** (`run_echo_commune`): feeds those opaque lines unchanged to cheap-model summarizer
3. **Session digest** (`project_endpoint_digest`): fetches same opaque lines, **parses them as the digest contract** via projection

**Critical:** The normalizer's output must simultaneously satisfy:
- Being opaque to echo (the echo child just pipes them through)
- Being parseable as the digest-record contract (valid JSON with `role`, `text`, `tool`)

#### The Tension: FOUND

**History seam philosophy:** Records are opaque; spt-core carries `.raw` verbatim; adapter owns the schema.

**Digest seam requirement:** Records must parse to the published contract; unknown roles/tool shapes cause projection to skip.

**In practice:**
- If an adapter's normalizer emits **anything other than valid contract JSON**, the digest **silently skips those lines** (`projection.rs:102-104`, test `contract_e2e.rs` does NOT cover digest yet).
- If the adapter intentionally emits diverse schemas (some contract, some adapter-specific), the digest sees only the contract lines.
- **No error, no feedback to the adapter author** — projection just drops invalid records.

#### Friction Points

1. **Documentation clarity (MINOR):**
   - `docs/MANIFEST.md:113-150` defines the digest-record contract clearly
   - **But:** no guidance doc on how to write a normalizer that emits contract JSON while keeping it opaque to echo
   - **Gap:** adapter authors must infer the contract is the only way to make digest work; if they emit anything else, digest silently fails

2. **Validation timing (MODERATE):**
   - `api digest-entry` validates records at push time (`reporting.rs:276`)
   - But `[history]` strategies do NOT validate — spt-core trusts the adapter's normalizer output
   - **Gap:** an adapter using Path A (locate+normalize) won't discover digest contract violations until projection-time, and projection just skips
   - **Recommendation for adapter author:** test the normalizer output against the contract schema manually

3. **Topology gap (CLOSED):**
   - Original ADR-0008 said digest is only for spt-hosted endpoints (needs broker PTY)
   - **Amendment (2026-06-12, ADR-0008 §Amendment)** moved source to normalized logs → topology-independent
   - Claude Code adapter can use Path A (locate JSONL) — **diagram this is proven** but not yet tested in E2E fixtures
   - Mock adapter only exercises Path B (native store) so far

#### What's Proven TODAY

**Tested in fixtures:**
- `crates/spt-live/src/history.rs` tests (unit): all three strategies with trivial commands
- `crates/spt-live/src/echo.rs` tests (unit): opaque pass-through, guard env, drop-file write
- `crates/spt-term/src/projection.rs` tests (unit): projection from contract lines, window collapse, forward-compat skip
- `crates/spt-daemon/src/digest.rs` tests (unit): projection from both history and digest.log sources
- `crates/spt/src/api/reporting.rs` tests (unit): digest-entry validation + storage
- `crates/spt/tests/contract_e2e.rs` (integration): mock adapter full round-trip (seed→bind→message spool→poll)
  - **BUT:** does NOT test echo-commune or digest projection end-to-end; mock's echo_commune role is wired but never exercised in the flow

**NOT tested in fixtures (DEPTH gap):**
- A real normalizer emitting contract JSON; echo-commune consuming it; digest projecting the same records in one E2E
- Path A with a real file locate (only Path B native store tested in digest projection, `digest.rs:160-183`)
- Incremental history updates (file-watch freshness deferred per CONTEXT.md)

---

### 5. DELETION-TEST: Which Seams are Mock/Test-Only vs Production?

**MOCK/TEST-ONLY (hypothetical exercise):**
- `adapters/mock/manifest.toml`: the only adapter-shaped artifact in the repo (R-DOCS-2)
  - Uses Path B (native `strategy = "native"`) so adapter can push via `api history-log`
  - `[session.echo_commune]` declared but never invoked in `contract_e2e.rs`
  - `[history]` routes to native store; digest fixture (`digest.rs:160-183`) reads from `digest.log`
- Reason: mock avoids file I/O complexity; real adapters can omit this

**PRODUCTION-READY (seams live-tested or at unit level with real command integration):**
- `[history]` three strategies: unit tests with real cross-platform commands (`history.rs:174-197`, `two_line_cmd()`, `sleeper()`)
- Echo-commune bounded execution: unit test with real pass-through command
- Projection algorithm: unit tests on sample contract JSON, forward-compat skip verified
- `api digest-entry` door: unit test validates contract JSON, rejects bad records
- Drop-file write atomicity: unit test confirmed spt-core is single writer

**FRICTION: What's NOT E2E tested together:**
- Real normalizer (Path A) + echo consuming raw output + digest projecting the same records
- Multi-strategy chaining (e.g., locating a file, normalizing it, echoing its output, projecting digest)

---

### 6. CONCRETE BLOCKERS vs INCONVENIENCES FOR AN EXTERNAL ADAPTER AUTHOR

#### BLOCKERS (breaks the seam)
**None.** The seams are architecturally complete. A real adapter can feed all three TODAY.

#### INCONVENIENCES (friction, not fatal)

1. **Digest-record contract is documented but not discoverable from failure**
   - **Problem:** If an adapter's normalizer emits non-contract JSON, digest silently skips those lines. No validation error at normalize-time or history-fetch-time.
   - **Impact:** Adapter author discovers digest doesn't work by realizing `spt endpoint digest <id>` returns empty, then reads the spec.
   - **Mitigation (in adapter code):** validate normalizer output locally against the contract before shipping.

2. **No guidance on **opaque-line + contract-overlap** tension**
   - **Problem:** History system says "adapter owns the schema" (opaque); digest system says "must be valid contract JSON." The overlap is implicit, not explicit.
   - **Impact:** Adapter author writes a normalizer that works for echo (opaque pass-through) but isn't valid contract JSON; digest silently fails; no error.
   - **Mitigation:** docs/MANIFEST.md §Session digest should add a note: "If `[history]` is declared, normalized lines are consumed by both echo (opaque) and digest (contract-parsed). Emit valid JSON matching the digest-record contract for digest to work."

3. **Path A (locate+normalize) not E2E proven with real file I/O**
   - **Problem:** Mock adapter uses only Path B (native push); contract_e2e.rs doesn't exercise locate+normalize + echo + digest in one flow.
   - **Impact:** A real adapter author using Path A only discovers friction when building (e.g., template substitution keys, error handling).
   - **Mitigation:** Add a second E2E fixture using Path A with a real script (even if trivial) to prove the seam.

4. **Incremental history freshness is deferred**
   - **Problem:** CONTEXT.md "session digest" notes "autonomous file-watch freshness is deferred to the consuming-frontend milestone."
   - **Impact:** Digest snapshots are on-demand pull only; no real-time tail. Echo-commune is pull-only too (not subscribed).
   - **Expectation:** A Claude Code adapter should not expect live digest deltas without consuming-frontend arriving (M10 target).

---

### 7. FILE EVIDENCE SUMMARY (for cross-reference)

| Seam | Key File | Line | Finding |
|------|----------|------|---------|
| `[history]` localize | `manifest.rs` | 271-282 | Three strategies declared, Path A/B split clear |
| `HistoryRecord` opaque | `history.rs` | 30-35 | `.raw: String` — no parsing |
| History fetch | `history.rs` | 84-136 | All three strategies; bounded commands; native → resolve_perch_path |
| Echo input | `echo.rs` | 91-123 | Consumes `&[HistoryRecord]` raw, feeds stdin as opaque lines |
| Digest-record contract | `projection.rs` | 55-95 | Published contract: `{role, text, tool}` — closed set roles |
| Projection | `projection.rs` | 112-163 | Folds records → windows → collapses tools → truncates args |
| Digest from history | `daemon/digest.rs` | 80-121 | Resolves adapter → fetches via history seam → projects |
| Digest from push door | `daemon/digest.rs` | 116-120 | Falls back to `digest.log` if no history strategy |
| `api digest-entry` door | `reporting.rs` | 256-289 | Validates contract JSON, rejects bad records |
| Mock adapter | `adapters/mock/manifest.toml` | 77-79 | Path B only; `api echo_commune` declared but not exercised E2E |
| Timeout bound | `daemon/digest.rs` | 49 | 30s fetch timeout; commands are bounded |

---

### RECOMMENDATION FOR READINESS REVIEW

**Status: READY with documentation gap**

A real Claude Code adapter author **can implement today** using:
1. **Path A** (locate Claude's per-session JSONL, write a normalizer that emits digest-record JSON)
2. Feed the same normalized records to **echo-commune** (opaque pass-through)
3. Enable **session-digest** projection (spt-core will parse the same JSON for digest)

**Friction points are minor (documentation + E2E coverage) and do not block implementation.**

**Recommended before calling production-ready:**
1. Add a note in `docs/MANIFEST.md` §Session digest: "Normalized history lines feed both echo (opaque) and digest (contract-parsed). Emit valid digest-record JSON for digest to function."
2. Add a second E2E fixture (alongside mock) using Path A with a real script (even trivial) to prove cross-file locate+normalize+echo+project flow.
3. Document the `keys` substitution parameters available per strategy (currently scattered; a reference table in MANIFEST.md would help).

Prompt:
Explore the spt-core codebase (C:\Users\decid\Documents\projects\spt-core) and report on the CONTENT SEAMS a harness adapter feeds: the history subsystem, echo-commune, and the session-digest. This is for an architecture-readiness review ahead of building a real `spt-claude-code` adapter.

Read CONTEXT.md entries for "live activity buffer (session digest)", "echo commune", history subsystem, and docs/adr/0008* + docs/MANIFEST.md history section. Use CONTEXT.md exact vocabulary.

Focus:
1. The `[history]` seam: strategies (locate_normalize / native / fetcher). What exactly must an adapter provide (locate_template, normalize_command, etc.)? Files: crates/spt-live/src/history.rs. Cite file:line. Is HistoryRecord still opaque ("carries raw verbatim")?
2. The echo-commune consumer: crates/spt-live/src/echo.rs — how it pipes raw history to an adapter summarizer. What command/role must the adapter declare?
3. The session-digest content seam (just re-founded in M9): the PUBLISHED digest-record contract (`{role∈input|agent|tool, text, tool{name,arg}}`) — where is it defined + documented? crates/spt-term/src/projection.rs, crates/spt-term/src/digest.rs. The `api digest-entry` push door (crates/spt/src/api/reporting.rs, crates/spt-store/src/history.rs append_digest_entry → separate digest.log). The on-demand projector (crates/spt-daemon/src/digest.rs project_endpoint_digest).
4. KEY READINESS QUESTION: for a real Claude Code adapter whose per-session JSONL is the log source — can it feed BOTH echo-commune AND the digest from its logs through these seams TODAY? Where's the friction? Does the digest-record contract require the adapter's normalized lines to match a specific shape, and is that shape documented well enough for an external adapter author? Is there tension between echo (wants opaque raw) and digest (wants the {role,text,tool} contract) when both read the SAME [history] source?
5. Apply DEPTH + DELETION-TEST lens. Note where only MOCK/TEST adapters exercise these seams (hypothetical vs real seam).

Report concise: file:line evidence per finding, the friction, blocks-vs-inconveniences a real adapter author. Do NOT propose solutions — map the seams + gaps.


<owl_reachability_notice>
You have an active owl perch. Launch this Agent with run_in_background: true so you stay reachable for incoming messages while it works.
</owl_reachability_notice>