- **Source mechanism reconciled (M9, ADR-0008 amendment made real):** the PTY-parse engine is **retired** — the digest is now an **on-demand projection** of the endpoint's normalized records (`spt-term::projection` over the published digest-record contract; `spt-daemon::digest::project_endpoint_digest` resolves the `[history]` source or the `api digest-entry` `digest.log` sink). No manifest seam (`[pty_digest]` is gone); the digest rides `[history]`. **Deferred (tracked, no consumer yet):** the *autonomous file-watch freshness* nudge and the Option-C persistence re-home land with the consuming-frontend milestone (Shell M10 / GUI pane) that actually reads the live stream — today the delta-stream contract ships and its deltas are driven by pulls / `api digest-entry` pushes.

- **Adapter-declared extraction (post-M9 milestone — reverses M9's "no manifest seam"):** M9 retired the PTY-parse engine and made the digest an on-demand projection over a published `{role,text,tool}` contract carried in `[history]`. That under-served real adapters: one normalizer cannot serve echo (opaque) and the digest (contract) at once, and a contract mismatch failed **silently**. So the digest gets its **own `[digest]` manifest seam** — an **imperative extractor** the adapter declares (its native log → the `{role, text, tool, ts}` contract; reads the same source files as `[history]` by default, own-source escape hatch), with **`api digest-entry`** as the always-available push fallback. **No declarative DSL** (anyone who could conform to a fixed format could as easily push or write the extractor). **`spt adapter digest-proof`** lets an author validate their extractor's output; the daemon surfaces the same skip-diagnostics at runtime. The PTY-parse engine stays retired. (Successor to ADR-0008.)
- **Two-origin merge.** The digest interleaves two sources by `ts`: the adapter's **extracted log records** (agent activity) and spt's own **context-injection entries** (psyche download / echo mirror / owl message), which spt appends to the endpoint's `digest.log` (the existing Path-B sink — spt becomes an internal producer). The projection merges both newest-relevant within the window. **Deferred (no consumer yet):** the GUI collapse/expand of context-injection entries, the echo-commune-reads-the-digest delta loop, and the *autonomous file-watch freshness* nudge land with the milestones that own those surfaces; the data model (the `[digest]` seam, `ts`, the context-injection category, thread-spanning) lands now so the contract need not break again.


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

- **Adapter-declared extraction (post-M9 milestone — reverses M9's "no manifest seam"):** M9 retired the PTY-parse engine and made the digest an on-demand projection over a published `{role,text,tool}` contract carried in `[history]`. That under-served real adapters: one normalizer cannot serve echo (opaque) and the digest (contract) at once, and a contract mismatch failed **silently**. So the digest gets its **own `[digest]` manifest seam** — an **imperative extractor** the adapter declares (its native log → the `{role, text, tool, ts}` contract; reads the same source files as `[history]` by default, own-source escape hatch), with **`api digest-entry`** as the always-available push fallback. **No declarative DSL** (anyone who could conform to a fixed format could as easily push or write the extractor). **`spt adapter digest-proof`** lets an author validate their extractor's output; the daemon surfaces the same skip-diagnostics at runtime. The PTY-parse engine stays retired. (Successor to ADR-0008.)
- **Two-origin merge.** The digest interleaves two sources by `ts`: the adapter's **extracted log records** (agent activity) and spt's own **context-injection entries** (psyche download / echo mirror / owl message), which spt appends to the endpoint's `digest.log` (the existing Path-B sink — spt becomes an internal producer). The projection merges both newest-relevant within the window. **Deferred (no consumer yet):** the GUI collapse/expand of context-injection entries, the echo-commune-reads-the-digest delta loop, and the *autonomous file-watch freshness* nudge land with the milestones that own those surfaces; the data model (the `[digest]` seam, `ts`, the context-injection category, thread-spanning) lands now so the contract need not break again.

- **Thread-spanning across session boundaries.** A live agent's identity persists across many harness *sessions* (a `/clear` or `/compact` rotates the harness `session_id` — see `api boundary` / **post-spawn seam** — while the endpoint identity and `parent_pid` anchor hold). The digest follows the **agent thread**, not a single session: its rolling window may bridge a boundary (the tail of session N and the head of session N+1 coexist until the old turns age out), so a glance right after a `/clear` still shows the agent mid-thread rather than empty. A session boundary is itself **represented distinctively** in the digest (a visible boundary marker, e.g. a `/clear` divider), so a reader sees that context reset *there* without losing the thread. (Heir to the sister project's per-agent `sessions.log` boundary ledger.)

- **Two access modes.** **Snapshot pull** — `spt endpoint digest <[subnet:]id[@node]>` returns the current structured buffer (a Shell/CLI/GUI on-demand render; feeds the frontend's "latest session output" pane; a Gateway's agent-window). **Structured-delta stream** — a subscriber (live Shell pane, GUI, Gateway) receives only *changes* (new turn, new tool entry, collapse update), keyed to log records, for resource-efficient near-realtime reflection. **Access is address-gated** — fetch/subscribe is allowed for anyone who can address the endpoint (visible + resolvable per the resolution policy), the same gate as messaging.

- **"Option (C)" logging surface:** the digest may also be *persisted* (opt-in per-endpoint) as a coarse activity log appended to the native history store (Path B), on-boundary or periodic — off by default.

-- **Source mechanism reconciled (M9, ADR-0008 amendment made real):** the PTY-parse engine is **retired** — the digest is now an **on-demand projection** of the endpoint's normalized records (`spt-term::projection` over the published digest-record contract; `spt-daemon::digest::project_endpoint_digest` resolves the `[history]` source or the `api digest-entry` `digest.log` sink). No manifest seam (`[pty_digest]` is gone); the digest rides `[history]`. **Deferred (tracked, no consumer yet):** the *autonomous file-watch freshness* nudge and the Option-C persistence re-home land with the consuming-frontend milestone (Shell M10 / GUI pane) that actually reads the live stream — today the delta-stream contract ships and its deltas are driven by pulls / `api digest-entry` pushes.

+- **Adapter-declared extraction (post-M9 milestone — reverses M9's "no manifest seam"):** M9 retired the PTY-parse engine and made the digest an on-demand projection over a published `{role,text,tool}` contract carried in `[history]`. That under-served real adapters: one normalizer cannot serve echo (opaque) and the digest (contract) at once, and a contract mismatch failed **silently**. So the digest gets its **own `[digest]` manifest seam** — an **imperative extractor** the adapter declares (its native log → the `{role, text, tool, ts}` contract; reads the same source files as `[history]` by default, own-source escape hatch), with **`api digest-entry`** as the always-available push fallback. **No declarative DSL** (anyone who could conform to a fixed format could as easily push or write the extractor). **`spt adapter digest-proof`** lets an author validate their extractor's output; the daemon surfaces the same skip-diagnostics at runtime. The PTY-parse engine stays retired. (Successor to ADR-0008.)

+- **Two-origin merge.** The digest interleaves two sources by `ts`: the adapter's **extracted log records** (agent activity) and spt's own **context-injection entries** (psyche download / echo mirror / owl message), which spt appends to the endpoint's `digest.log` (the existing Path-B sink — spt becomes an internal producer). The projection merges both newest-relevant within the window. **Deferred (no consumer yet):** the GUI collapse/expand of context-injection entries, the echo-commune-reads-the-digest delta loop, and the *autonomous file-watch freshness* nudge land with the milestones that own those surfaces; the data model (the `[digest]` seam, `ts`, the context-injection category, thread-spanning) lands now so the contract need not break again.

_Avoid_: "PTY digest" (the superseded source mechanism); parsing agent content out of PTY bytes anywhere.


sugg1: agree. Q4 tail: agree. -sugg2: agree. Q5: agree with (ii). -yes, agree


Glossary captured. Now the ADR (you greenlit it). Find the next number + ADR-0008 to supersede.


Bash

ls C:/Users/decid/Documents/projects/spt-core/docs/adr/ 2>&1 | tail -12; echo "=== ADR-0008 header ==="; head -20 C:/Users/decid/Documents/projects/spt-core/docs/adr/0008-*.md 2>&1

List ADRs + read 0008 header


0007-subnet-notification-primitive.md
0008-live-activity-buffer-pty-digest.md
0009-endpoint-access-control-model.md
0010-immutable-home-subnet-fork-not-rehome.md
0011-branchstore-git-kv-state-substrate.md
0012-psyche-trust-boundary-stdout-event-outbound.md
0013-psyche-reconciled-context-merge.md
0014-publish-target-repo-and-lapse-proof-canonical-url.md
0015-release-signing-two-key-trust-anchor.md
0016-platform-targeted-update-sets.md
0017-subnet-mesh-membership-seed-proof.md
0018-broker-brain-process-isolation-restoration.md
=== ADR-0008 header ===
# Live activity buffer (PTY digest)

## Status

accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records (the implementation debt below is discharged)

## Context

We want a rolling, human-glanceable "what is this agent doing right now" view — the last ~N user turns + agent output between, with tool-usage sprints collapsed to a single `used: …` line — that a Shell, CLI, or GUI pane can render. The obvious implementation (spt-core parses the live PTY byte stream) looks like it **contradicts the history-subsystem decision** (CONTEXT §history subsystem), which deliberately refused to build per-harness parsers ("unbounded, version-fragile maintenance burden") and pushed parsing into adapters (Path A) or a native store the adapter writes (Path B). A future reader would reasonably flag the apparent reversal. This ADR records why it is *not* one, and the shape chosen.

## Decision

Add a **live activity buffer (PTY digest)** as a **distinct artifact** — not scrollback (raw bytes, R-TERM-2), not transcript history (full conversation behind communes/resume, Paths A/B). Its purpose is at-a-glance current activity, not replay.

The parsing-boundary distinction that resolves the tension:
- **The adapter supplies the patterns; spt-core only runs them.** The manifest provides `input_pattern` + `agent_pattern` (each opens a span capturing everything, newlines included, until the next pattern match of any kind) and `tool_pattern` (a **list** — one per tool, plus a final **catchall** for user-installed tools, capturing tool-name + arg fragment). This is exactly Path A's "adapter owns the parser" rule and the "spt-core owns the mechanism, adapter owns the pattern" principle (identical to opaque command templates) — **not** a built-in per-harness parser. The rejected maintenance burden stays in the adapter.
- **It parses the live PTY byte stream, not the harness transcript.** A light rolling regex over bytes the broker already holds is a categorically different artifact from locating + parsing version-fragile harness transcript files (the thing Path A/B exist to avoid).

Shape:
- **Broker-PTY-dependent → spt-hosted-primary.** Needs the broker-owned PTY (topology 2). **Harness-hosted (topology 1) is capability-gated:** an adapter that can feed spt-core the stream (e.g. a `pi`-style harness) opts in via a declared capability; harnesses that own their process tree opaquely (Claude Code) cannot, and the digest is simply unavailable there.


Next is ADR-0019; it supersedes ADR-0008's source-mechanism lineage. Writing it.


Write

# Digest rides an adapter-declared `[digest]` extractor (session-spanning, two-origin)

## Status

accepted (2026-06-13) · **supersedes the source-mechanism lineage of [ADR-0008](0008-live-activity-buffer-pty-digest.md)** (its 2026-06-01 PTY-pattern decision, the 2026-06-12 "normalized logs" amendment, and the M9 "no manifest seam / rides `[history]`" reconciliation). The **product surface** defined in ADR-0008 (a rolling, glanceable activity view distinct from scrollback and transcript) still stands and is broadened here.

## Context

M9 reconciled the digest onto the log source and, in its plan §2, chose "approach A": a single published `{role,text,tool}` contract carried in the adapter's `[history]` records, **no digest-specific manifest seam** — the adapter's existing `[history]` normalizer was expected to emit the contract, and the v0.5.0 release notes told adapter authors exactly that. This decision was made inside the milestone and **was not approved by the operator**, who reopened it on review. Four problems are real:

1. **Dual-consumer conflict.** One `[history]` source feeds *two* consumers with opposite needs: the **echo-commune** pipes records *opaque and verbatim* to a summarizer (it wants full-fidelity records), while the **digest** needs them to *parse* as the contract. One normalizer cannot serve both — emit rich records and the digest silently drops every line; emit contract records and echo loses fidelity.
2. **Silent failure.** `spt_term::projection::record_to_tagged` is `serde_json::from_str(raw).ok()` — any line that is not valid contract JSON is dropped with no count, sample, or log. A normalizer that does not exactly match the contract yields an **empty digest with zero signal**; the author's only symptom is "`spt endpoint digest` returns nothing."
3. **No thread continuity.** A perch's `session_id` is pinned at first bind (`establish_perch` refuses a change while the owner is alive); `[history]` `locate_template` resolves a *single* `{session_id}` file. A live agent that `/clear`s (same process, new session, new log file) rotates `session_id` via `api boundary`, but the digest then reads only the new session — it **follows** the thread but cannot **span** it, going near-empty at every boundary.
4. **Blind to injected context.** The digest showed only what the agent *did*, not what spt *fed* it (session-start psyche download, echo-commune mirror, incoming owl messages). An echo-commune building its next context delta benefits from seeing what context the agent already has, so it does not re-summarize it.

A harness's real log shape forces the design: a sampled Claude Code session JSONL carries nine top-level record `type`s (most are UI/metadata noise), `assistant` records hold a `content` **list** mixing `thinking` + `text` + `tool_use` (one line → multiple digest entries, `thinking` excluded), and `user.content` is sometimes a string and sometimes a tool-result list. Extraction means filter-types + walk-array + branch-per-block + skip-thinking — not a flat field map. Every record also carries a `timestamp`, making cross-origin interleave feasible.

## Decision

The digest gets its **own `[digest]` manifest seam**. Specifically:

- **Imperative extractor, not a declarative DSL.** `[digest]` declares an adapter-supplied command that maps the harness's native log → the `{role, text, tool, ts}` contract. By default it reads the **same source files as `[history]`** (reference its locate pattern; DRY) with an **own-source escape hatch**; **`api digest-entry`** remains the always-available push fallback. We reject a declarative match/extract DSL: a flat map cannot express real nested logs, and a DSL powerful enough to do so reinvents a programming language — and anyone who *could* conform their output to a fixed format could as cheaply push via `api digest-entry` or write the extractor. (This corrects M9 §2, which rejected a digest-specific command only because it wrongly assumed echo's one normalizer could also serve the digest.)
- **Thread-spanning.** A per-endpoint **session ledger** (heir to the sister project's `psyches/tracked/agents/<id>/sessions.log`) records the agent's session rotations; `api boundary` appends the new `session_id` at each `/clear` / `/compact`. The digest enumerates the last *K* sessions (newest-first, bounded), so the rolling window **bridges** a boundary. A session boundary is **rendered distinctively** (a visible `/clear` divider) so a reader sees the context reset without losing the thread.
- **Presentation is adapter-defaulted, consumer-overridable.** Window depth, arg-truncation, and sprint-collapse become `[digest]` defaults the adapter declares and any consumer may override at pull/subscribe; spt-core ships fallback defaults. The fixed "last ~3 turns" is dropped as a spt-core requirement. Extraction stays the adapter's; *how much* is shown stops being spt-core dogma.
- **Two-origin merge.** The digest interleaves, by `ts`, the adapter's **extracted log records** (agent activity) and spt's own **context-injection entries** — a new collapsible category with subtypes `psyche_download | echo_mirror | owl_message`. spt appends its injection entries to the endpoint's `digest.log` (the existing Path-B sink — spt becomes an internal producer); the projection merges both within the window. The record contract gains an optional `ts` ordering key.
- **Diagnostics are first-class.** `spt adapter digest-proof <adapter> [--sample <log>]` runs the declared extractor over a real sample and prints the parsed records, the rendered digest, and every dropped/malformed line with its reason. The daemon surfaces the same skip-diagnostics at runtime. The silent drop is removed.

**Deferred (no consumer yet, by the same logic M9 used):** the GUI collapse/expand UX for context-injection entries, the echo-commune-reads-the-digest delta loop, and the autonomous file-watch freshness nudge land with the milestones that own those surfaces. The **data model** (the `[digest]` seam, `ts`, the context-injection category, thread-spanning) lands now so the adapter-facing contract need not break a second time.

## Consequences

- **Breaking for adapter authors** (again): the M9 guidance ("emit the contract through your `[history]` normalizer") is replaced by "declare a `[digest]` extractor (or push via `api digest-entry`)." The v0.5.0 release note that announced the opposite is wrong and must be corrected; the **harness integration checklist** in the docs is updated as part of this milestone.
- `digest.log` changes role from a *fallback source* to a *merge input* (it now also carries spt's injection entries).
- New `REQ-*` ids must be minted before implementation (traceability rule 3); the relevant `REQ-TERM-*` titles/evidence re-point in the same restoration discipline M9 used.
- ADR-0008's source mechanism is now fully superseded; only its product-surface framing survives (and is broadened from "recent activity" to "recent context").

## Alternatives considered

- **Declarative detection DSL** in the manifest — rejected: too weak for real nested logs; a sufficiently powerful version is a reinvented language; the push door already covers the simple case.
- **One extractor shared with echo (M9 approach A)** — rejected: the dual-consumer conflict above; it is the decision being reversed.
- **Extend `[history]` instead of a new `[digest]` section** — rejected: `[history]` resolves a single session file; bolting multi-session spanning onto it re-tangles the two consumers we are separating.
- **Follow-and-reset window** (digest shows only the post-`/clear` session) — rejected: loses thread continuity, the whole point of session-spanning; a glance right after `/clear` would show an idle agent mid-task.
- **Build all of the context-mirroring feature now** (GUI + echo-feedback) — rejected: builds consumers whose surfaces do not exist yet; only the data model is expensive to retrofit, so only it lands now.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs\adr\0019-digest-adapter-declared-extractor-session-spanning.md (file state is current in your context — no need to Read it back)

# Digest rides an adapter-declared `[digest]` extractor (session-spanning, two-origin)

## Status

accepted (2026-06-13) · **supersedes the source-mechanism lineage of [ADR-0008](0008-live-activity-buffer-pty-digest.md)** (its 2026-06-01 PTY-pattern decision, the 2026-06-12 "normalized logs" amendment, and the M9 "no manifest seam / rides `[history]`" reconciliation). The **product surface** defined in ADR-0008 (a rolling, glanceable activity view distinct from scrollback and transcript) still stands and is broadened here.

## Context

M9 reconciled the digest onto the log source and, in its plan §2, chose "approach A": a single published `{role,text,tool}` contract carried in the adapter's `[history]` records, **no digest-specific manifest seam** — the adapter's existing `[history]` normalizer was expected to emit the contract, and the v0.5.0 release notes told adapter authors exactly that. This decision was made inside the milestone and **was not approved by the operator**, who reopened it on review. Four problems are real:

1. **Dual-consumer conflict.** One `[history]` source feeds *two* consumers with opposite needs: the **echo-commune** pipes records *opaque and verbatim* to a summarizer (it wants full-fidelity records), while the **digest** needs them to *parse* as the contract. One normalizer cannot serve both — emit rich records and the digest silently drops every line; emit contract records and echo loses fidelity.
2. **Silent failure.** `spt_term::projection::record_to_tagged` is `serde_json::from_str(raw).ok()` — any line that is not valid contract JSON is dropped with no count, sample, or log. A normalizer that does not exactly match the contract yields an **empty digest with zero signal**; the author's only symptom is "`spt endpoint digest` returns nothing."
3. **No thread continuity.** A perch's `session_id` is pinned at first bind (`establish_perch` refuses a change while the owner is alive); `[history]` `locate_template` resolves a *single* `{session_id}` file. A live agent that `/clear`s (same process, new session, new log file) rotates `session_id` via `api boundary`, but the digest then reads only the new session — it **follows** the thread but cannot **span** it, going near-empty at every boundary.
4. **Blind to injected context.** The digest showed only what the agent *did*, not what spt *fed* it (session-start psyche download, echo-commune mirror, incoming owl messages). An echo-commune building its next context delta benefits from seeing what context the agent already has, so it does not re-summarize it.

A harness's real log shape forces the design: a sampled Claude Code session JSONL carries nine top-level record `type`s (most are UI/metadata noise), `assistant` records hold a `content` **list** mixing `thinking` + `text` + `tool_use` (one line → multiple digest entries, `thinking` excluded), and `user.content` is sometimes a string and sometimes a tool-result list. Extraction means filter-types + walk-array + branch-per-block + skip-thinking — not a flat field map. Every record also carries a `timestamp`, making cross-origin interleave feasible.

## Decision

The digest gets its **own `[digest]` manifest seam**. Specifically:

- **Imperative extractor, not a declarative DSL.** `[digest]` declares an adapter-supplied command that maps the harness's native log → the `{role, text, tool, ts}` contract. By default it reads the **same source files as `[history]`** (reference its locate pattern; DRY) with an **own-source escape hatch**; **`api digest-entry`** remains the always-available push fallback. We reject a declarative match/extract DSL: a flat map cannot express real nested logs, and a DSL powerful enough to do so reinvents a programming language — and anyone who *could* conform their output to a fixed format could as cheaply push via `api digest-entry` or write the extractor. (This corrects M9 §2, which rejected a digest-specific command only because it wrongly assumed echo's one normalizer could also serve the digest.)
- **Thread-spanning.** A per-endpoint **session ledger** (heir to the sister project's `psyches/tracked/agents/<id>/sessions.log`) records the agent's session rotations; `api boundary` appends the new `session_id` at each `/clear` / `/compact`. The digest enumerates the last *K* sessions (newest-first, bounded), so the rolling window **bridges** a boundary. A session boundary is **rendered distinctively** (a visible `/clear` divider) so a reader sees the context reset without losing the thread.
- **Presentation is adapter-defaulted, consumer-overridable.** Window depth, arg-truncation, and sprint-collapse become `[digest]` defaults the adapter declares and any consumer may override at pull/subscribe; spt-core ships fallback defaults. The fixed "last ~3 turns" is dropped as a spt-core requirement. Extraction stays the adapter's; *how much* is shown stops being spt-core dogma.
- **Two-origin merge.** The digest interleaves, by `ts`, the adapter's **extracted log records** (agent activity) and spt's own **context-injection entries** — a new collapsible category with subtypes `psyche_download | echo_mirror | owl_message`. spt appends its injection entries to the endpoint's `digest.log` (the existing Path-B sink — spt becomes an internal producer); the projection merges both within the window. The record contract gains an optional `ts` ordering key.
- **Diagnostics are first-class.** `spt adapter digest-proof <adapter> [--sample <log>]` runs the declared extractor over a real sample and prints the parsed records, the rendered digest, and every dropped/malformed line with its reason. The daemon surfaces the same skip-diagnostics at runtime. The silent drop is removed.

**Deferred (no consumer yet, by the same logic M9 used):** the GUI collapse/expand UX for context-injection entries, the echo-commune-reads-the-digest delta loop, and the autonomous file-watch freshness nudge land with the milestones that own those surfaces. The **data model** (the `[digest]` seam, `ts`, the context-injection category, thread-spanning) lands now so the adapter-facing contract need not break a second time.

## Consequences

- **Breaking for adapter authors** (again): the M9 guidance ("emit the contract through your `[history]` normalizer") is replaced by "declare a `[digest]` extractor (or push via `api digest-entry`)." The v0.5.0 release note that announced the opposite is wrong and must be corrected; the **harness integration checklist** in the docs is updated as part of this milestone.
- `digest.log` changes role from a *fallback source* to a *merge input* (it now also carries spt's injection entries).
- New `REQ-*` ids must be minted before implementation (traceability rule 3); the relevant `REQ-TERM-*` titles/evidence re-point in the same restoration discipline M9 used.
- ADR-0008's source mechanism is now fully superseded; only its product-surface framing survives (and is broadened from "recent activity" to "recent context").

## Alternatives considered

- **Declarative detection DSL** in the manifest — rejected: too weak for real nested logs; a sufficiently powerful version is a reinvented language; the push door already covers the simple case.
- **One extractor shared with echo (M9 approach A)** — rejected: the dual-consumer conflict above; it is the decision being reversed.
- **Extend `[history]` instead of a new `[digest]` section** — rejected: `[history]` resolves a single session file; bolting multi-session spanning onto it re-tangles the two consumers we are separating.
- **Follow-and-reset window** (digest shows only the post-`/clear` session) — rejected: loses thread continuity, the whole point of session-spanning; a glance right after `/clear` would show an idle agent mid-task.
- **Build all of the context-mirroring feature now** (GUI + echo-feedback) — rejected: builds consumers whose surfaces do not exist yet; only the data model is expensive to retrofit, so only it lands now.


Edit

accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records (the implementation debt below is discharged)

accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired · **source mechanism fully superseded by [ADR-0019](0019-digest-adapter-declared-extractor-session-spanning.md) (2026-06-13)** — the M9 "no manifest seam / rides `[history]`" stance is reversed; the digest gets its own adapter-declared `[digest]` extractor, thread-spanning, and a two-origin merge. Only this ADR's product-surface framing survives (broadened from "recent activity" to "recent context").


<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	# Live activity buffer (PTY digest)
2
3	## Status
4
5	accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records (the implementation debt below is discharged)
6

# Live activity buffer (PTY digest)

## Status

accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records (the implementation debt below is discharged)


accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records (the implementation debt below is discharged)

accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records · **source mechanism FULLY superseded by [ADR-0019](0019-digest-adapter-declared-extractor-session-spanning.md) (2026-06-13)** — the M9 "no manifest seam / rides `[history]`" stance is reversed: the digest gets its own adapter-declared `[digest]` extractor, thread-spanning, and a two-origin (activity + injected-context) merge. Only this ADR's product-surface framing survives (broadened from "recent activity" to "recent context").


The file C:\Users\decid\Documents\projects\spt-core\docs\adr\0008-live-activity-buffer-pty-digest.md has been updated successfully. (file state is current in your context — no need to Read it back)

# Live activity buffer (PTY digest)

## Status

accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records (the implementation debt below is discharged)

## Context

We want a rolling, human-glanceable "what is this agent doing right now" view — the last ~N user turns + agent output between, with tool-usage sprints collapsed to a single `used: …` line — that a Shell, CLI, or GUI pane can render. The obvious implementation (spt-core parses the live PTY byte stream) looks like it **contradicts the history-subsystem decision** (CONTEXT §history subsystem), which deliberately refused to build per-harness parsers ("unbounded, version-fragile maintenance burden") and pushed parsing into adapters (Path A) or a native store the adapter writes (Path B). A future reader would reasonably flag the apparent reversal. This ADR records why it is *not* one, and the shape chosen.

## Decision

Add a **live activity buffer (PTY digest)** as a **distinct artifact** — not scrollback (raw bytes, R-TERM-2), not transcript history (full conversation behind communes/resume, Paths A/B). Its purpose is at-a-glance current activity, not replay.

The parsing-boundary distinction that resolves the tension:
- **The adapter supplies the patterns; spt-core only runs them.** The manifest provides `input_pattern` + `agent_pattern` (each opens a span capturing everything, newlines included, until the next pattern match of any kind) and `tool_pattern` (a **list** — one per tool, plus a final **catchall** for user-installed tools, capturing tool-name + arg fragment). This is exactly Path A's "adapter owns the parser" rule and the "spt-core owns the mechanism, adapter owns the pattern" principle (identical to opaque command templates) — **not** a built-in per-harness parser. The rejected maintenance burden stays in the adapter.
- **It parses the live PTY byte stream, not the harness transcript.** A light rolling regex over bytes the broker already holds is a categorically different artifact from locating + parsing version-fragile harness transcript files (the thing Path A/B exist to avoid).

Shape:
- **Broker-PTY-dependent → spt-hosted-primary.** Needs the broker-owned PTY (topology 2). **Harness-hosted (topology 1) is capability-gated:** an adapter that can feed spt-core the stream (e.g. a `pi`-style harness) opts in via a declared capability; harnesses that own their process tree opaquely (Claude Code) cannot, and the digest is simply unavailable there.
- **Structured, source-tagged output** (input / agent / collapsed-tool entries) so consumers render richly. Window depth N (~3) is a config knob; tool-arg truncation (~25 chars) is spt-core-controlled *presentation* (the template controls *extraction*).
- **Two access modes over one transport.** The digest is a parsed projection of the same PTY stream the broker streams for R-TERM-3, so: **snapshot pull** (`spt digest <[subnet:]id[@node]>`) and a **broker-pushed structured-delta stream** (subscribe; pushes only changes for near-realtime reflection). The delta-stream is a structured overlay riding the **same R-TERM-3 byte-stream substrate** (raw vs parsed = two channels on the one session surface). Access is **address-gated** (same as messaging; respects ADR-0006 visibility).
- **Optional "Option (C)" persistence:** opt-in per-endpoint, append digests to the native history store (Path B) as a coarse activity log — a third logging surface, off by default, not a replacement for A/B.

## Consequences

- New v1 requirement to register: the live activity buffer + the `input_pattern`/`agent_pattern`/`tool_pattern[]` manifest seam + `spt digest` (pull) + the digest delta-stream (push). Lands with **M3** (needs the spt-term broker + PTY); Shell consumption is M5; the GUI pane is the frontend milestone; persistence is M3+.
- The manifest schema (`docs/MANIFEST.md`) gains the `pty_digest` seam; the capability-declaration seam gains the harness-hosted stream-feed opt-in.
- Does **not** reverse the history-subsystem decision — the no-built-in-parser rule still holds; the digest honors it by running adapter-supplied patterns over the live byte stream rather than parsing harness transcripts.
- Builds on R-TERM-1/2/3 (broker, session surface, byte-stream) and ADR-0006 (address-gating).

## Amendment — source mechanism superseded: normalized session logs, never PTY parsing (2026-06-12)

**What changes:** the digest's *source*. The original decision parsed the live PTY byte stream; the digest is now a **rolling tail-projection of the endpoint's normalized session logs** — the same normalized history the echo-commune consumes (history subsystem Path A locate+normalize / Path B native store). One pipeline, two consumers. Harnesses with no usable logs push entries directly via a new **`api digest-entry`** verb.

**Why (the Gateway grill, 2026-06-12):** the PTY byte stream of an alternate-screen TUI is a **rendering protocol, not a content log**. Scroll and resize trigger full repaints — the same content re-enters the stream and corrupts delta computation — and semantic turn boundaries drown in repaint traffic. The original §Decision rationale ("a light rolling regex over bytes ≠ fragile transcript parsing") proved **backwards in practice**: the byte stream is the *more* fragile surface, and transcript parsing carries **zero new burden** because Paths A/B already pay for it — the digest just consumes their output. The governing split: **render surfaces read the PTY** (R-TERM raw byte ring, unchanged); **content surfaces read the logs**.

**What stands (the product surface, unchanged):** the formula (N-turn window, collapsed tool sprints, presentation-vs-extraction split), structured source-tagged output, both access modes (snapshot pull + structured-delta stream, deltas now keyed to log records), address-gating, Option-C persistence. The no-built-in-parser rule **still holds** — normalization remains adapter-owned (Path A) or adapter-pushed (Path B).

**Improved consequence:** the digest becomes **topology-independent** — harness-hosted endpoints (Claude Code's per-session JSONL is Path A's worked example) get digests too; the original "unavailable under opaque harnesses" limitation dissolves. Freshness = file-watch + adapter hook nudges; a near-realtime **incremental** normalize story is design-phase work for the implementing milestone.

**Implementation debt recorded:** `spt-daemon::digest` (`DigestEngine` + the manifest `pty_digest` pattern seam) implements the superseded shape; reconciliation + the `docs/MANIFEST.md` schema revision land with the implementing milestone.

**Debt discharged (M9, 2026-06-13):** the PTY-parse engine (`spt-term`'s `DigestParser` + `spt-daemon`'s byte-feed `DigestEngine`) and the `[pty_digest]` manifest seam are **retired**. The digest is now an **on-demand projection** of the endpoint's normalized session-log records: `spt-term::projection` folds the published **digest-record contract** (the spt-core-owned `{role, text, tool}` overlay an adapter targets in its `[history]` lines, or pushes via `api digest-entry`) into the same structured `Digest`; `spt-daemon::digest::project_endpoint_digest` projects on demand at the snapshot-pull / reproject handlers and `DigestHub::project_and_publish` drives the delta-stream. Topology-independent (a Claude-Code-hosted endpoint's Path A JSONL projects with no broker PTY — proven by the CC-fixture E2E). The **autonomous file-watch freshness nudge** and **Option-C persistence re-home** are deferred to the consuming-frontend milestone (`docs/DEFERRED.md`) — the delta-stream contract ships, driven by pull / `api digest-entry` publishes. REQ-TERM-4 re-titled + its evidence re-pointed onto the projection in the same change.

-accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records (the implementation debt below is discharged)

+accepted (2026-06-01) · **source mechanism superseded by the 2026-06-12 amendment below** (product surface stands) · **implementation reconciled onto the log source in M9 (2026-06-13)** — the PTY-parse engine + `pty_digest` seam are retired; the digest is an on-demand projection of normalized session-log records · **source mechanism FULLY superseded by [ADR-0019](0019-digest-adapter-declared-extractor-session-spanning.md) (2026-06-13)** — the M9 "no manifest seam / rides `[history]`" stance is reversed: the digest gets its own adapter-declared `[digest]` extractor, thread-spanning, and a two-origin (activity + injected-context) merge. Only this ADR's product-surface framing survives (broadened from "recent activity" to "recent context").


Docs captured: glossary rewritten (digest = recent *context*, two-origin, adapter-tuned, thread-spanning), **ADR-0019** written, ADR-0008 marked fully superseded.

Now the four candidates you asked to fold in, mapped against the design we just built:

| # | Original candidate | Disposition in this milestone |
|---|---|---|
| **01** register-time validation | **IN — generalized.** Validate `[digest]` fields at `adapter add` (extractor present, source pattern resolvable, knob types) — same move as my original 01 (`keys ⊆ SUBSTITUTION_KEYS`). Both are "catch the manifest contract at register, not spawn." Cheap. |
| **02** digest diagnostic | **IN — elevated to load-bearing.** Became `spt adapter digest-proof` (author-time) + runtime skip-diagnostics. It's now *mandatory*, not nice-to-have: since we can't validate the extractor's *output* at register, this is the only thing standing between an author and a silently-empty digest. |
| **03** dual-consumer E2E | **IN — it's the milestone's headline test.** One source → echo (opaque) + digest (contract) + two-origin merge + spanning across a `/clear`. The realest fixture we can write. |
| **04** Psyche M2b→M3b | **OUT — orthogonal.** This milestone touches the session *ledger* and `api boundary` (live-agent lifecycle), but not Psyche *hosting*. 04 stays a separate decision for the daemon/live-agent milestone. |

**Milestone scope (crisp):**

*In* — `[digest]` section + register validation · imperative extractor + `api digest-entry` push · session ledger + `api boundary` append + thread-spanning enumeration + distinctive boundary marker · presentation knobs adapter-default/consumer-override (drop fixed ~3) · contract gains `ts` + context-injection category · two-origin merge (spt taps psyche/echo/spool → append to `digest.log`) · `digest-proof` + runtime diagnostics · E2E · `REQ-TERM-4` re-point + new REQs · docs: **correct the v0.5.0 release note** (your original grievance — it's now false), MANIFEST `[digest]` section, **harness integration checklist**.

*Deferred (no consumer yet)* — GUI collapse/expand of injection entries · echo-reads-digest delta loop · autonomous file-watch freshness · candidate 04.

**My recs on the small forks** (veto any; otherwise planning inherits them):
- **Ledger:** `<perch>/sessions.log` JSONL `{ts, session_id, trigger: boot|clear|compact}`, appended by `establish_perch` (first bind) + `api boundary`; bound *K* by config. Direct heir to legacy.
- **Boundary marker:** a `DigestEntry::Boundary{kind, ts}` variant so it renders inline in the timeline and a GUI can style it.
- **Context-injection entries:** the adapter extractor *only ever* emits activity records `{role∈input|agent|tool}` — unchanged for adapters. The context category is **spt-owned**, written by spt to `digest.log` as a distinct tagged record `{kind∈psyche_download|echo_mirror|owl_message, body, ts}`. Keeps the adapter contract minimal; spt owns its own entries.

Next step after you confirm: mint the `REQ-*` ids (traceability rule 3 — before any code), then `/gsd:plan-phase` or hand to todlando.

Two questions: do the candidate dispositions + scope look right? And do you want to keep grilling the small forks, or are we converged enough to move to REQ-minting?


note that we shouldn't "correct the 0.5.0 release note" in the changelog. the next release's changelog will simply point out its supersession as coinciding with that release. -agree all recs --- proceed to mint the REQs and write the JIT plan for this milestone. note this project does not use GSD workflows.


<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.


Noted — changelog is append-only; the **next** release's changelog notes the supersession, no retro-edit of v0.5.0. Correcting that scope item.

Proceeding: mint REQs (registry-first, rule 3) + write the JIT plan, no GSD. First I need the milestone's place in the roadmap and the REQ-TERM registry shape.


Bash

echo "=== ROADMAP milestones (M9+ context) ==="; grep -niE "^#+ |milestone|^\| M[0-9]|lecturn|M10|M9" C:/Users/decid/Documents/projects/spt-core/ROADMAP.md 2>&1 | head -40

Read ROADMAP milestone structure


=== ROADMAP milestones (M9+ context) ===
1:# spt-core Roadmap
5:## End goal
9:## Scope decisions (locked)
15:## Path to fruition (sequence)
23:**Stage D — Phased build (M0–M5).** Dependency-ordered, each milestone ships something usable.
25:## Milestones (all v1)
29:- **M2 — Harness contract + lifecycle.** **Strictly harness-agnostic — zero Claude Code conventions in this repo.** spt-core defines the adapter contract; the only adapter-shaped artifact in-tree is a generic mock/test adapter (manifest fixture + fake `api` caller) that exercises the contract (PRD R-DOCS-2 dev-agent quickstart). **The rebuilt `spt` plugin (Claude Code) is a SEPARATE downstream project** built atop these crates/binary — spt-core v1's acceptance proof (PRD §"feature parity"), not an spt-core deliverable. It lives and builds outside this repo. **Split into two JIT milestones** (decision 2026-06-01):
37:  - **M3 COMPLETE** (M3a + M3b + M3c all ✅, 2026-06-03): the real ADR-0004 architecture is live — native PTY hosting, the consolidated broker/brain daemon, and seamless signed self-update. Next milestone: M4.
41:## Post-v1-core milestones
49:- **🔜 NEXT milestone (planned 2026-06-12) — M9: Adapter customization & session surfaces.** Product of the two-day **Gateway grill** (operator + `doyle`; CONTEXT.md mints @ `8dc22ff`/`bd469b9`; ADR-0008 amended; the lecturn device design parked in the sibling `spt-lecturn/` repo for a later build). Shell-free, independently-shippable features: **adapter profiles** (sparse leaf-replace overlays on the parent manifest; shipped + local sources, local surviving adapter updates; composite `<adapter>:<profile>` addressing; tighten-only consent floors; `create-profile`/`delete-profile`) · **adapter strings** (`[strings]` manifest KV, overlay-resolved, data-only; `get-string`/`set-string`) · **keyword hints** (`[hints]` + `spt api hint`, once-per-session, one per message — adapters teach their surface exactly-when-relevant) · **`user-msg` type** (user-authority messages, identity-gated per the KH 7.3/7.5 posture; the daemon re-stamps impostors) · **Gateway endpoint-type acceptance** (the open type system honoring a human-backed downstream type end-to-end) · **`live-role.md`** (durable broad-purpose statement in `tracked/`, injected first at start transitions, `spt endpoint role` the sole writer) · the **session-digest log-source revision** (ADR-0008 amendment: digest = a projection of normalized session logs, never PTY parsing; `api digest-entry`; REQ-TERM-4 re-point — restoration-style honest re-evidence). Task breakdown: [`M9-PLAN.md`](./M9-PLAN.md). First beneficiaries: `spt-claude-code` (profiles/hints/digest are its enablers) and the future lecturn build.
50:- **Queued after M9 (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped; this adds the **drive channel** (owner→shell ephemeral latest-wins continuous control), the **shell tunnel** (reliable-ordered link-bound stream for opaque protocols, USB/IP-class), **per-capability approval gates** (class-keyed grants), and the **Gateway-as-owner code audit**. **M11 — Remote attach (`spt rc`)** over the already-built R-TERM substrate (controller/viewer split with controller-exclusive resize, `kick`/`--take` handoff, consent-gated input; the xterm.js bridge client = the GUI terminal pane and the future Android-gateway shape). `spt-claude-code` scoping remains on deck alongside.
52:> _Roadmap note: this milestone list is behind reality — M8 (CLI nounification + acceptance) and the v0.3.x release line shipped after M7 but are tracked in commits/CHANGELOG, not itemized here. A full roadmap reconciliation is a separate docs pass._
54:## De-risk spikes (do early — these can invalidate the architecture)
61:## Immediate next steps
73:4. **M0** (workspace + `spt-proto` + `spt-store`) — **started**; task breakdown in [`M0-PLAN.md`](./M0-PLAN.md). Each milestone gets its own just-in-time plan (this one is M0); the roadmap stays the milestone sequence, the JIT plan is the task layer.
75:## Tooling: requirement traceability (adopted)
77:`traceable-reqs` is adopted as the requirement-tracing gate. `traceable-reqs.toml` seeds the `REQ-*` registry from PRD `R-*` + KNOWN-HAZARDS invariants (all inactive until milestone activation). The development contract + enforcement (CI gate, milestone activation, agent rule, quality audit) is `docs/TRACEABILITY.md`; the agent rule lives in `CLAUDE.md`. **M0 task:** install the CLI, validate the seed, wire the CI workflow, activate M0 reqs.
79:## Open threads carried forward
81:- **Cross-node Gateway WAN user-authority** (ratified + activated 2026-06-13). M9-T4/T5 shipped the local user-msg identity gate; the WAN half is now being completed. **Trust posture (operator-ratified):** the subnet membership boundary is the trust boundary — a subnet is machines the user already trusts, so a `user-msg` from a **Gateway-typed** origin over the subnet is honored; defending against an *in-subnet forge* of the Gateway type is explicitly out of scope (a compromised subnet member is game-over by construction). The earlier per-node "user-surface trust set" proposal was vetoed as overkill. **Work** = add `endpoint_type` to the registry `Instance` (additive, epoch-leased replication) + populate on advertise + resolve `(from-id, QUIC-proven origin-node)→endpoint_type` in `receive_wan`, flipping the already-plumbed `origin_user_backed`. Fail-closed survives only as N-1 rollout grace (a node not yet advertising `endpoint_type`). **SHIPPED 2026-06-13** on `m9-gateway-wan` (PR #9, REQ-MSG-6 doc+impl+unit) — doyle gate PASS. **REQUIRED-BEFORE-LECTURN (tracked condition from the gate):** the T5 twohost *honored-path* integration test — Gateway advert on node A → `user-msg` HONORED at B; agent advert → re-stamped — was deferred (the security-critical NEGATIVE path is already E2E-tested via the `wanmsg` funnel, the resolve logic is matrix-covered, and there is no production exerciser of the positive path until a real cross-node Gateway exists). It MUST land before `spt-lecturn` ships a real cross-node Gateway, so the honored wire path is proven before anything relies on it.


echo "=== REQ-TERM entries ==="; grep -nE "REQ-TERM-[0-9]" C:/Users/decid/Documents/projects/spt-core/traceable-reqs.toml 2>&1; echo "=== last 5 req ids minted (tail of registry) ==="; grep -nE '^id = "REQ-' C:/Users/decid/Documents/projects/spt-core/traceable-reqs.toml 2>&1 | tail -8; echo "=== a sample full entry for format ==="; sed -n '410,418p' C:/Users/decid/Documents/projects/spt-core/traceable-reqs.toml 2>&1

Read REQ-TERM registry + format


=== REQ-TERM entries ===
401:id = "REQ-TERM-1"
405:id = "REQ-TERM-2"
409:id = "REQ-TERM-3"
414:id = "REQ-TERM-4"
=== last 5 req ids minted (tail of registry) ===
825:id = "REQ-DAEMON-9"
829:id = "REQ-HAZARD-EPOCH-RESET"
836:id = "REQ-MESH-1"
840:id = "REQ-MESH-2"
844:id = "REQ-MESH-3"
848:id = "REQ-MESH-4"
852:id = "REQ-MESH-5"
856:id = "REQ-MESH-6"
=== a sample full entry for format ===
title = "Byte-stream remote terminal streaming for v1"
required_stages = ["impl", "unit"]
# ── Live activity buffer / PTY digest (ADR-0008, 2026-06-01; activate M3) ──
[[requirements]]
id = "REQ-TERM-4"
title = "Live activity buffer (session digest): projection of normalized session logs, snapshot-pull (spt endpoint digest) + structured-delta-stream contract + api digest-entry push"
required_stages = ["impl", "unit", "int"]
[[requirements]]
id = "REQ-FRONT-1"


Roadmap context clear: M9 done; M10 (Shell) / M11 (remote) queued. This digest milestone is a new spt-claude-code enabler slotting next. Per traceability rule 5, I'll **register** the REQs now with `required_stages = []` (keeps the tree green) and make "activate" task 1 of the plan at execution start. Reading the exact registry anchor.


412	# ── Live activity buffer / PTY digest (ADR-0008, 2026-06-01; activate M3) ──
413	[[requirements]]
414	id = "REQ-TERM-4"
415	title = "Live activity buffer (session digest): projection of normalized session logs, snapshot-pull (spt endpoint digest) + structured-delta-stream contract + api digest-entry push"
416	required_stages = ["impl", "unit", "int"]
417	[[requirements]]
418	id = "REQ-FRONT-1"
419	title = "Day-one launcher/manager frontend (list/launch/attach/init)"

# ── Live activity buffer / PTY digest (ADR-0008, 2026-06-01; activate M3) ──
[[requirements]]
id = "REQ-TERM-4"
title = "Live activity buffer (session digest): projection of normalized session logs, snapshot-pull (spt endpoint digest) + structured-delta-stream contract + api digest-entry push"
required_stages = ["impl", "unit", "int"]
[[requirements]]
id = "REQ-FRONT-1"
title = "Day-one launcher/manager frontend (list/launch/attach/init)"


note that we shouldn't "correct the 0.5.0 release note" in the changelog. the next release's changelog will simply point out its supersession as coinciding with that release. -agree all recs --- proce…


Edit

id = "REQ-TERM-4"
title = "Live activity buffer (session digest): projection of normalized session logs, snapshot-pull (spt endpoint digest) + structured-delta-stream contract + api digest-entry push"
required_stages = ["impl", "unit", "int"]
[[requirements]]
id = "REQ-FRONT-1"

id = "REQ-TERM-4"
title = "Live activity buffer (session digest): projection of normalized session logs, snapshot-pull (spt endpoint digest) + structured-delta-stream contract + api digest-entry push"
required_stages = ["impl", "unit", "int"]

# ── Digest extractor seam & thread-spanning (ADR-0019, 2026-06-13; activate at the
#    digest-milestone execution start — registered now per rule 5, stages empty until then) ──
[[requirements]]
id = "REQ-TERM-5"
title = "Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL."
required_stages = []
[[requirements]]
id = "REQ-TERM-6"
title = "Thread-spanning digest across session boundaries: a per-endpoint session ledger (`<perch>/sessions.log`) appended at first bind and by `api boundary` on `/clear`|`/compact` session rotation, the digest enumerating the last K sessions so its rolling window bridges a boundary, and a distinctive in-timeline boundary marker (DigestEntry::Boundary). The digest follows the live-agent thread, not a single session."
required_stages = []
[[requirements]]
id = "REQ-TERM-7"
title = "Two-origin digest merge: spt-owned context-injection entries (psyche_download | echo_mirror | owl_message) appended by spt to the endpoint `digest.log`, timestamp-interleaved with the adapter's extracted activity records into one ordered timeline, via a distinct context-injection record category. Data model only this milestone; GUI collapse/expand and the echo-reads-digest delta loop are deferred to the surfaces that consume them."
required_stages = []
[[requirements]]
id = "REQ-FRONT-1"


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

id = "REQ-TERM-4"
title = "Live activity buffer (session digest): projection of normalized session logs, snapshot-pull (spt endpoint digest) + structured-delta-stream contract + api digest-entry push"
required_stages = ["impl", "unit", "int"]
[[requirements]]
id = "REQ-FRONT-1"

id = "REQ-TERM-4"
title = "Live activity buffer (session digest): projection of normalized session logs, snapshot-pull (spt endpoint digest) + structured-delta-stream contract + api digest-entry push"
required_stages = ["impl", "unit", "int"]

# ── Digest extractor seam & thread-spanning (ADR-0019, 2026-06-13; activate at the
#    digest-milestone execution start — registered now per rule 5, stages empty until then) ──
[[requirements]]
id = "REQ-TERM-5"
title = "Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL."
required_stages = []
[[requirements]]
id = "REQ-TERM-6"
title = "Thread-spanning digest across session boundaries: a per-endpoint session ledger (`<perch>/sessions.log`) appended at first bind and by `api boundary` on `/clear`|`/compact` session rotation, the digest enumerating the last K sessions so its rolling window bridges a boundary, and a distinctive in-timeline boundary marker (DigestEntry::Boundary). The digest follows the live-agent thread, not a single session."
required_stages = []
[[requirements]]
id = "REQ-TERM-7"
title = "Two-origin digest merge: spt-owned context-injection entries (psyche_download | echo_mirror | owl_message) appended by spt to the endpoint `digest.log`, timestamp-interleaved with the adapter's extracted activity records into one ordered timeline, via a distinct context-injection record category. Data model only this milestone; GUI collapse/expand and the echo-reads-digest delta loop are deferred to the surfaces that consume them."
required_stages = []
[[requirements]]
id = "REQ-FRONT-1"

+# ── Digest extractor seam & thread-spanning (ADR-0019, 2026-06-13; activate at the

+#    digest-milestone execution start — registered now per rule 5, stages empty until then) ──

+title = "Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL."

+title = "Thread-spanning digest across session boundaries: a per-endpoint session ledger (`<perch>/sessions.log`) appended at first bind and by `api boundary` on `/clear`|`/compact` session rotation, the digest enumerating the last K sessions so its rolling window bridges a boundary, and a distinctive in-timeline boundary marker (DigestEntry::Boundary). The digest follows the live-agent thread, not a single session."

+title = "Two-origin digest merge: spt-owned context-injection entries (psyche_download | echo_mirror | owl_message) appended by spt to the endpoint `digest.log`, timestamp-interleaved with the adapter's extracted activity records into one ordered timeline, via a distinct context-injection record category. Data model only this milestone; GUI collapse/expand and the echo-reads-digest delta loop are deferred to the surfaces that consume them."

title = "Day-one launcher/manager frontend (list/launch/attach/init)"

required_stages = []  # rule 5: GUI milestone — no frontend work in M0–M4


REQs registered (stages empty → tree stays green; activated at execution per rule 5). Verify the registry parses + check is green, then write the plan.


Bash

traceable-reqs check 2>&1 | grep -iE "REQ-TERM-[567]|error|invalid|EXIT|fail|^\s*\[" | head; echo "exit:$?"

Verify traceable-reqs check green with new REQs


  [OK] REQ-API-1  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-API-2  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-API-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-ARCH-1  required: [impl]  stages: -doc +impl -unit -int
  [OK] REQ-ARCH-2  required: [impl]  stages: -doc +impl -unit -int
  [OK] REQ-ARCH-3  required: [impl, unit]  stages: -doc +impl +unit -int
  [OK] REQ-ARCH-4  required: [impl, unit]  stages: -doc +impl +unit -int
  [OK] REQ-CLI-1  required: [impl, unit]  stages: -doc +impl +unit -int
  [OK] REQ-CLI-2  required: [impl, unit]  stages: -doc +impl +unit -int
  [OK] REQ-CLI-3  required: [impl, unit]  stages: -doc +impl +unit -int
exit:0


Registry parses, check green (new REQs inactive). Writing the JIT plan.


Write

# Digest extractor & thread-spanning — JIT milestone plan

> **STATUS: PLANNED (2026-06-13).** Product of the digest grill (operator + `doyle`).
> Grounded against the live tree post-M9 + **ADR-0019** (the governing decision) +
> the rewritten CONTEXT.md "live activity buffer (session digest)" entry. This
> project does **not** use GSD — this is a hand-authored JIT plan, the heir to
> `M9-PLAN.md`. Executes on operator dispatch (likely → todlando), `doyle` gates.
>
> **Roadmap placement:** the next milestone, ahead of the queued Shell (prev "M10")
> and remote-attach (prev "M11") — it is an `spt-claude-code` enabler. Final
> milestone number is operator-ratify; the plan stands regardless.

---

## 1. Goal

Make the session digest something a **real, first adapter** (`spt-claude-code`) can
actually drive: give it its own adapter-declared extractor seam, follow a live
agent across `/clear` session-hops, and merge in the context spt itself feeds the
agent. Reverses M9's "no manifest seam" stance (ADR-0019).

**End state:** an adapter declares `[digest]`, runs `spt adapter digest-proof` to
confirm its extractor before shipping, and `spt endpoint digest <id>` renders a
time-ordered timeline that (a) spans a `/clear` with a visible boundary marker and
(b) interleaves the agent's activity with spt's injected context — with no silent
drops.

## 2. Requirements (registered; activate at execution start — rule 5)

| REQ | Scope | Stages to activate |
|-----|-------|--------------------|
| **REQ-TERM-5** | `[digest]` extractor seam + register validation + presentation knobs + `digest-proof` + runtime diagnostics | `doc, impl, unit, int` |
| **REQ-TERM-6** | thread-spanning: session ledger + `api boundary` append + last-K enumeration + boundary marker | `impl, unit, int` |
| **REQ-TERM-7** | two-origin merge: spt context-injection entries → `digest.log`, ts-interleave, context category (data model only) | `impl, unit, int` |
| REQ-TERM-4 | carried (projection + pull + delta-stream + `api digest-entry`) — no re-point; new REQs layer on | unchanged |

**Task 0 (first commit of execution):** set the three `required_stages` above in
`traceable-reqs.toml`. The tree is green now because they are empty; activation +
first evidence land together so no commit is ever left activated-but-unevidenced.

## 3. Design (settled — see ADR-0019, do not re-litigate)

The full decision record is **ADR-0019**. Load-bearing points the tasks depend on:

- **Imperative extractor, no DSL.** `[digest]` declares a command (native log →
  `{role,text,tool,ts}`). Defaults to the `[history]` source files (reference its
  locate pattern); own-source escape hatch. `api digest-entry` push stays the
  fallback. Real CC logs (9 record types, `assistant.content` a mixed
  thinking/text/tool_use list, one line → many entries) are why a declarative map
  is refused.
- **One source, two extractors.** `[history]`/echo stays opaque + single-session;
  `[digest]` is contract-typed + session-spanning. They no longer share a
  normalizer — the dual-consumer conflict is the whole reason for this milestone.
- **Presentation is adapter-defaulted, consumer-overridable** (window depth,
  arg-truncation, sprint-collapse). The fixed "~3 turns" is dropped.
- **Thread-spanning** via a per-endpoint **session ledger** (`<perch>/sessions.log`,
  heir to legacy `psyches/tracked/agents/<id>/sessions.log`); `api boundary`
  appends the rotated `session_id`; the digest enumerates the last K sessions.
- **Two-origin merge.** spt appends its own context-injection entries
  (`psyche_download | echo_mirror | owl_message`) to `digest.log`; the projection
  interleaves them with extracted activity records by `ts`.
- **Diagnostics are mandatory** (the silent drop is the gap that bit us): a shared
  engine behind `spt adapter digest-proof` (author-time) and a runtime warn/count.

## 4. Task breakdown

### Wave 1 — the `[digest]` seam + extractor (REQ-TERM-5)
- **T1 — manifest `[digest]` section.** `spt-runtime`: struct + serde + `validate()`
  (extractor command present; source pattern resolvable or own-source given; knob
  types). Add optional `ts` to the `spt-term` digest-record contract. `[doc/impl/unit]`
- **T2 — extractor execution.** `spt-daemon::digest`: `project_endpoint_digest`
  resolves the `[digest]` extractor over the source file(s); apply adapter-default
  presentation knobs with consumer override threaded from pull/subscribe. `[impl/unit]`
- **T3 — diagnostics + proof tool.** Shared skip-diagnostic engine (count + first
  mismatch + reason); `spt adapter digest-proof <adapter> [--sample <log>]` (prints
  parsed records, rendered digest, every dropped line + why); daemon emits the same
  at runtime. Remove the silent `from_str().ok()` drop. `[impl/unit]`
- **T-doc — MANIFEST.md `[digest]` section.** `[doc]`

### Wave 2 — thread-spanning (REQ-TERM-6)
- **T4 — session ledger.** `<perch>/sessions.log` JSONL `{ts, session_id, trigger:
  boot|clear|compact}`; writer appends at `establish_perch` first bind; reader
  enumerates last K (config-bounded). `[impl/unit]`
- **T5 — boundary append.** Extend `cmd_boundary` / `resurface_at_boundary` to append
  the rotated `session_id` to the ledger (on top of the existing rebind). `[impl/unit]`
- **T6 — spanning projection + marker.** Digest source enumerates last K sessions,
  runs the extractor per file, concatenates oldest→newest; insert
  `DigestEntry::Boundary{kind, ts}` at each transition; window bridges the hop. `[impl/unit]`

### Wave 3 — two-origin merge (REQ-TERM-7)
- **T7 — context category + merge.** `digest.log` becomes a tagged union (activity
  record vs context-injection record `{kind, body, ts}`); projection interleaves
  extracted activity + digest.log entries by `ts`. `[impl/unit]`
- **T8 — injection taps.** Append a context entry where spt injects: psyche download
  (`spt-live::psyche`), echo mirror (`spt-live::echo`), owl message (spool/`ingest`).
  Adapters never emit context records — spt owns them. `[impl/unit]`

### Wave 4 — integration, docs, gate
- **T9 — E2E (REQ-TERM-5/6/7 `int`).** One harness source → echo (opaque) **and**
  digest (contract) from the same files; a `/clear` spanned with a boundary marker;
  spt context entries interleaved by ts. The realest fixture (corrected dual-consumer
  proof + Candidate 03). `[int x3]`
- **T-docs — sweep.** MANIFEST `[digest]` (T-doc), **harness integration checklist**
  updated, CHANGELOG `[Unreleased]` (note: ADR-0008 source-mechanism **superseded by
  ADR-0019**; the v0.5.0 note is **not** retro-edited — the next release's changelog
  records the supersession coinciding with it), CONTEXT.md (done), ADR-0019 (done). `[doc]`

## 5. Commit choreography
Atomic per task; `traceable-reqs check` EXIT=0 + CI-targeted local sweep green at
each commit. Task 0 (activate stages) rides T1's commit so first evidence and
activation land together. No commit left activated-but-unevidenced.

## 6. Gate checklist (doyle, pre-merge)
- [ ] REQ-TERM-5/6/7 each `doc?/impl/unit/int` satisfied; `traceable-reqs check` EXIT=0; no orphan tags.
- [ ] `[digest]` section validated at register (bad extractor / bad knob caught at `adapter add`, not spawn).
- [ ] `spt adapter digest-proof` runs against a **real CC session JSONL** sample and surfaces a deliberately-broken extractor's drops (no silent empty).
- [ ] Spanning E2E: digest bridges a `/clear`, boundary marker present, window not reset-to-empty.
- [ ] Two-origin E2E: a psyche/echo/owl injection appears interleaved by ts.
- [ ] Full suite: workspace 0-fail, `clippy -D`, `--no-default-features`, xtask check, `traceable-reqs check` EXIT=0.
- [ ] Docs: MANIFEST `[digest]`, harness integration checklist, CHANGELOG supersession note (NOT a v0.5.0 retro-edit), CONTEXT + ADR-0019 consistent.

## 7. Deferred (no consumer yet — same logic as M9)
- GUI collapse/expand of context-injection entries.
- The echo-commune-reads-the-digest delta loop (the consumer that motivated two-origin).
- Autonomous file-watch digest freshness (still deferred from M9).
- Candidate 04 (Psyche M2b→M3b hosting) — orthogonal; belongs to the daemon/live-agent milestone.

## 8. Risks / watch-items
- **`ts` source skew.** Adapter records carry the harness's `timestamp`; spt stamps
  its own injections. Clock-source differences could misorder the merge — pin both to
  the same monotonic/wall basis or document the ordering rule. Flag at gate.
- **Ledger growth / bound.** `sessions.log` must bound or rotate (legacy's seal-on-boot
  failure grew it unboundedly — see the sister project's `doyle-sessions-seal` post-mortem). Cap K + the on-disk ledger.
- **`digest.log` dual-schema.** Activity and context records share the file; the
  tagged-union discriminator must be unambiguous and forward-compat (unknown kind → skip, like the contract).
- **Don't expand scope** — data model only for two-origin; no GUI, no echo loop, no new realtime subsystem.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\DIGEST-MILESTONE-PLAN.md (file state is current in your context — no need to Read it back)

# Digest extractor & thread-spanning — JIT milestone plan

> **STATUS: PLANNED (2026-06-13).** Product of the digest grill (operator + `doyle`).
> Grounded against the live tree post-M9 + **ADR-0019** (the governing decision) +
> the rewritten CONTEXT.md "live activity buffer (session digest)" entry. This
> project does **not** use GSD — this is a hand-authored JIT plan, the heir to
> `M9-PLAN.md`. Executes on operator dispatch (likely → todlando), `doyle` gates.
>
> **Roadmap placement:** the next milestone, ahead of the queued Shell (prev "M10")
> and remote-attach (prev "M11") — it is an `spt-claude-code` enabler. Final
> milestone number is operator-ratify; the plan stands regardless.

---

## 1. Goal

Make the session digest something a **real, first adapter** (`spt-claude-code`) can
actually drive: give it its own adapter-declared extractor seam, follow a live
agent across `/clear` session-hops, and merge in the context spt itself feeds the
agent. Reverses M9's "no manifest seam" stance (ADR-0019).

**End state:** an adapter declares `[digest]`, runs `spt adapter digest-proof` to
confirm its extractor before shipping, and `spt endpoint digest <id>` renders a
time-ordered timeline that (a) spans a `/clear` with a visible boundary marker and
(b) interleaves the agent's activity with spt's injected context — with no silent
drops.

## 2. Requirements (registered; activate at execution start — rule 5)

| REQ | Scope | Stages to activate |
|-----|-------|--------------------|
| **REQ-TERM-5** | `[digest]` extractor seam + register validation + presentation knobs + `digest-proof` + runtime diagnostics | `doc, impl, unit, int` |
| **REQ-TERM-6** | thread-spanning: session ledger + `api boundary` append + last-K enumeration + boundary marker | `impl, unit, int` |
| **REQ-TERM-7** | two-origin merge: spt context-injection entries → `digest.log`, ts-interleave, context category (data model only) | `impl, unit, int` |
| REQ-TERM-4 | carried (projection + pull + delta-stream + `api digest-entry`) — no re-point; new REQs layer on | unchanged |

**Task 0 (first commit of execution):** set the three `required_stages` above in
`traceable-reqs.toml`. The tree is green now because they are empty; activation +
first evidence land together so no commit is ever left activated-but-unevidenced.

## 3. Design (settled — see ADR-0019, do not re-litigate)

The full decision record is **ADR-0019**. Load-bearing points the tasks depend on:

- **Imperative extractor, no DSL.** `[digest]` declares a command (native log →
  `{role,text,tool,ts}`). Defaults to the `[history]` source files (reference its
  locate pattern); own-source escape hatch. `api digest-entry` push stays the
  fallback. Real CC logs (9 record types, `assistant.content` a mixed
  thinking/text/tool_use list, one line → many entries) are why a declarative map
  is refused.
- **One source, two extractors.** `[history]`/echo stays opaque + single-session;
  `[digest]` is contract-typed + session-spanning. They no longer share a
  normalizer — the dual-consumer conflict is the whole reason for this milestone.
- **Presentation is adapter-defaulted, consumer-overridable** (window depth,
  arg-truncation, sprint-collapse). The fixed "~3 turns" is dropped.
- **Thread-spanning** via a per-endpoint **session ledger** (`<perch>/sessions.log`,
  heir to legacy `psyches/tracked/agents/<id>/sessions.log`); `api boundary`
  appends the rotated `session_id`; the digest enumerates the last K sessions.
- **Two-origin merge.** spt appends its own context-injection entries
  (`psyche_download | echo_mirror | owl_message`) to `digest.log`; the projection
  interleaves them with extracted activity records by `ts`.
- **Diagnostics are mandatory** (the silent drop is the gap that bit us): a shared
  engine behind `spt adapter digest-proof` (author-time) and a runtime warn/count.

## 4. Task breakdown

### Wave 1 — the `[digest]` seam + extractor (REQ-TERM-5)
- **T1 — manifest `[digest]` section.** `spt-runtime`: struct + serde + `validate()`
  (extractor command present; source pattern resolvable or own-source given; knob
  types). Add optional `ts` to the `spt-term` digest-record contract. `[doc/impl/unit]`
- **T2 — extractor execution.** `spt-daemon::digest`: `project_endpoint_digest`
  resolves the `[digest]` extractor over the source file(s); apply adapter-default
  presentation knobs with consumer override threaded from pull/subscribe. `[impl/unit]`
- **T3 — diagnostics + proof tool.** Shared skip-diagnostic engine (count + first
  mismatch + reason); `spt adapter digest-proof <adapter> [--sample <log>]` (prints
  parsed records, rendered digest, every dropped line + why); daemon emits the same
  at runtime. Remove the silent `from_str().ok()` drop. `[impl/unit]`
- **T-doc — MANIFEST.md `[digest]` section.** `[doc]`

### Wave 2 — thread-spanning (REQ-TERM-6)
- **T4 — session ledger.** `<perch>/sessions.log` JSONL `{ts, session_id, trigger:
  boot|clear|compact}`; writer appends at `establish_perch` first bind; reader
  enumerates last K (config-bounded). `[impl/unit]`
- **T5 — boundary append.** Extend `cmd_boundary` / `resurface_at_boundary` to append
  the rotated `session_id` to the ledger (on top of the existing rebind). `[impl/unit]`
- **T6 — spanning projection + marker.** Digest source enumerates last K sessions,
  runs the extractor per file, concatenates oldest→newest; insert
  `DigestEntry::Boundary{kind, ts}` at each transition; window bridges the hop. `[impl/unit]`

### Wave 3 — two-origin merge (REQ-TERM-7)
- **T7 — context category + merge.** `digest.log` becomes a tagged union (activity
  record vs context-injection record `{kind, body, ts}`); projection interleaves
  extracted activity + digest.log entries by `ts`. `[impl/unit]`
- **T8 — injection taps.** Append a context entry where spt injects: psyche download
  (`spt-live::psyche`), echo mirror (`spt-live::echo`), owl message (spool/`ingest`).
  Adapters never emit context records — spt owns them. `[impl/unit]`

### Wave 4 — integration, docs, gate
- **T9 — E2E (REQ-TERM-5/6/7 `int`).** One harness source → echo (opaque) **and**
  digest (contract) from the same files; a `/clear` spanned with a boundary marker;
  spt context entries interleaved by ts. The realest fixture (corrected dual-consumer
  proof + Candidate 03). `[int x3]`
- **T-docs — sweep.** MANIFEST `[digest]` (T-doc), **harness integration checklist**
  updated, CHANGELOG `[Unreleased]` (note: ADR-0008 source-mechanism **superseded by
  ADR-0019**; the v0.5.0 note is **not** retro-edited — the next release's changelog
  records the supersession coinciding with it), CONTEXT.md (done), ADR-0019 (done). `[doc]`

## 5. Commit choreography
Atomic per task; `traceable-reqs check` EXIT=0 + CI-targeted local sweep green at
each commit. Task 0 (activate stages) rides T1's commit so first evidence and
activation land together. No commit left activated-but-unevidenced.

## 6. Gate checklist (doyle, pre-merge)
- [ ] REQ-TERM-5/6/7 each `doc?/impl/unit/int` satisfied; `traceable-reqs check` EXIT=0; no orphan tags.
- [ ] `[digest]` section validated at register (bad extractor / bad knob caught at `adapter add`, not spawn).
- [ ] `spt adapter digest-proof` runs against a **real CC session JSONL** sample and surfaces a deliberately-broken extractor's drops (no silent empty).
- [ ] Spanning E2E: digest bridges a `/clear`, boundary marker present, window not reset-to-empty.
- [ ] Two-origin E2E: a psyche/echo/owl injection appears interleaved by ts.
- [ ] Full suite: workspace 0-fail, `clippy -D`, `--no-default-features`, xtask check, `traceable-reqs check` EXIT=0.
- [ ] Docs: MANIFEST `[digest]`, harness integration checklist, CHANGELOG supersession note (NOT a v0.5.0 retro-edit), CONTEXT + ADR-0019 consistent.

## 7. Deferred (no consumer yet — same logic as M9)
- GUI collapse/expand of context-injection entries.
- The echo-commune-reads-the-digest delta loop (the consumer that motivated two-origin).
- Autonomous file-watch digest freshness (still deferred from M9).
- Candidate 04 (Psyche M2b→M3b hosting) — orthogonal; belongs to the daemon/live-agent milestone.

## 8. Risks / watch-items
- **`ts` source skew.** Adapter records carry the harness's `timestamp`; spt stamps
  its own injections. Clock-source differences could misorder the merge — pin both to
  the same monotonic/wall basis or document the ordering rule. Flag at gate.
- **Ledger growth / bound.** `sessions.log` must bound or rotate (legacy's seal-on-boot
  failure grew it unboundedly — see the sister project's `doyle-sessions-seal` post-mortem). Cap K + the on-disk ledger.
- **`digest.log` dual-schema.** Activity and context records share the file; the
  tagged-union discriminator must be unambiguous and forward-compat (unknown kind → skip, like the contract).
- **Don't expand scope** — data model only for two-origin; no GUI, no echo loop, no new realtime subsystem.


Add the milestone to ROADMAP as the next one.


Edit

- **Queued after M9 (each its own plan+vet cycle):** **M10 — Shell-substrate extensions**

- **🔜 NEXT milestone (planned 2026-06-13) — Digest extractor & thread-spanning.** Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it slots ahead of the Shell/remote-attach queue below. Adapter-declared **`[digest]` extractor seam** (imperative, no DSL; same source files as `[history]` by default + own-source escape; `api digest-entry` push fallback; register-time validation; `spt adapter digest-proof` + runtime skip-diagnostics — no silent drop) · **thread-spanning** across `/clear` session-hops (per-endpoint session ledger + `api boundary` append + last-K enumeration + distinctive boundary marker) · **two-origin merge** (spt's context-injection entries — psyche download / echo mirror / owl message — interleaved with extracted activity by `ts`; data model only). New reqs REQ-TERM-5/6/7. Deferred: GUI collapse, echo-reads-digest loop, autonomous freshness. Task breakdown: [`DIGEST-MILESTONE-PLAN.md`](./DIGEST-MILESTONE-PLAN.md). (Final milestone number operator-ratify.)
- **Queued after the digest milestone (each its own plan+vet cycle):** **M10 — Shell-substrate extensions**


50	- **Queued after M9 (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped; this adds the **drive channel** (owner→shell ephemeral latest-wins continuous control), the **shell tunnel** (reliable-ordered link-bound stream for opaque protocols, USB/IP-class), **per-capability approval gates** (class-keyed grants), and the **Gateway-as-owner code audit**. **M11 — Remote attach (`spt rc`)** over the already-built R-TERM substrate (controller/viewer split with controller-exclusive resize, `kick`/`--take` handoff, consent-gated input; the xterm.js bridge client = the GUI terminal pane and the future Android-gateway shape). `spt-claude-code` scoping remains on deck alongside.


- **Queued after M9 (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped;

- **🔜 NEXT milestone (planned 2026-06-13) — Digest extractor & thread-spanning.** Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it slots ahead of the Shell/remote-attach queue below. Adapter-declared **`[digest]` extractor seam** (imperative, no DSL; same source files as `[history]` by default + own-source escape; `api digest-entry` push fallback; register-time validation; `spt adapter digest-proof` + runtime skip-diagnostics — no silent drop) · **thread-spanning** across `/clear` session-hops (per-endpoint session ledger + `api boundary` append + last-K enumeration + distinctive boundary marker) · **two-origin merge** (spt's context-injection entries — psyche download / echo mirror / owl message — interleaved with extracted activity by `ts`; data model only). New reqs REQ-TERM-5/6/7. Deferred: GUI collapse, echo-reads-digest loop, autonomous freshness. Task breakdown: [`DIGEST-MILESTONE-PLAN.md`](./DIGEST-MILESTONE-PLAN.md). (Final milestone number operator-ratify.)
- **Queued after the digest milestone (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped;


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

- **🔜 NEXT milestone (planned 2026-06-13) — Digest extractor & thread-spanning.** Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it slots ahead of the Shell/remote-attach queue below. Adapter-declared **`[digest]` extractor seam** (imperative, no DSL; same source files as `[history]` by default + own-source escape; `api digest-entry` push fallback; register-time validation; `spt adapter digest-proof` + runtime skip-diagnostics — no silent drop) · **thread-spanning** across `/clear` session-hops (per-endpoint session ledger + `api boundary` append + last-K enumeration + distinctive boundary marker) · **two-origin merge** (spt's context-injection entries — psyche download / echo mirror / owl message — interleaved with extracted activity by `ts`; data model only). New reqs REQ-TERM-5/6/7. Deferred: GUI collapse, echo-reads-digest loop, autonomous freshness. Task breakdown: [`DIGEST-MILESTONE-PLAN.md`](./DIGEST-MILESTONE-PLAN.md). (Final milestone number operator-ratify.)
- **Queued after the digest milestone (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped;

- **✅ Broker/brain process-isolation restoration — delivered (2026-06-11).** Design: [`docs/BROKER-BRAIN-SPLIT-RESTORATION.md`](./docs/BROKER-BRAIN-SPLIT-RESTORATION.md) (verified-with-amendments by `doyle`); decision: **ADR-0018**; build plan: [`RESTORATION-PLAN.md`](./RESTORATION-PLAN.md) (D1–D7). Corrects an unintended regression — the broker/brain split (ADR-0004) was specced + spiked as two *processes*, but the production daemon runs the broker as an in-process *thread* (`daemon.rs:165-170`), so `spt update apply` swaps the binary on disk yet never restarts the running code. Every release to date needed a manual fleet daemon bounce (paid 3× on v0.3.2; `enlyzeam` reproduced the `\r`-corruption for ~a day with the fix on disk). Restore the two-process model: broker = always-up per-machine anchor (seed-lock + liveness + brain supervisor) spawning the brain as a child; update = brain snapshot+self-exit → broker respawns from the swapped binary → re-attach; durable absolute-deadline loop timing; broker cursor-of-record with all-session resume; readiness-gated auto-rollback to last-known-good (two-phase applied record + the no-pre-ready-migration invariant); broker-owned generation custody; the §B-adjudicated direct-call→versioned-IPC-verb surface (net near-free, shellwake stays brain, new-brain × old-broker N-1 compat test). Re-points the regression-masked REQ-DAEMON-2 / REQ-UPD-3 `int` evidence to a productionized SPIKE-01/03 process-level survival E2E; mints `REQ-HAZARD-BROKER-PROCESS-ISOLATION` + `REQ-HAZARD-ROLLBACK-STATE-COMPAT` (KNOWN-HAZARDS 6.7/6.8). **Sequenced before `spt-claude-code`** (operator 2026-06-09): the *last* release needing a manual bounce, so every adapter-era release rolls seamlessly and the adapter lands on the final topology; changes daemon internals only, leaving the M8-frozen CLI/api surface untouched. **Delivery (D1–D7 + field-hardening across v0.4.0–v0.4.2):** the two-process model shipped in **v0.4.0** (public 2026-06-10); the D7 process-level survival E2E (`brain_survive.rs`) + N-1 gate landed and re-pointed the regression-masked REQ-DAEMON-2/UPD-3 `int`. The **v0.4.1** fleet-verify proved Windows seamless (hfenduleam: brain pid rolls, broker held, `exe_hash` flips, no manual bounce) but surfaced a **Linux-only** respawn bug — the resident broker respawned the brain via per-spawn `current_exe()`, which on Linux follows the `apply` rename to `.old-N` and ran OLD bytes under an `applied` record (caught by the bytes-level `exe_hash` assert; `REQ-HAZARD-BRAIN-RESPAWN-PATH` / KNOWN-HAZARDS 6.11). **v0.4.2** (public 2026-06-11) fixed it: respawn from the canonical path captured at broker start + a promotion **bytes-gate** (a wrong-bytes trial auto-rolls-back instead of recording success), with a real in-place-rename `int` E2E (`brain_respawn_rename.rs`) that fails on Linux pre-fix and is CI-gated green on both runners. **Acceptance:** Windows live-seamless proven twice (hfenduleam 0.4.0→0.4.1→0.4.2, broker pid held throughout); Linux proven by the CI-gated in-place-rename E2E on the kitsubito runner; the whole fleet (kitsubito/enlyzeam/hfenduleam) runs v0.4.2 on fixed brokers, after the project's **final two manual bounces** (kitsubito + the enlyzeam v0.3.2 catch-up). The live Linux *field*-seamless rides the next real release (operator decision 2026-06-11: close on the CI-gated E2E + fixed-broker fleet rather than a synthetic debug rollout). KNOWN-HAZARDS 6.7/6.8 closed out. **Next:** `spt-claude-code` — the v1 acceptance proof on the final topology, its own repo.

- **🔜 NEXT milestone (planned 2026-06-12) — M9: Adapter customization & session surfaces.** Product of the two-day **Gateway grill** (operator + `doyle`; CONTEXT.md mints @ `8dc22ff`/`bd469b9`; ADR-0008 amended; the lecturn device design parked in the sibling `spt-lecturn/` repo for a later build). Shell-free, independently-shippable features: **adapter profiles** (sparse leaf-replace overlays on the parent manifest; shipped + local sources, local surviving adapter updates; composite `<adapter>:<profile>` addressing; tighten-only consent floors; `create-profile`/`delete-profile`) · **adapter strings** (`[strings]` manifest KV, overlay-resolved, data-only; `get-string`/`set-string`) · **keyword hints** (`[hints]` + `spt api hint`, once-per-session, one per message — adapters teach their surface exactly-when-relevant) · **`user-msg` type** (user-authority messages, identity-gated per the KH 7.3/7.5 posture; the daemon re-stamps impostors) · **Gateway endpoint-type acceptance** (the open type system honoring a human-backed downstream type end-to-end) · **`live-role.md`** (durable broad-purpose statement in `tracked/`, injected first at start transitions, `spt endpoint role` the sole writer) · the **session-digest log-source revision** (ADR-0008 amendment: digest = a projection of normalized session logs, never PTY parsing; `api digest-entry`; REQ-TERM-4 re-point — restoration-style honest re-evidence). Task breakdown: [`M9-PLAN.md`](./M9-PLAN.md). First beneficiaries: `spt-claude-code` (profiles/hints/digest are its enablers) and the future lecturn build.

-- **Queued after M9 (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped; this adds the **drive channel** (owner→shell ephemeral latest-wins continuous control), the **shell tunnel** (reliable-ordered link-bound stream for opaque protocols, USB/IP-class), **per-capability approval gates** (class-keyed grants), and the **Gateway-as-owner code audit**. **M11 — Remote attach (`spt rc`)** over the already-built R-TERM substrate (controller/viewer split with controller-exclusive resize, `kick`/`--take` handoff, consent-gated input; the xterm.js bridge client = the GUI terminal pane and the future Android-gateway shape). `spt-claude-code` scoping remains on deck alongside.

+- **🔜 NEXT milestone (planned 2026-06-13) — Digest extractor & thread-spanning.** Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it slots ahead of the Shell/remote-attach queue below. Adapter-declared **`[digest]` extractor seam** (imperative, no DSL; same source files as `[history]` by default + own-source escape; `api digest-entry` push fallback; register-time validation; `spt adapter digest-proof` + runtime skip-diagnostics — no silent drop) · **thread-spanning** across `/clear` session-hops (per-endpoint session ledger + `api boundary` append + last-K enumeration + distinctive boundary marker) · **two-origin merge** (spt's context-injection entries — psyche download / echo mirror / owl message — interleaved with extracted activity by `ts`; data model only). New reqs REQ-TERM-5/6/7. Deferred: GUI collapse, echo-reads-digest loop, autonomous freshness. Task breakdown: [`DIGEST-MILESTONE-PLAN.md`](./DIGEST-MILESTONE-PLAN.md). (Final milestone number operator-ratify.)

+- **Queued after the digest milestone (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped; this adds the **drive channel** (owner→shell ephemeral latest-wins continuous control), the **shell tunnel** (reliable-ordered link-bound stream for opaque protocols, USB/IP-class), **per-capability approval gates** (class-keyed grants), and the **Gateway-as-owner code audit**. **M11 — Remote attach (`spt rc`)** over the already-built R-TERM substrate (controller/viewer split with controller-exclusive resize, `kick`/`--take` handoff, consent-gated input; the xterm.js bridge client = the GUI terminal pane and the future Android-gateway shape). `spt-claude-code` scoping remains on deck alongside.

> _Roadmap note: this milestone list is behind reality — M8 (CLI nounification + acceptance) and the v0.3.x release line shipped after M7 but are tracked in commits/CHANGELOG, not itemized here. A full roadmap reconciliation is a separate docs pass._


Done. Milestone minted + planned.

**Written this session:**
- **CONTEXT.md** — digest entry rewritten: "recent *context*" (activity + injected payloads), two-origin, adapter-tuned presentation, thread-spanning + distinctive `/clear` marker.
- **ADR-0019** — the governing decision (adapter-declared `[digest]` extractor, no DSL, thread-spanning, two-origin). **ADR-0008** marked fully superseded (product surface survives).
- **traceable-reqs.toml** — `REQ-TERM-5` (extractor seam + proof tool + diagnostics), `REQ-TERM-6` (thread-spanning ledger), `REQ-TERM-7` (two-origin merge). Registered with empty stages → tree green; activate as task 0 of execution (rule 5).
- **DIGEST-MILESTONE-PLAN.md** — 4 waves (seam → spanning → merge → integration), commit choreography, gate checklist, deferred set, risk list (ts-skew, ledger bound, digest.log dual-schema).
- **ROADMAP.md** — added as the next milestone, ahead of Shell/remote queue; number left for you to ratify.

Changelog: no v0.5.0 retro-edit — next release's changelog records the ADR-0008→0019 supersession, per your call.

All files **uncommitted** (no commit instruction). Traceable green.

Next move is yours: want me to brief **todlando** (owl commune) to execute the plan, or hold? And do you want a milestone number assigned now or left as operator-ratify?


set the milestone number, commit, then send a message for todlando to kick off the plan execution.


Setting M10 = digest milestone (Shell→M11, remote→M12 — it's built next, honest sequence). Fixing the roadmap + plan, then committing on a milestone branch stacked on the M9 tip (the digest code this depends on isn't in main yet). Checking git state first.


Bash

echo "branch: $(git branch --show-current)"; echo "=== status ==="; git status --short 2>&1 | head -20; echo "=== recent ==="; git log --oneline -3 2>&1

Check current branch + working tree state


branch: main
=== status ===
 M CONTEXT.md
 M ROADMAP.md
 M docs/adr/0008-live-activity-buffer-pty-digest.md
 M traceable-reqs.toml
?? DIGEST-MILESTONE-PLAN.md
?? docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md
=== recent ===
22a9b6f Merge pull request #10 from SaberMage/m9-digest-logs
c991c98 Merge pull request #9 from SaberMage/m9-gateway-wan
49f6796 Merge pull request #7 from SaberMage/m9-adapter-custom


On `main` — and the M9 stack (#7/#9/#10) is already merged here, so main has the digest code; the milestone branches cleanly off main. I'm on the default branch, so I'll branch before committing. First the M10 numbering edits.


Edit

- **🔜 NEXT milestone (planned 2026-06-13) — Digest extractor & thread-spanning.** Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it slots ahead of the Shell/remote-attach queue below.

- **🔜 M10 — Digest extractor & thread-spanning** (planned 2026-06-13; NEXT). Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it takes the M10 slot ahead of the Shell/remote-attach work (which shift to M11/M12).


-- **🔜 NEXT milestone (planned 2026-06-13) — Digest extractor & thread-spanning.** Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it slots ahead of the Shell/remote-attach queue below. Adapter-declared **`[digest]` extractor seam** (imperative, no DSL; same source files as `[history]` by default + own-source escape; `api digest-entry` push fallback; register-time validation; `spt adapter digest-proof` + runtime skip-diagnostics — no silent drop) · **thread-spanning** across `/clear` session-hops (per-endpoint session ledger + `api boundary` append + last-K enumeration + distinctive boundary marker) · **two-origin merge** (spt's context-injection entries — psyche download / echo mirror / owl message — interleaved with extracted activity by `ts`; data model only). New reqs REQ-TERM-5/6/7. Deferred: GUI collapse, echo-reads-digest loop, autonomous freshness. Task breakdown: [`DIGEST-MILESTONE-PLAN.md`](./DIGEST-MILESTONE-PLAN.md). (Final milestone number operator-ratify.)

+- **🔜 M10 — Digest extractor & thread-spanning** (planned 2026-06-13; NEXT). Product of the digest grill (operator + `doyle`); governed by **ADR-0019** (reverses M9's "no manifest seam"), CONTEXT.md "session digest" rewritten. An `spt-claude-code` enabler, so it takes the M10 slot ahead of the Shell/remote-attach work (which shift to M11/M12). Adapter-declared **`[digest]` extractor seam** (imperative, no DSL; same source files as `[history]` by default + own-source escape; `api digest-entry` push fallback; register-time validation; `spt adapter digest-proof` + runtime skip-diagnostics — no silent drop) · **thread-spanning** across `/clear` session-hops (per-endpoint session ledger + `api boundary` append + last-K enumeration + distinctive boundary marker) · **two-origin merge** (spt's context-injection entries — psyche download / echo mirror / owl message — interleaved with extracted activity by `ts`; data model only). New reqs REQ-TERM-5/6/7. Deferred: GUI collapse, echo-reads-digest loop, autonomous freshness. Task breakdown: [`DIGEST-MILESTONE-PLAN.md`](./DIGEST-MILESTONE-PLAN.md). (Final milestone number operator-ratify.)

- **Queued after the digest milestone (each its own plan+vet cycle):** **M10 — Shell-substrate extensions** — the M5 shell mechanism already shipped; this adds the **drive channel** (owner→shell ephemeral latest-wins continuous control), the **shell tunnel** (reliable-ordered link-bound stream for opaque protocols, USB/IP-class), **per-capability approval gates** (class-keyed grants), and the **Gateway-as-owner code audit**. **M11 — Remote attach (`spt rc`)** over the already-built R-TERM substrate (controller/viewer split with controller-exclusive resize, `kick`/`--take` handoff, consent-gated input; the xterm.js bridge client = the GUI terminal pane and the future Android-gateway shape). `spt-claude-code` scoping remains on deck alongside.