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

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


Now the CHANGELOG. Find the `[Unreleased]` section:


Bash

cd C:/Users/decid/Documents/projects/spt-core && grep -n "Unreleased\|## \[" CHANGELOG.md | head -6

Locate CHANGELOG Unreleased section


4:notices or does differently. The `## [<version>]` section of each release
10:## [Unreleased]
12:## [0.4.2] - 2026-06-11
18:## [0.4.1] - 2026-06-11
24:## [0.4.0] - 2026-06-10
35:## [0.3.2] - 2026-06-09


10	## [Unreleased]
11
12	## [0.4.2] - 2026-06-11

## [Unreleased]

## [0.4.2] - 2026-06-11


Edit

## [Unreleased]

- **Session digest re-founded on session logs (M9, ADR-0008 amendment).** The live activity buffer is now an on-demand projection of an endpoint's normalized session logs, not a parse of the PTY byte stream. The digest works topology-independently — a harness-hosted endpoint (e.g. Claude Code's per-session JSONL) gets a digest with no broker PTY. A new published **digest-record contract** (`{role, text, tool}`) is what a projectable adapter emits in its `[history]` lines (or pushes via the new **`spt api digest-entry`** door); the digest has no manifest seam of its own. The PTY-parse engine and the `[pty_digest]` manifest section are **removed**. `spt endpoint digest <id>` (snapshot pull) and `--follow` (structured-delta stream) are unchanged at the surface. (Autonomous near-realtime freshness is deferred to the consuming-frontend milestone; the delta-stream contract ships, driven by pulls / digest-entry pushes.)

## [0.4.2] - 2026-06-11


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

## [Unreleased]

- **Session digest re-founded on session logs (M9, ADR-0008 amendment).** The live activity buffer is now an on-demand projection of an endpoint's normalized session logs, not a parse of the PTY byte stream. The digest works topology-independently — a harness-hosted endpoint (e.g. Claude Code's per-session JSONL) gets a digest with no broker PTY. A new published **digest-record contract** (`{role, text, tool}`) is what a projectable adapter emits in its `[history]` lines (or pushes via the new **`spt api digest-entry`** door); the digest has no manifest seam of its own. The PTY-parse engine and the `[pty_digest]` manifest section are **removed**. `spt endpoint digest <id>` (snapshot pull) and `--follow` (structured-delta stream) are unchanged at the surface. (Autonomous near-realtime freshness is deferred to the consuming-frontend milestone; the delta-stream contract ships, driven by pulls / digest-entry pushes.)

## [0.4.2] - 2026-06-11

+- **Session digest re-founded on session logs (M9, ADR-0008 amendment).** The live activity buffer is now an on-demand projection of an endpoint's normalized session logs, not a parse of the PTY byte stream. The digest works topology-independently — a harness-hosted endpoint (e.g. Claude Code's per-session JSONL) gets a digest with no broker PTY. A new published **digest-record contract** (`{role, text, tool}`) is what a projectable adapter emits in its `[history]` lines (or pushes via the new **`spt api digest-entry`** door); the digest has no manifest seam of its own. The PTY-parse engine and the `[pty_digest]` manifest section are **removed**. `spt endpoint digest <id>` (snapshot pull) and `--follow` (structured-delta stream) are unchanged at the surface. (Autonomous near-realtime freshness is deferred to the consuming-frontend milestone; the delta-stream contract ships, driven by pulls / digest-entry pushes.)


Now the docs-site pages (G3 sweep). Find the `pty_digest` references in them:


docs-site\src\cli\reference.md:25:  endpoint  Endpoint operations: list, lifecycle, fork, digest, access
docs-site\src\cli\reference.md:1085:Endpoint operations: list, lifecycle, fork, digest, access.
docs-site\src\cli\reference.md:1101:  digest       Show a hosted session's live activity buffer (PTY digest)
docs-site\src\cli\reference.md:1271:### spt endpoint digest
docs-site\src\cli\reference.md:1274:Show a hosted session's live activity buffer (PTY digest).
docs-site\src\cli\reference.md:1279:Usage: spt endpoint digest [OPTIONS] <ID>
docs-site\src\self-update\overview.md:16:  release's artifact digests.
docs-site\src\self-update\overview.md:19:  from a trusted key *and* a matching artifact digest; an unverified binary
docs-site\src\harness-contract\integration-checklist.md:65:| **`[history]` strategy** (`fetcher` / `locate_normalize` / `native` + `api history-log`) | spt-core can read the session transcript — feeds the live digest and mind sync | RUN |
docs-site\src\harness-contract\integration-checklist.md:88:| **`[pty_digest]` patterns** (`input_pattern`, `agent_pattern`, `tool_patterns`, …) | A live activity digest extracted from the PTY byte stream — adapter-owned regexes; spt-core runs them, never parses your transcript | RUN |
docs-site\src\harness-contract\integration-checklist.md:93:`[pty_digest]` patterns so `spt endpoint digest` shows live tool calls; no shell
docs-site\src\harness-contract\manifest.md:191:## `[pty_digest]` — live activity buffer patterns
docs-site\src\harness-contract\manifest.md:195:activity digest (`spt endpoint digest <id>`). The patterns are opaque to spt-core —
docs-site\src\harness-contract\manifest.md:200:[pty_digest]
docs-site\src\harness-contract\manifest.md:212:section just means the digest is unavailable for this adapter.
docs-site\src\harness-contract\manifest.md:285:- `[pty_digest]`, when present, requires non-empty `input_pattern` and
docs-site\src\terminal\overview.md:18:- **The live digest** — with adapter-supplied `[pty_digest]` patterns,
docs-site\src\terminal\overview.md:19:  `spt endpoint digest <id>` shows an at-a-glance parsed view of what a session is
docs-site\src\terminal\overview.md:20:  doing now (`--follow` streams changes). Raw bytes and parsed digest are
docs-site\src\terminal\overview.md:31:`spt endpoint digest` · the attach surface · [`spt api` injection-adjacent calls](../harness-contract/api.md).


`cli/reference.md` is xtask-generated from the binary `--help` — update the source help string in cli.rs, then regenerate. Find it:


Grep

PTY digest|live activity buffer|Digest \{|Show a hosted session


287-    },
288:    /// Show a hosted session's live activity buffer (PTY digest).
--
291-    /// snapshot, or `--follow`s the delta-stream. Local endpoints only.
292:    Digest {
--
887-            EndpointCmd::Rename { old_id, new_id } => cmd_rename(&old_id, &new_id),
888:            EndpointCmd::Digest { id, follow, json } => cmd_digest(&id, follow, json),
--
921-
922:/// `spt digest <id>` — show a daemon-hosted session's live activity buffer
--
970-        Ok(None) => {
971:            eprintln!("NO_DIGEST:{id} has no live activity buffer (not a daemon-hosted session?)");
--

[Showing results with pagination = limit: 15]


288	    /// Show a hosted session's live activity buffer (PTY digest).
289	    ///
290	    /// The at-a-glance "what is this agent doing now" view. Pulls a
291	    /// snapshot, or `--follow`s the delta-stream. Local endpoints only.
292	    Digest {
293	        /// The (local) endpoint id to read.
294	        id: String,
295	        /// Stream live changes instead of a one-shot snapshot (Ctrl-C to stop).
296	        #[arg(long)]
297	        follow: bool,

    /// Show a hosted session's live activity buffer (PTY digest).
    ///
    /// The at-a-glance "what is this agent doing now" view. Pulls a
    /// snapshot, or `--follow`s the delta-stream. Local endpoints only.
    Digest {
        /// The (local) endpoint id to read.
        id: String,
        /// Stream live changes instead of a one-shot snapshot (Ctrl-C to stop).
        #[arg(long)]
        follow: bool,


Edit

    /// Show a hosted session's live activity buffer (PTY digest).
    ///
    /// The at-a-glance "what is this agent doing now" view. Pulls a
    /// snapshot, or `--follow`s the delta-stream. Local endpoints only.
    Digest {

    /// Show a session's live activity buffer (session digest).
    ///
    /// The at-a-glance "what is this agent doing now" view — a projection of the
    /// endpoint's normalized session logs. Pulls a snapshot, or `--follow`s the
    /// delta-stream. Local endpoints only.
    Digest {


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

    /// Show a hosted session's live activity buffer (PTY digest).
    ///
    /// The at-a-glance "what is this agent doing now" view. Pulls a
    /// snapshot, or `--follow`s the delta-stream. Local endpoints only.
    Digest {

    /// Show a session's live activity buffer (session digest).
    ///
    /// The at-a-glance "what is this agent doing now" view — a projection of the
    /// endpoint's normalized session logs. Pulls a snapshot, or `--follow`s the
    /// delta-stream. Local endpoints only.
    Digest {

/// The new (bare) id — charset-validated; `:`/`@` are reserved.

-    /// Show a hosted session's live activity buffer (PTY digest).

+    /// Show a session's live activity buffer (session digest).

-    /// The at-a-glance "what is this agent doing now" view. Pulls a

-    /// snapshot, or `--follow`s the delta-stream. Local endpoints only.

+    /// The at-a-glance "what is this agent doing now" view — a projection of the

+    /// endpoint's normalized session logs. Pulls a snapshot, or `--follow`s the

+    /// delta-stream. Local endpoints only.


Update the `cmd_digest` doc + the `NO_DIGEST` message (now log-projection, not daemon-hosted-only):


922
923	/// `spt digest <id>` — show a daemon-hosted session's live activity buffer
924	/// (REQ-TERM-4 / ADR-0008). Ensures the daemon is up, then pulls a snapshot or
925	/// follows the delta-stream over the digest-control channel. Local addressing
926	/// only: a qualified `[subnet:]id@node` form is refused (cross-node is M4).
927	// [impl->REQ-TERM-4]
928	fn cmd_digest(id: &str, follow: bool, json: bool) -> i32 {
929	    // Local addressing only — the qualified cross-node form is M4 (spt-net).
930	    if id.contains('@') || id.contains(':') {
931	        eprintln!(
932	            "CROSS_NODE_M4:{id} — qualified [subnet:]id@node addressing is M4, not yet supported"
933	        );
934	        return 1;
935	    }
936	    // The digest lives in the daemon; bring it up if absent (best-effort —
937	    // a start failure still tries the socket; announced when it transitions).
938	    let _ = ensure_daemon_announced();
939	    let name = spt_daemon::digest_socket_name();
940
941	    if follow {
942	        let res = spt_daemon::follow(&name, id, 0, |up| {
943	            if json {
944	                println!("{}", spt_daemon::update_to_json(up));
945	            } else {
946	                print!("{}", spt_daemon::render_update(up));
947	            }
948	            let _ = std::io::stdout().flush();
949	            true // stream until the connection ends / the user interrupts
950	        });
951	        return match res {
952	            Ok(()) => 0,
953	            Err(e) => {
954	                eprintln!("DIGEST_FOLLOW_FAIL:{id}: {e}");
955	                1
956	            }
957	        };
958	    }
959
960	    match spt_daemon::pull_snapshot(&name, id) {
961	        Ok(Some((digest, version))) => {
962	            if json {
963	                println!("{}", spt_daemon::digest_to_json(&digest));
964	            } else {
965	                print!("{}", spt_daemon::render_digest(&digest));
966	                let _ = std::io::stdout().flush();
967	            }
968	            eprintln!("DIGEST:{id} version={version}");
969	            0
970	        }
971	        Ok(None) => {
972	            eprintln!("NO_DIGEST:{id} has no live activity buffer (not a daemon-hosted session?)");
973	            1

/// `spt digest <id>` — show a daemon-hosted session's live activity buffer
/// (REQ-TERM-4 / ADR-0008). Ensures the daemon is up, then pulls a snapshot or
/// follows the delta-stream over the digest-control channel. Local addressing
/// only: a qualified `[subnet:]id@node` form is refused (cross-node is M4).
// [impl->REQ-TERM-4]
fn cmd_digest(id: &str, follow: bool, json: bool) -> i32 {
    // Local addressing only — the qualified cross-node form is M4 (spt-net).
    if id.contains('@') || id.contains(':') {
        eprintln!(
            "CROSS_NODE_M4:{id} — qualified [subnet:]id@node addressing is M4, not yet supported"
        );
        return 1;
    }
    // The digest lives in the daemon; bring it up if absent (best-effort —
    // a start failure still tries the socket; announced when it transitions).
    let _ = ensure_daemon_announced();
    let name = spt_daemon::digest_socket_name();

    if follow {
        let res = spt_daemon::follow(&name, id, 0, |up| {
            if json {
                println!("{}", spt_daemon::update_to_json(up));
            } else {
                print!("{}", spt_daemon::render_update(up));
            }
            let _ = std::io::stdout().flush();
            true // stream until the connection ends / the user interrupts
        });
        return match res {
            Ok(()) => 0,
            Err(e) => {
                eprintln!("DIGEST_FOLLOW_FAIL:{id}: {e}");
                1
            }
        };
    }

    match spt_daemon::pull_snapshot(&name, id) {
        Ok(Some((digest, version))) => {
            if json {
                println!("{}", spt_daemon::digest_to_json(&digest));
            } else {
                print!("{}", spt_daemon::render_digest(&digest));
                let _ = std::io::stdout().flush();
            }
            eprintln!("DIGEST:{id} version={version}");
            0
        }
        Ok(None) => {
            eprintln!("NO_DIGEST:{id} has no live activity buffer (not a daemon-hosted session?)");
            1


/// `spt digest <id>` — show a daemon-hosted session's live activity buffer
/// (REQ-TERM-4 / ADR-0008). Ensures the daemon is up, then pulls a snapshot or
/// follows the delta-stream over the digest-control channel. Local addressing
/// only: a qualified `[subnet:]id@node` form is refused (cross-node is M4).
// [impl->REQ-TERM-4]

/// `spt endpoint digest <id>` — show a session's live activity buffer
/// (REQ-TERM-4 / ADR-0008 amendment). Ensures the daemon is up, then pulls a
/// snapshot (projected on demand from the endpoint's normalized session logs) or
/// follows the delta-stream over the digest-control channel. Local addressing
/// only: a qualified `[subnet:]id@node` form is refused (cross-node is later).
// [impl->REQ-TERM-4]


/// `spt digest <id>` — show a daemon-hosted session's live activity buffer
/// (REQ-TERM-4 / ADR-0008). Ensures the daemon is up, then pulls a snapshot or
/// follows the delta-stream over the digest-control channel. Local addressing
/// only: a qualified `[subnet:]id@node` form is refused (cross-node is M4).
// [impl->REQ-TERM-4]

/// `spt endpoint digest <id>` — show a session's live activity buffer
/// (REQ-TERM-4 / ADR-0008 amendment). Ensures the daemon is up, then pulls a
/// snapshot (projected on demand from the endpoint's normalized session logs) or
/// follows the delta-stream over the digest-control channel. Local addressing
/// only: a qualified `[subnet:]id@node` form is refused (cross-node is later).
// [impl->REQ-TERM-4]

-/// `spt digest <id>` — show a daemon-hosted session's live activity buffer

-/// (REQ-TERM-4 / ADR-0008). Ensures the daemon is up, then pulls a snapshot or

+/// `spt endpoint digest <id>` — show a session's live activity buffer

+/// (REQ-TERM-4 / ADR-0008 amendment). Ensures the daemon is up, then pulls a

+/// snapshot (projected on demand from the endpoint's normalized session logs) or

-/// only: a qualified `[subnet:]id@node` form is refused (cross-node is M4).

+/// only: a qualified `[subnet:]id@node` form is refused (cross-node is later).

fn cmd_digest(id: &str, follow: bool, json: bool) -> i32 {

// Local addressing only — the qualified cross-node form is M4 (spt-net).


        Ok(None) => {
            eprintln!("NO_DIGEST:{id} has no live activity buffer (not a daemon-hosted session?)");
            1

        Ok(None) => {
            eprintln!("NO_DIGEST:{id} has no activity buffer (no session-log source / no records yet?)");
            1


        Ok(None) => {
            eprintln!("NO_DIGEST:{id} has no live activity buffer (not a daemon-hosted session?)");
            1

        Ok(None) => {
            eprintln!("NO_DIGEST:{id} has no activity buffer (no session-log source / no records yet?)");
            1

-            eprintln!("NO_DIGEST:{id} has no live activity buffer (not a daemon-hosted session?)");

+            eprintln!("NO_DIGEST:{id} has no activity buffer (no session-log source / no records yet?)");


Now the docs-site prose pages. Read the `manifest.md` `[pty_digest]` section:


189	injects a UUID the harness echoes back.
190
191	## `[pty_digest]` — live activity buffer patterns
192
193	For sessions spt-core hosts in its own terminal layer, the adapter may supply
194	regex patterns that segment the raw PTY byte stream into an at-a-glance
195	activity digest (`spt endpoint digest <id>`). The patterns are opaque to spt-core —
196	the same rule as command templates; spt-core compiles and runs them, never
197	understanding harness semantics.
198
199	```toml
200	[pty_digest]
201	input_pattern = "\\nUSER> "      # user-turn boundary (required)
202	agent_pattern = "\\nAGENT> "     # agent-turn boundary (required)
203	tool_patterns = [                 # optional: one regex per known tool,
204	  "(?P<name>Write)\\((?P<arg>[^)]*)\\)",   # naming `arg` (+ optional `name`)
205	]
206	catchall_pattern = "(?P<name>[A-Z][a-zA-Z]+)\\((?P<arg>[^)]*)\\)"  # optional
207	window_turns = 3                  # optional presentation override
208	persist = false                   # opt-in coarse activity log (default off)
209	```
210
211	Both boundary markers are required when the section is present; an absent
212	section just means the digest is unavailable for this adapter.
213
214	## `[update]` — adapter self-update

injects a UUID the harness echoes back.

## `[pty_digest]` — live activity buffer patterns

For sessions spt-core hosts in its own terminal layer, the adapter may supply
regex patterns that segment the raw PTY byte stream into an at-a-glance
activity digest (`spt endpoint digest <id>`). The patterns are opaque to spt-core —
the same rule as command templates; spt-core compiles and runs them, never
understanding harness semantics.

```toml
[pty_digest]
input_pattern = "\\nUSER> "      # user-turn boundary (required)
agent_pattern = "\\nAGENT> "     # agent-turn boundary (required)
tool_patterns = [                 # optional: one regex per known tool,
  "(?P<name>Write)\\((?P<arg>[^)]*)\\)",   # naming `arg` (+ optional `name`)
]
catchall_pattern = "(?P<name>[A-Z][a-zA-Z]+)\\((?P<arg>[^)]*)\\)"  # optional
window_turns = 3                  # optional presentation override
persist = false                   # opt-in coarse activity log (default off)
```

Both boundary markers are required when the section is present; an absent
section just means the digest is unavailable for this adapter.

## `[update]` — adapter self-update


## `[pty_digest]` — live activity buffer patterns

For sessions spt-core hosts in its own terminal layer, the adapter may supply
regex patterns that segment the raw PTY byte stream into an at-a-glance
activity digest (`spt endpoint digest <id>`). The patterns are opaque to spt-core —
the same rule as command templates; spt-core compiles and runs them, never
understanding harness semantics.

```toml
[pty_digest]
input_pattern = "\\nUSER> "      # user-turn boundary (required)
agent_pattern = "\\nAGENT> "     # agent-turn boundary (required)
tool_patterns = [                 # optional: one regex per known tool,
  "(?P<name>Write)\\((?P<arg>[^)]*)\\)",   # naming `arg` (+ optional `name`)
]
catchall_pattern = "(?P<name>[A-Z][a-zA-Z]+)\\((?P<arg>[^)]*)\\)"  # optional
window_turns = 3                  # optional presentation override
persist = false                   # opt-in coarse activity log (default off)
```

Both boundary markers are required when the section is present; an absent
section just means the digest is unavailable for this adapter.

## `[update]` — adapter self-update

## Session digest — the digest-record contract (no manifest section)

The live activity digest (`spt endpoint digest <id>`) is a **projection of the
endpoint's normalized session logs**, not a parse of the PTY byte stream. It has
**no manifest section of its own** — it rides your `[history]` strategy. What you
publish against is the **digest-record contract**: emit your normalized history
lines (or push them with `spt api digest-entry`) as JSON objects spt-core can
project:

```json
{"role": "input", "text": "add a file"}
{"role": "agent", "text": "on it"}
{"role": "tool",  "tool": {"name": "Write", "arg": "src/a.rs"}}
```

- `role` ∈ `input` | `agent` | `tool` (the source tag).
- `text` — the input / agent span (omitted for `tool`).
- `tool` — `{name, arg}`, present iff `role == "tool"`; consecutive tool records
  collapse into one sprint.

Unknown fields are ignored; an unparseable line is skipped. The history subsystem
still treats your lines as opaque (it carries them verbatim) — this contract is the
digest's own overlay on their content. Presentation (last ~3 turns, ~25-char arg
truncation) is spt-core's; extraction is yours. An adapter whose logs don't carry
the contract simply gets no digest (or pushes entries via `api digest-entry`).

## `[update]` — adapter self-update


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

## `[pty_digest]` — live activity buffer patterns

For sessions spt-core hosts in its own terminal layer, the adapter may supply
regex patterns that segment the raw PTY byte stream into an at-a-glance
activity digest (`spt endpoint digest <id>`). The patterns are opaque to spt-core —
the same rule as command templates; spt-core compiles and runs them, never
understanding harness semantics.

```toml
[pty_digest]
input_pattern = "\\nUSER> "      # user-turn boundary (required)
agent_pattern = "\\nAGENT> "     # agent-turn boundary (required)
tool_patterns = [                 # optional: one regex per known tool,
  "(?P<name>Write)\\((?P<arg>[^)]*)\\)",   # naming `arg` (+ optional `name`)
]
catchall_pattern = "(?P<name>[A-Z][a-zA-Z]+)\\((?P<arg>[^)]*)\\)"  # optional
window_turns = 3                  # optional presentation override
persist = false                   # opt-in coarse activity log (default off)
```

Both boundary markers are required when the section is present; an absent
section just means the digest is unavailable for this adapter.

## `[update]` — adapter self-update

## Session digest — the digest-record contract (no manifest section)

The live activity digest (`spt endpoint digest <id>`) is a **projection of the
endpoint's normalized session logs**, not a parse of the PTY byte stream. It has
**no manifest section of its own** — it rides your `[history]` strategy. What you
publish against is the **digest-record contract**: emit your normalized history
lines (or push them with `spt api digest-entry`) as JSON objects spt-core can
project:

```json
{"role": "input", "text": "add a file"}
{"role": "agent", "text": "on it"}
{"role": "tool",  "tool": {"name": "Write", "arg": "src/a.rs"}}
```

- `role` ∈ `input` | `agent` | `tool` (the source tag).
- `text` — the input / agent span (omitted for `tool`).
- `tool` — `{name, arg}`, present iff `role == "tool"`; consecutive tool records
  collapse into one sprint.

Unknown fields are ignored; an unparseable line is skipped. The history subsystem
still treats your lines as opaque (it carries them verbatim) — this contract is the
digest's own overlay on their content. Presentation (last ~3 turns, ~25-char arg
truncation) is spt-core's; extraction is yours. An adapter whose logs don't carry
the contract simply gets no digest (or pushes entries via `api digest-entry`).

## `[update]` — adapter self-update

`parent_ancestor_name` as the process-tree anchor. `uuid_inject`: spt-core

-## `[pty_digest]` — live activity buffer patterns

+## Session digest — the digest-record contract (no manifest section)

-For sessions spt-core hosts in its own terminal layer, the adapter may supply

-regex patterns that segment the raw PTY byte stream into an at-a-glance

-activity digest (`spt endpoint digest <id>`). The patterns are opaque to spt-core —

-the same rule as command templates; spt-core compiles and runs them, never

+The live activity digest (`spt endpoint digest <id>`) is a **projection of the

+endpoint's normalized session logs**, not a parse of the PTY byte stream. It has

+**no manifest section of its own** — it rides your `[history]` strategy. What you

+publish against is the **digest-record contract**: emit your normalized history

+lines (or push them with `spt api digest-entry`) as JSON objects spt-core can

-input_pattern = "\\nUSER> "      # user-turn boundary (required)

-agent_pattern = "\\nAGENT> "     # agent-turn boundary (required)

-tool_patterns = [                 # optional: one regex per known tool,

-  "(?P<name>Write)\\((?P<arg>[^)]*)\\)",   # naming `arg` (+ optional `name`)

-catchall_pattern = "(?P<name>[A-Z][a-zA-Z]+)\\((?P<arg>[^)]*)\\)"  # optional

-window_turns = 3                  # optional presentation override

-persist = false                   # opt-in coarse activity log (default off)

+{"role": "input", "text": "add a file"}

+{"role": "tool",  "tool": {"name": "Write", "arg": "src/a.rs"}}

-Both boundary markers are required when the section is present; an absent

-section just means the digest is unavailable for this adapter.

+- `role` ∈ `input` | `agent` | `tool` (the source tag).

+- `text` — the input / agent span (omitted for `tool`).

+- `tool` — `{name, arg}`, present iff `role == "tool"`; consecutive tool records

+Unknown fields are ignored; an unparseable line is skipped. The history subsystem

+still treats your lines as opaque (it carries them verbatim) — this contract is the

+digest's own overlay on their content. Presentation (last ~3 turns, ~25-char arg

+truncation) is spt-core's; extraction is yours. An adapter whose logs don't carry

+the contract simply gets no digest (or pushes entries via `api digest-entry`).

How spt-core updates (and first installs — install is the first update) this


Fix the validation note at manifest.md:285:


282	  **must not** have one.
283	- `[history] strategy = "fetcher"` requires `fetcher`;
284	  `locate_normalize` requires both `locate_template` and `normalize_command`.
285	- `[env.*] direction = "inject"` requires a `value`.
286	- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
287	  `repo` **and** `signing_key`.
288	- `[pty_digest]`, when present, requires non-empty `input_pattern` and
289	  `agent_pattern`.
290
291	A violation is a one-line error naming the field — fix and re-add.

  **must not** have one.
- `[history] strategy = "fetcher"` requires `fetcher`;
  `locate_normalize` requires both `locate_template` and `normalize_command`.
- `[env.*] direction = "inject"` requires a `value`.
- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
  `repo` **and** `signing_key`.
- `[pty_digest]`, when present, requires non-empty `input_pattern` and
  `agent_pattern`.

A violation is a one-line error naming the field — fix and re-add.


- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
  `repo` **and** `signing_key`.
- `[pty_digest]`, when present, requires non-empty `input_pattern` and
  `agent_pattern`.

A violation is a one-line error naming the field — fix and re-add.

- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
  `repo` **and** `signing_key`.

A violation is a one-line error naming the field — fix and re-add.


- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
  `repo` **and** `signing_key`.
- `[pty_digest]`, when present, requires non-empty `input_pattern` and
  `agent_pattern`.

A violation is a one-line error naming the field — fix and re-add.

- `[update] avenue = "delegated"` requires `command`; `file_pull` requires
  `repo` **and** `signing_key`.

A violation is a one-line error naming the field — fix and re-add.

- `[env.*] direction = "inject"` requires a `value`.

-- `[pty_digest]`, when present, requires non-empty `input_pattern` and


Now `terminal/overview.md:18-20` + `integration-checklist.md:65,88,93`:


14	  reconnects resume the stream without gaps or duplicates.
15	- **Input injection** — `send-keys`/`send-line` style injection per the
16	  adapter's declared `[inject]` methods, respecting activity state (never
17	  disrupt a working agent).
18	- **The live digest** — with adapter-supplied `[pty_digest]` patterns,
19	  `spt endpoint digest <id>` shows an at-a-glance parsed view of what a session is
20	  doing now (`--follow` streams changes). Raw bytes and parsed digest are
21	  two channels over the same session surface.
22	- **Update immunity** — PTYs live in the broker, logic in the brain; a
23	  self-update swaps the brain while every hosted process and byte stream
24	  stays intact.
25
26	Activity and idleness are always **reported** (`api state busy|idle`), never
27	inferred from terminal quiescence — quiet terminals lie.
28
29	## Commands
30
31	`spt endpoint digest` · the attach surface · [`spt api` injection-adjacent calls](../harness-contract/api.md).
32
33	*Deeper tutorial coming with the docs' next tier.*

  reconnects resume the stream without gaps or duplicates.
- **Input injection** — `send-keys`/`send-line` style injection per the
  adapter's declared `[inject]` methods, respecting activity state (never
  disrupt a working agent).
- **The live digest** — with adapter-supplied `[pty_digest]` patterns,
  `spt endpoint digest <id>` shows an at-a-glance parsed view of what a session is
  doing now (`--follow` streams changes). Raw bytes and parsed digest are
  two channels over the same session surface.
- **Update immunity** — PTYs live in the broker, logic in the brain; a
  self-update swaps the brain while every hosted process and byte stream
  stays intact.

Activity and idleness are always **reported** (`api state busy|idle`), never
inferred from terminal quiescence — quiet terminals lie.

## Commands

`spt endpoint digest` · the attach surface · [`spt api` injection-adjacent calls](../harness-contract/api.md).

*Deeper tutorial coming with the docs' next tier.*


- **The live digest** — with adapter-supplied `[pty_digest]` patterns,
  `spt endpoint digest <id>` shows an at-a-glance parsed view of what a session is
  doing now (`--follow` streams changes). Raw bytes and parsed digest are
  two channels over the same session surface.

- **The live digest** — `spt endpoint digest <id>` shows an at-a-glance view of
  what a session is doing now (`--follow` streams changes), **projected from the
  endpoint's normalized session logs** (the digest-record contract over
  `[history]`), never the PTY byte stream. Topology-independent — it works for a
  harness-hosted endpoint with no broker PTY.


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

- **The live digest** — `spt endpoint digest <id>` shows an at-a-glance view of
  what a session is doing now (`--follow` streams changes), **projected from the
  endpoint's normalized session logs** (the digest-record contract over
  `[history]`), never the PTY byte stream. Topology-independent — it works for a
  harness-hosted endpoint with no broker PTY.

# Terminal hosting

spt-core can *own* agent sessions in its own terminal layer: the daemon's
broker holds a real PTY per hosted session, which is what makes sessions
supervisable, attachable from other machines, and immune to self-update.

## What the broker holding the PTY buys

- **spt-hosted startup** — spt-core spawns sessions itself from the
  manifest's `[session.self]` template and binds them (`api bind`), instead
  of waiting inside someone else's process tree.
- **Remote attach** — a byte-stream viewport onto a live session from any
  paired node (compute and files stay on the hosting node). Restart-safe:
  reconnects resume the stream without gaps or duplicates.
- **Input injection** — `send-keys`/`send-line` style injection per the
  adapter's declared `[inject]` methods, respecting activity state (never
  disrupt a working agent).
- **The live digest** — with adapter-supplied `[pty_digest]` patterns,
  `spt endpoint digest <id>` shows an at-a-glance parsed view of what a session is
  doing now (`--follow` streams changes). Raw bytes and parsed digest are
  two channels over the same session surface.
- **Update immunity** — PTYs live in the broker, logic in the brain; a
  self-update swaps the brain while every hosted process and byte stream
  stays intact.

Activity and idleness are always **reported** (`api state busy|idle`), never
inferred from terminal quiescence — quiet terminals lie.

## Commands

`spt endpoint digest` · the attach surface · [`spt api` injection-adjacent calls](../harness-contract/api.md).

*Deeper tutorial coming with the docs' next tier.*

-- **The live digest** — with adapter-supplied `[pty_digest]` patterns,

-  `spt endpoint digest <id>` shows an at-a-glance parsed view of what a session is

-  doing now (`--follow` streams changes). Raw bytes and parsed digest are

-  two channels over the same session surface.

+- **The live digest** — `spt endpoint digest <id>` shows an at-a-glance view of

+  what a session is doing now (`--follow` streams changes), **projected from the

+  endpoint's normalized session logs** (the digest-record contract over

+  `[history]`), never the PTY byte stream. Topology-independent — it works for a

+  harness-hosted endpoint with no broker PTY.


62	| **`[inject]` channels** (`activity` / `idle`) + **`api poll <id> --include-deferred`** | Inbound message delivery. Declares HOW spt-core reaches the agent (hook inject vs. pull-relay); `poll` is the pull path for hooks that can't inject | RUN |
63	| **Honest `can_inject` per hook** | Lets spt-core route around a hook that can't surface text — the load-bearing harness-varying fact | RUN |
64	| **`api boundary <clear\|compact> <id> --to-session-id <sid>`** | The endpoint's identity, spool, and history survive a context reset under a new session id | BOUNDARY |
65	| **`[history]` strategy** (`fetcher` / `locate_normalize` / `native` + `api history-log`) | spt-core can read the session transcript — feeds the live digest and mind sync | RUN |
66	| **`[identity]`** (`session_id_source`, `parent_ancestor_name`) | Post-spawn id resolution when the harness mints the session id itself | START |
67	| **`[env.*]` bridge** (e.g. `OWL_SESSION_ID`) | The session learns its own endpoint id / context the harness must inject | START |
68	| **`[update]` avenue + command** | Ripple-update: spt-core refreshes your adapter alongside its own self-update (REQ-UPD-5); also the install-on-demand bootstrap | KEEP-CURRENT |
69
70	**claude-code:** `Idle` hook → `api state idle`; messages arrive over the hook
71	inject channel (`can_inject = true`), pull-relay fallback when busy.
72	`PreCompact`/clear hooks → `api boundary`. `[history] strategy = "fetcher"`
73	(Claude Code's transcript is a binary the fetcher reads). `[update] avenue =
74	"delegated"`, `command = "claude plugin update spt"` — the harness's own
75	updater is the avenue.
76
77	---
78
79	## Group 3 — Optional (capability-specific)
80
81	Reach for these when the capability applies; ignore them otherwise.
82
83	| Surface | Feature it buys | Lifecycle stage |
84	| --- | --- | --- |
85	| **`api shutdown <id>`** | Graceful signoff — runs the final echo-commune BEFORE teardown so the context delta is never lost to ordering | END |
86	| **`api presence <id>` / `api driven-by <id>`** | Most-recently-active resolution across the subnet; lets a session tell local input from remote-drive | RUN |
87	| **Workers** (`api worker-start <parent> <id>`, `worker-poll`, `worker-stop`) | Nested, short-lived sub-agents under a parent endpoint | RUN |
88	| **`[pty_digest]` patterns** (`input_pattern`, `agent_pattern`, `tool_patterns`, …) | A live activity digest extracted from the PTY byte stream — adapter-owned regexes; spt-core runs them, never parses your transcript | RUN |
89	| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
90	| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |
91
92	**claude-code:** uses `api shutdown` for graceful `/signoff`; declares
93	`[pty_digest]` patterns so `spt endpoint digest` shows live tool calls; no shell
94	body (it is a harness, not a driven surface).
95
96	---

| **`[inject]` channels** (`activity` / `idle`) + **`api poll <id> --include-deferred`** | Inbound message delivery. Declares HOW spt-core reaches the agent (hook inject vs. pull-relay); `poll` is the pull path for hooks that can't inject | RUN |
| **Honest `can_inject` per hook** | Lets spt-core route around a hook that can't surface text — the load-bearing harness-varying fact | RUN |
| **`api boundary <clear\|compact> <id> --to-session-id <sid>`** | The endpoint's identity, spool, and history survive a context reset under a new session id | BOUNDARY |
| **`[history]` strategy** (`fetcher` / `locate_normalize` / `native` + `api history-log`) | spt-core can read the session transcript — feeds the live digest and mind sync | RUN |
| **`[identity]`** (`session_id_source`, `parent_ancestor_name`) | Post-spawn id resolution when the harness mints the session id itself | START |
| **`[env.*]` bridge** (e.g. `OWL_SESSION_ID`) | The session learns its own endpoint id / context the harness must inject | START |
| **`[update]` avenue + command** | Ripple-update: spt-core refreshes your adapter alongside its own self-update (REQ-UPD-5); also the install-on-demand bootstrap | KEEP-CURRENT |

**claude-code:** `Idle` hook → `api state idle`; messages arrive over the hook
inject channel (`can_inject = true`), pull-relay fallback when busy.
`PreCompact`/clear hooks → `api boundary`. `[history] strategy = "fetcher"`
(Claude Code's transcript is a binary the fetcher reads). `[update] avenue =
"delegated"`, `command = "claude plugin update spt"` — the harness's own
updater is the avenue.

---

## Group 3 — Optional (capability-specific)

Reach for these when the capability applies; ignore them otherwise.

| Surface | Feature it buys | Lifecycle stage |
| --- | --- | --- |
| **`api shutdown <id>`** | Graceful signoff — runs the final echo-commune BEFORE teardown so the context delta is never lost to ordering | END |
| **`api presence <id>` / `api driven-by <id>`** | Most-recently-active resolution across the subnet; lets a session tell local input from remote-drive | RUN |
| **Workers** (`api worker-start <parent> <id>`, `worker-poll`, `worker-stop`) | Nested, short-lived sub-agents under a parent endpoint | RUN |
| **`[pty_digest]` patterns** (`input_pattern`, `agent_pattern`, `tool_patterns`, …) | A live activity digest extracted from the PTY byte stream — adapter-owned regexes; spt-core runs them, never parses your transcript | RUN |
| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |
| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |

**claude-code:** uses `api shutdown` for graceful `/signoff`; declares
`[pty_digest]` patterns so `spt endpoint digest` shows live tool calls; no shell
body (it is a harness, not a driven surface).

---


| **`[pty_digest]` patterns** (`input_pattern`, `agent_pattern`, `tool_patterns`, …) | A live activity digest extracted from the PTY byte stream — adapter-owned regexes; spt-core runs them, never parses your transcript | RUN |

| **Digest-record contract** in your `[history]` lines (or `api digest-entry`) | A live activity digest (`spt endpoint digest`) projected from your normalized session logs — emit records as `{role, text, tool}`. No manifest section; it rides `[history]`, topology-independent | RUN |


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

| **`api shutdown <id>`** | Graceful signoff — runs the final echo-commune BEFORE teardown so the context delta is never lost to ordering | END |

| **`api presence <id>` / `api driven-by <id>`** | Most-recently-active resolution across the subnet; lets a session tell local input from remote-drive | RUN |

| **Workers** (`api worker-start <parent> <id>`, `worker-poll`, `worker-stop`) | Nested, short-lived sub-agents under a parent endpoint | RUN |

-| **`[pty_digest]` patterns** (`input_pattern`, `agent_pattern`, `tool_patterns`, …) | A live activity digest extracted from the PTY byte stream — adapter-owned regexes; spt-core runs them, never parses your transcript | RUN |

+| **Digest-record contract** in your `[history]` lines (or `api digest-entry`) | A live activity digest (`spt endpoint digest`) projected from your normalized session logs — emit records as `{role, text, tool}`. No manifest section; it rides `[history]`, topology-independent | RUN |

| **`[session.notif]` template** | Native OS notification render (toast / shell alert) for consent + capability prompts, instead of burying them in agent output | RUN |

| **Shell surfaces** (`kind = "shell"`: `api bind-shell --link`, `api emit`, `api owner-shutdown`, the `[shell]` body) | Driven surfaces — notifiers, sensors, power buttons — authenticated by the launch link token alone. See [Shells](../shells/overview.md) | START / RUN |


**claude-code:** uses `api shutdown` for graceful `/signoff`; declares
`[pty_digest]` patterns so `spt endpoint digest` shows live tool calls; no shell
body (it is a harness, not a driven surface).

**claude-code:** uses `api shutdown` for graceful `/signoff`; its `[history]`
records carry the digest-record contract so `spt endpoint digest` shows live tool
calls (the per-session JSONL is Path A's worked example); no shell body (it is a
harness, not a driven surface).


**claude-code:** uses `api shutdown` for graceful `/signoff`; declares
`[pty_digest]` patterns so `spt endpoint digest` shows live tool calls; no shell
body (it is a harness, not a driven surface).

**claude-code:** uses `api shutdown` for graceful `/signoff`; its `[history]`
records carry the digest-record contract so `spt endpoint digest` shows live tool
calls (the per-session JSONL is Path A's worked example); no shell body (it is a
harness, not a driven surface).

-**claude-code:** uses `api shutdown` for graceful `/signoff`; declares

-`[pty_digest]` patterns so `spt endpoint digest` shows live tool calls; no shell

-body (it is a harness, not a driven surface).

+**claude-code:** uses `api shutdown` for graceful `/signoff`; its `[history]`

+records carry the digest-record contract so `spt endpoint digest` shows live tool

+calls (the per-session JSONL is Path A's worked example); no shell body (it is a


Docs prose updated. Regenerate the CLI reference + run the docs drift gate:


Bash

cd C:/Users/decid/Documents/projects/spt-core && cargo run -q -p xtask -- gen 2>&1 | tail -5 && echo "=== check ===" && cargo run -q -p xtask -- check 2>&1 | tail -8

Regenerate CLI reference + drift check


xtask gen: wrote C:\Users\decid\Documents\projects\spt-core\docs-site/src/cli/reference.md
=== check ===
xtask check: OK


xtask gen + check OK. Update the plan status (T7b done), then final validation + commit 4:


Edit

- **T7b** ⏳ — the atomic re-wire + retire + REQ re-point. **Blocked on a scope
  ruling (see Discovery).**

- **T7b core** ✅ `5a09577` — on-demand projector + hub `project_and_publish` +
  reproject verb + handlers + removed `run_digest_feed` + CC-fixture E2E.
- **T7b retire+re-point** ✅ (this commit) — deleted the spt-term parser +
  `PtyDigest` + `[pty_digest]` (schema re-blessed); retitled REQ-TERM-4 (honest
  log-source title) with its `impl`/`unit`/`int` tags on the projection + E2E;
  migrated the native fixture to the contract; MANIFEST/CONTEXT/ADR-0008/docs-site
  /CHANGELOG swept; deferred autonomous-freshness + Option-C re-home logged to
  `docs/DEFERRED.md`. **G3 ready** — `traceable-reqs check` EXIT=0, clippy -D +
  workspace tests green, `xtask check` OK. doyle pinged for the G3 gate.


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

- **T7b** ⏳ — the atomic re-wire + retire + REQ re-point. **Blocked on a scope
  ruling (see Discovery).**

- **T7b core** ✅ `5a09577` — on-demand projector + hub `project_and_publish` +
  reproject verb + handlers + removed `run_digest_feed` + CC-fixture E2E.
- **T7b retire+re-point** ✅ (this commit) — deleted the spt-term parser +
  `PtyDigest` + `[pty_digest]` (schema re-blessed); retitled REQ-TERM-4 (honest
  log-source title) with its `impl`/`unit`/`int` tags on the projection + E2E;
  migrated the native fixture to the contract; MANIFEST/CONTEXT/ADR-0008/docs-site
  /CHANGELOG swept; deferred autonomous-freshness + Option-C re-home logged to
  `docs/DEFERRED.md`. **G3 ready** — `traceable-reqs check` EXIT=0, clippy -D +
  workspace tests green, `xtask check` OK. doyle pinged for the G3 gate.

contract + `project`/`project_lines` + 8 units. Additive; parser still green.

- **T7a commit 2** ✅ `6033326` — `api digest-entry` push door +

`spt_store::history::append_digest_entry` (separate `digest.log`) + units.

-- **T7b** ⏳ — the atomic re-wire + retire + REQ re-point. **Blocked on a scope

+- **T7b core** ✅ `5a09577` — on-demand projector + hub `project_and_publish` +

+- **T7b retire+re-point** ✅ (this commit) — deleted the spt-term parser +

+  log-source title) with its `impl`/`unit`/`int` tags on the projection + E2E;

+  migrated the native fixture to the contract; MANIFEST/CONTEXT/ADR-0008/docs-site

+  /CHANGELOG swept; deferred autonomous-freshness + Option-C re-home logged to

+  `docs/DEFERRED.md`. **G3 ready** — `traceable-reqs check` EXIT=0, clippy -D +

### Discovery (affects T7b scope — surfaced to doyle 2026-06-13)


Final full validation before the atomic retire commit:


Bash

cd C:/Users/decid/Documents/projects/spt-core && echo "=== clippy ===" && cargo clippy --workspace --all-targets -- -D warnings 2>&1 | grep -E "error|warning:|Finished" | tail -3 && echo "=== tests ===" && cargo test --workspace 2>&1 | grep -E "error\[|panicked|FAILED|test result: FAILED" | head -10 && echo "(no failures above = pass)" && echo "=== traceable ===" && traceable-reqs check >/dev/null 2>&1; echo "EXIT=$?"

Final workspace validation


Command running in background with ID: bdct9wt8b. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bdct9wt8b.output. You will be notified when it completes. To check interim output, use Read on that file path.


Validation running in background. I'll wait for it to complete before the atomic commit.


cd C:/Users/decid/Documents/projects/spt-core && f="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-core/c570fe63-c413-4a17-bb14-ee75e02a53bd/tasks/bdct9wt8b.output"; for i in $(seq 1 40); do if grep -q "EXIT=" "$f" 2>/dev/null; then break; fi; sleep 6; done; cat "$f"

Wait for and read validation result


<task-notification>
<task-id>bdct9wt8b</task-id>
<tool-use-id>toolu_01WXPUKVk3XiTKBw5zdgKJdM</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bdct9wt8b.output</output-file>
<status>completed</status>
<summary>Background command "Final workspace validation" completed (exit code 0)</summary>
</task-notification>


=== clippy ===
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.40s
=== tests ===
(no failures above = pass)
=== traceable ===
EXIT=0


Prompt:
<task-notification>
<task-id>bdct9wt8b</task-id>
<tool-use-id>toolu_01WXPUKVk3XiTKBw5zdgKJdM</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bdct9wt8b.output</output-file>
<status>completed</status>
<summary>Background command "Final workspace validation" completed (exit code 0)</summary>
</task-notification>


All green: clippy clean, workspace tests pass (no failures), traceable EXIT=0. Commit 4 — the atomic retire + REQ-TERM-4 re-point + docs:


cd C:/Users/decid/Documents/projects/spt-core && git add -A && git status --short | head -25 && echo "---" && git commit -q -m "$(cat <<'EOF'
refactor(digest): retire the PTY-parse engine + re-point REQ-TERM-4 to the log source (T7b)

The atomic retire + restoration-style re-point that completes M9 Wave 3 (ADR-0008
amendment). The new log-source projection (T7a/T7b-core) is already in place and
evidences REQ-TERM-4, so the superseded shape is removed in one change — the tree
is never left un-evidenced.

Retired:
- spt-term: the `DigestParser` / `DigestPatterns` / `DigestError` regex-over-bytes
  primitive + its PTY integration test (the `regex` dep drops). The digest output
  types (Digest/Turn/DigestEntry/ToolUse) + DigestConfig stay; the parser-only
  presentation knobs (ANSI strip, scan cap) go.
- spt-runtime: the `PtyDigest` struct + the `[pty_digest]` manifest field +
  validation + tests; the manifest JSON schema re-blessed.

Re-point (same commit the old evidence dies):
- REQ-TERM-4 retitled to the honest log-source scope ("projection of normalized
  session logs, snapshot-pull + structured-delta-stream contract + api
  digest-entry push, opt-in Path-B log"); its impl/unit/int tags now sit on the
  projection + the CC-fixture E2E. `traceable-reqs check` EXIT=0.
- The stray native history fixture migrated to the digest-record contract shape
  (one schema in the tree; history stays opaque — cosmetic, semantics unchanged).

Docs sweep (G3): MANIFEST + docs-site replace `[pty_digest]` with the published
digest-record contract; CONTEXT + ADR-0008 mark the implementation debt
discharged; the CLI help/reference drop "PTY digest"; CHANGELOG `[Unreleased]`.
The deferred autonomous file-watch freshness + Option-C persistence re-home are
logged to docs/DEFERRED.md (consuming-frontend milestone), not deleted.

// [impl->REQ-TERM-4] // [unit->REQ-TERM-4] // [int->REQ-TERM-4] // [doc->REQ-TERM-4]

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF
)" && echo "--- commit 4 done ---" && git log --oneline -6

Commit T7b retire + REQ re-point


warning: in the working copy of 'Cargo.lock', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'M9-WAVE3-JIT.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-runtime/manifest.schema.json', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-term/src/digest.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-term/src/projection.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs-site/src/cli/reference.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs/DEFERRED.md', LF will be replaced by CRLF the next time Git touches it
M  CHANGELOG.md
M  CONTEXT.md
M  Cargo.lock
M  M9-WAVE3-JIT.md
M  crates/spt-daemon/Cargo.toml
M  crates/spt-live/src/history.rs
M  crates/spt-runtime/manifest.schema.json
M  crates/spt-runtime/src/lib.rs
M  crates/spt-runtime/src/manifest.rs
M  crates/spt-term/Cargo.toml
M  crates/spt-term/src/digest.rs
M  crates/spt-term/src/lib.rs
M  crates/spt-term/src/projection.rs
D  crates/spt-term/tests/digest.rs
M  crates/spt/src/cli.rs
M  docs-site/src/cli/reference.md
M  docs-site/src/harness-contract/integration-checklist.md
M  docs-site/src/harness-contract/manifest.md
M  docs-site/src/terminal/overview.md
M  docs/DEFERRED.md
M  docs/MANIFEST.md
M  docs/adr/0008-live-activity-buffer-pty-digest.md
M  traceable-reqs.toml
---
--- commit 4 done ---
ba8e3c1 refactor(digest): retire the PTY-parse engine + re-point REQ-TERM-4 to the log source (T7b)
5a09577 feat(daemon): on-demand session-digest log projection + push-driven deltas (T7b core)
76ff4a3 docs(m9): Wave3 status — T7a landed; T7b scope discovery (byte-feed never live-wired)
6033326 feat(api): digest-entry push door for log-less adapters (T7a)
82ead73 feat(term): session-digest log projection primitive + published record contract (T7a)
714f6b8 docs(m9): Wave 3 / G3 JIT plan — digest log-projection (doyle-vetted)


**T7b complete — Wave 3 / G3 fully implemented (4 commits, all green).** Now the T5-twohost side-check doyle asked for, then ping for the G3 gate. Verify the T5 twohost honored-path int is logged:


cd C:/Users/decid/Documents/projects/spt-core && echo "=== REQ-MSG-6 in toml ===" && grep -n -A2 "REQ-MSG-6" traceable-reqs.toml | head -8; echo "=== T5 twohost mentions ===" && grep -rn -i "twohost.*honored\|honored-path\|T5.*twohost\|lecturn" M9-PLAN.md ROADMAP.md 2>/dev/null | head -6

Check T5 twohost honored-path logging


=== REQ-MSG-6 in toml ===
175:title = "Gateway type acceptance: a Gateway-typed perch binds (api bind --type, open type system — un-hardcode the live_agent default), advertises/addressable like any endpoint, owns shells (owner validation not agent-family-gated), subscribes to digests, and is the user-msg identity gate's user-backed origin (REQ-MSG-5); in-tree mock-gateway fixture (R-DOCS-2 pattern, no downstream adapter code). Cross-node WAN Gateway-origin (registry endpoint_type trust) tracked by REQ-MSG-6"
176-required_stages = ["doc", "impl", "unit"]  # activated 2026-06-13: M9-T5 (CONTEXT.md §Gateway; api bind --type un-hardcode at establish_perch; gateway-owns-shell + local user-backed-origin E2E via mock-gateway fixture; the WAN fail-closed row tested at the receive_wan funnel)
177-[[requirements]]
--
278:id = "REQ-MSG-6"
279-title = "cross-node Gateway user-msg honored via advertised endpoint_type: a user-msg from a Gateway-typed origin survives the receive_wan funnel as user-msg (vs the fail-closed re-stamp), keyed on the QUIC-handshake-proven origin node (never wire `from`). Trust boundary = subnet membership (operator-ratified 2026-06-13); no defense against an in-subnet member forging the type. Instance.endpoint_type is an additive serde-default field extending REQ-INST-7's data model. Absent/unknown type → re-stamp (N-1 rollout grace)"
280-required_stages = ["doc", "impl", "unit"]  # activated 2026-06-13: M9 cross-node Gateway WAN. doc = CONTEXT.md §Gateway posture rewrite (commit 30fea7f); impl = Instance.endpoint_type field (T1) + advertise populate (T2) + receive_wan origin resolve (T3); unit = the origin matrix in wan.rs + advertise-populate assertion (T4). doyle gate PASS 2026-06-13 (PR #9). INT DEFERRED, REQUIRED-BEFORE-LECTURN (tracked gate condition): the T5 twohost HONORED-path int (Gateway advert on A → user-msg honored at B; agent advert → re-stamped) must land before spt-lecturn ships a real cross-node Gateway — the negative path is already E2E-tested (wanmsg funnel), but the positive wire path has no production exerciser until then. Activate `int` when adding it.
=== T5 twohost mentions ===
M9-PLAN.md:131:no lecturn code in spt-core, per the adapter boundary). Evidence:
M9-PLAN.md:210:- **Don't expand scope**: no shell work (M10), no rc (M11), no lecturn code,
ROADMAP.md:45:- **M7 — Subnet & quickstart UX. ✅ delivered (2026-06-06** — planned 2026-06-05, task breakdown [`M7-PLAN.md`](./M7-PLAN.md)). Subnet setup is now a first-class, guided, secure product surface and the public quickstart is honest about personas. Shipped: the **join ceremony's product surface** (always-on daemon pairing responder + guided `spt subnet join` CLI initiator over the rig-proven SPAKE2 wire) · the `spt subnet` noun namespace (`status [NAME] [--nodes]` / `create` / `show-code` / `join`; `spt pair` deleted) · **elevation-gated membership mutations** (ADR-0005 amendment) · **node labels** (hostname-default, gossiped, addressable in `@node` qualifiers, refuse-on-ambiguity) · `spt poll` removed (`ready` + `ready --once`) · **`spt how-to <topic>`** in-binary agent guidance · quickstart rewritten as human-pairs / agents-message prompt blocks · README/docs-site sweep + `quickstart_e2e` + the twohost ladder's product-surface join rung. **Acceptance ran on REAL hardware:** fresh two-machine setup via `subnet create` + the user's own fully-guided join (elevation prompts honored), `status --nodes` labeled+online both sides, cross-machine `SENT(WAN)` both directions (HFENDULEAM ↔ gravity-linux). The run surfaced two publish-blocking field bugs, **both fixed at closeout**: elevated daemon spawn → the de-elevation seam (KNOWN-HAZARDS 5.7) and immortal ghost registry rows → silent-peer eviction (KNOWN-HAZARDS 4.10), plus refusals now rendering copy-paste qualified targets. Harvest (Linux elevation model, NTP-first clock, firewall registration at install, post-join address seeding, trust-prune verb, M8 nounification spec) → `docs/DEFERRED.md`. **Next:** `spt-claude-code` scoping (the v1 acceptance proof, own repo).
ROADMAP.md: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.
ROADMAP.md: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.


T5 twohost honored-path int is **already logged** in both REQ-MSG-6 (traceable-reqs.toml:280) and ROADMAP.md:81 as REQUIRED-BEFORE-LECTURN — doyle's side-check is satisfied (from commit 58f034f). Ping doyle for the G3 gate:


cd C:/Users/decid/Documents/projects/spt-core && $OWL send doyle todlando <<'EOF'
[G3 READY — Wave 3 complete, gate request] m9-digest-logs is done, 5 commits atop 58f034f, all green:
- 82ead73 T7a spt-term projection primitive + published digest-record contract (8 units)
- 6033326 T7a api digest-entry door + separate digest.log store
- 5a09577 T7b-core on-demand projector (project_endpoint_digest) + hub project_and_publish + reproject verb + handlers; removed dead run_digest_feed; CC-fixture E2E (harness_hosted_digest_projects_and_pushes_deltas — Path A JSONL, no broker/PTY)
- ba8e3c1 T7b retire+re-point: deleted DigestParser/PtyDigest/[pty_digest] (schema re-blessed, regex dep dropped); RETITLED REQ-TERM-4 to your honest log-source title with impl/unit/int tags on the projection+E2E; migrated the native fixture to the contract; full docs sweep.

G3 checklist:
- REQ-TERM-4 re-point audit: traceable EXIT=0, title honest, no orphan tags (old parser/from_manifest tags died with their code; new evidence = projection units + daemon projector + CC E2E + MANIFEST doc tag). [OK] doc+impl+unit+int.
- Zero PTY-parse remnants: grep-clean (DigestParser/DigestPatterns/DigestError/PtyDigest/pty_digest/ingest_chunk/run_digest_feed all gone from code).
- CC-fixture E2E: green (topology-independent projection + push-driven delta both proven).
- Full suite: clippy -D workspace, cargo test --workspace, xtask check (cli reference regen'd), all green.
- Docs: MANIFEST + docs-site replace [pty_digest] w/ the digest-record contract; CONTEXT 341 + ADR-0008 debt discharged; CHANGELOG [Unreleased]. Deferred autonomous-freshness + Option-C re-home logged to docs/DEFERRED.md (consuming-frontend milestone) per your guardrails 2 & 3 — exact G3-note wording used.

SIDE-CHECK CONFIRMED: T5 twohost honored-path int IS already logged REQUIRED-BEFORE-LECTURN on BOTH REQ-MSG-6 (toml:280) and ROADMAP:81 (commit 58f034f) — nothing to add.

Opening the PR (base m9-gateway-wan, the stack tip) for your G3 gate. Your gate, please.
EOF

Ping doyle for G3 gate


➜ SENT:doyle


Push the branch + open the PR (base = stack tip `m9-gateway-wan`) for doyle's G3 gate:


cd C:/Users/decid/Documents/projects/spt-core && git push -u origin m9-digest-logs 2>&1 | tail -8

Push Wave3 branch


remote:
remote: Create a pull request for 'm9-digest-logs' on GitHub by visiting:
remote:      https://github.com/SaberMage/spt-core/pull/new/m9-digest-logs
remote:
branch 'm9-digest-logs' set up to track 'origin/m9-digest-logs'.
To https://github.com/SaberMage/spt-core.git
 * [new branch]      m9-digest-logs -> m9-digest-logs


cd C:/Users/decid/Documents/projects/spt-core && gh pr create --base m9-gateway-wan --head m9-digest-logs --title "M9 Wave 3 (G3): session-digest log-source re-foundation (REQ-TERM-4)" --body "$(cat <<'EOF'
Wave 3 of M9 — re-founds the live activity buffer (session digest) on **normalized session logs** instead of the PTY byte stream (ADR-0008 amendment, 2026-06-12). doyle-vetted plan (`M9-WAVE3-JIT.md`); §2 RULED; T7b scope RULED (snapshot-pull on-demand projection is the G3 bar; autonomous live-nudge deferred).

## What changed
- **Published digest-record contract** (`spt-term::projection`): the spt-core-owned `{role ∈ input|agent|tool, text, tool{name,arg}}` overlay a projectable adapter targets in its `[history]` lines (or pushes via `api digest-entry`). One schema, no mapping layer; the history subsystem stays opaque. Forward-compat: unknown fields ignored, malformed/unknown-role lines skipped.
- **`api digest-entry`** — the log-less-adapter push door, appending to a **separate** `digest.log` (never the opaque transcript).
- **On-demand projection** (`spt-daemon::digest::project_endpoint_digest`): the snapshot-pull / reproject handlers project from the endpoint's records (the `[history]` source, or `digest.log`); `DigestHub::project_and_publish` drives the structured-delta stream. **Topology-independent** — a harness-hosted (Claude-Code) endpoint with no broker PTY gets a digest.
- **Retired**: the `DigestParser` byte-parse primitive, the byte-feed `DigestEngine`, the dead `run_digest_feed` driver, and the `[pty_digest]` manifest seam (schema re-blessed; `regex` dep dropped).
- **REQ-TERM-4 re-pointed** (restoration discipline): retitled to the honest log-source scope; its `impl`/`unit`/`int` tags moved onto the projection + the CC-fixture E2E in the same commit the old evidence died.

## Deferred (tracked, no consumer yet — `docs/DEFERRED.md`)
Autonomous file-watch freshness + the Option-C persistence re-home land with the consuming-frontend milestone (Shell M10 / GUI pane / Gateway window). The delta-stream **contract** ships; deltas are driven by pulls / `api digest-entry` pushes.

## Verification
- `traceable-reqs check` EXIT=0 — REQ-TERM-4 `[OK]` doc+impl+unit+int.
- `cargo clippy --workspace --all-targets -D warnings` clean; `cargo test --workspace` green; `xtask check` OK.
- CC-fixture E2E (`harness_hosted_digest_projects_and_pushes_deltas`) proves topology-independent projection + push-driven delta (Path A locate+normalize, no broker).

Base is the M9 stack tip (`m9-gateway-wan`). G3 gate: doyle.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)" 2>&1 | tail -5

Open Wave3 PR for G3 gate


**PR #10 opened** for doyle's G3 gate. Wave 3 / G3 fully implemented, validated, pushed. Update the M9 status memory:


1	---
2	name: m9-gateway-wan-progress
3	description: M9 cross-node Gateway WAN user-msg (REQ-MSG-6) — PR #9 doyle gate PASS, merge-ready; T5 int deferred required-before-lecturn
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: 6b7355ca-c59b-4b57-a4e8-b248ce4673c6
8	---
9
10	**M9 cross-node Gateway WAN user-msg** (doyle directive 2026-06-13, operator GO). Branch `m9-gateway-wan` stacked on m9-user-surfaces. Doc stage was doyle's (commit 30fea7f). I (todlando) own impl/unit. **REQ-MSG-6** minted [doc,impl,unit] all green; REQ-EP-6 retitled (WAN residual no longer deferred).
11
12	**Impl COMPLETE, PR #9 open** (base m9-user-surfaces, CI-vehicle pattern like PR #8). 4 commits: T1 Instance.endpoint_type (0b79951) → T2 advertise populate from info.json state (0f84e25) → T3+T4 receive_wan origin resolve + unit matrix (1588525) + REQ-mint (6f720d6). T5 twohost int **SKIPPED** (unit matrix + existing wanmsg wire fail-closed test cover it; flagged in PR for doyle).
13
14	Local sweep ALL GREEN: workspace tests 0-fail, clippy -D clean, --no-default-features builds, xtask check OK, traceable EXIT=0. **PR #9 CI GREEN both runners** (first Linux RED was a transient Broker::bind socket flake in applyhost test — unrelated, cleared on rerun). **doyle PINGED for his gate** (posture-adherence = no smuggled extra gate beyond "is advertised type gateway", additive/N-1 Instance, proven-node keying not wire `from`, the matrix). Awaiting his stamp → then Wave 3 / next beat.
15
16	**Ratified posture (deviation=STOP):** subnet membership IS the trust boundary; NO defense against an in-subnet member forging the gateway type; proven-node keying is correctness not trust. Don't re-introduce the vetoed per-node "user-surface trust set".
17
18	**Mechanism:** Instance.endpoint_type (additive serde-default, rides epoch lease, no new channel) ← advertise_local stamps perch's own info.json `state` ← receive_wan resolves via RegistryHost::instances_of keyed by QUIC-proven origin_node, flips origin_user_backed. Absent type → fail-closed re-stamp.
19
20	**GATE PASS ✓ — doyle cleared PR #9 gateway-wan 2026-06-13** (read impl whole, re-ran origin_user_backed_matrix 1/0 + wanmsg 3/0 + xtask: posture-faithful, no smuggled gate, proven-node keying confirmed, production wiring real not stub). PR #9 merge-ready (operator/release-gated). **TRACKED CONDITION (non-blocking, logged commit 58f034f):** T5 twohost HONORED-path int (Gateway advert A → honored at B; agent → re-stamped) REQUIRED-BEFORE-LECTURN — on REQ-MSG-6 (activate `int` when adding) + ROADMAP. M9 milestone: G1 ✓ G2 ✓ gateway-wan ✓.
21
22	SEQUENCING: PR #8 (Wave 2 G2) — **G2 FULLY CLOSED ✓ by doyle 2026-06-13** (CI green both runners after disk-full fix, see [[hfenduleam-disk-full-ci]]; his substance PASS + reconcile_driver_structurally_excludes_live_role green). Wave 2 stamped; OPERATOR owns merge-to-main per release model. **PR #9 now under doyle's gate review** (he pulled it, back shortly).
23

---
name: m9-gateway-wan-progress
description: M9 cross-node Gateway WAN user-msg (REQ-MSG-6) — PR #9 doyle gate PASS, merge-ready; T5 int deferred required-before-lecturn
metadata:
  node_type: memory
  type: project
  originSessionId: 6b7355ca-c59b-4b57-a4e8-b248ce4673c6
---

**M9 cross-node Gateway WAN user-msg** (doyle directive 2026-06-13, operator GO). Branch `m9-gateway-wan` stacked on m9-user-surfaces. Doc stage was doyle's (commit 30fea7f). I (todlando) own impl/unit. **REQ-MSG-6** minted [doc,impl,unit] all green; REQ-EP-6 retitled (WAN residual no longer deferred).

**Impl COMPLETE, PR #9 open** (base m9-user-surfaces, CI-vehicle pattern like PR #8). 4 commits: T1 Instance.endpoint_type (0b79951) → T2 advertise populate from info.json state (0f84e25) → T3+T4 receive_wan origin resolve + unit matrix (1588525) + REQ-mint (6f720d6). T5 twohost int **SKIPPED** (unit matrix + existing wanmsg wire fail-closed test cover it; flagged in PR for doyle).

Local sweep ALL GREEN: workspace tests 0-fail, clippy -D clean, --no-default-features builds, xtask check OK, traceable EXIT=0. **PR #9 CI GREEN both runners** (first Linux RED was a transient Broker::bind socket flake in applyhost test — unrelated, cleared on rerun). **doyle PINGED for his gate** (posture-adherence = no smuggled extra gate beyond "is advertised type gateway", additive/N-1 Instance, proven-node keying not wire `from`, the matrix). Awaiting his stamp → then Wave 3 / next beat.

**Ratified posture (deviation=STOP):** subnet membership IS the trust boundary; NO defense against an in-subnet member forging the gateway type; proven-node keying is correctness not trust. Don't re-introduce the vetoed per-node "user-surface trust set".

**Mechanism:** Instance.endpoint_type (additive serde-default, rides epoch lease, no new channel) ← advertise_local stamps perch's own info.json `state` ← receive_wan resolves via RegistryHost::instances_of keyed by QUIC-proven origin_node, flips origin_user_backed. Absent type → fail-closed re-stamp.

**GATE PASS ✓ — doyle cleared PR #9 gateway-wan 2026-06-13** (read impl whole, re-ran origin_user_backed_matrix 1/0 + wanmsg 3/0 + xtask: posture-faithful, no smuggled gate, proven-node keying confirmed, production wiring real not stub). PR #9 merge-ready (operator/release-gated). **TRACKED CONDITION (non-blocking, logged commit 58f034f):** T5 twohost HONORED-path int (Gateway advert A → honored at B; agent → re-stamped) REQUIRED-BEFORE-LECTURN — on REQ-MSG-6 (activate `int` when adding) + ROADMAP. M9 milestone: G1 ✓ G2 ✓ gateway-wan ✓.

SEQUENCING: PR #8 (Wave 2 G2) — **G2 FULLY CLOSED ✓ by doyle 2026-06-13** (CI green both runners after disk-full fix, see [[hfenduleam-disk-full-ci]]; his substance PASS + reconcile_driver_structurally_excludes_live_role green). Wave 2 stamped; OPERATOR owns merge-to-main per release model. **PR #9 now under doyle's gate review** (he pulled it, back shortly).


Write

---
name: m9-wave3-digest-progress
description: M9 Wave 3 / G3 — session-digest log-source re-foundation (REQ-TERM-4); PR #10 open, awaiting doyle G3 gate
metadata:
  type: project
---

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

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

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

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

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

M9 milestone: G1 ✓ G2 ✓ gateway-wan ✓ (see [[m9-gateway-wan-progress]]) · **G3 pending doyle gate**. Merge-to-main = operator/release-only. After G3 → M9 milestone close.


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

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

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

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

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

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

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

M9 milestone: G1 ✓ G2 ✓ gateway-wan ✓ (see [[m9-gateway-wan-progress]]) · **G3 pending doyle gate**. Merge-to-main = operator/release-only. After G3 → M9 milestone close.


1	- [M9 status](m9-wave1-progress.md) — THREE gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Only Wave3/G3 left (digest log-projection + REQ-TERM-4 re-point). Merge-to-main = release-only (operator). Don't re-plan closed waves
2	- [hfenduleam target bloat → CI RED](hfenduleam-target-bloat-ci-red.md) — Win-runner LNK1318/os-error-112 can be disk-full from cargo target bloat (396GB seen), NOT a code regression; cargo clean + re-run; check disk before treating a Windows RED as a code defect

- [M9 status](m9-wave1-progress.md) — THREE gates closed: G1 (Wave1 profiles/strings/hints, PR#7) · G2 (Wave2 user-msg/Gateway/live-role, PR#8) · Gateway-WAN (cross-node user-msg, PR#9, REQ-MSG-6). Only Wave3/G3 left (digest log-projection + REQ-TERM-4 re-point). Merge-to-main = release-only (operator). Don't re-plan closed waves
- [hfenduleam target bloat → CI RED](hfenduleam-target-bloat-ci-red.md) — Win-runner LNK1318/os-error-112 can be disk-full from cargo target bloat (396GB seen), NOT a code regression; cargo clean + re-run; check disk before treating a Windows RED as a code defect


Edit

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

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


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

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

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

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

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

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


Wave 3 / G3 is **fully implemented and up for doyle's gate**. Final status:

## M9 Wave 3 (G3) — session-digest log-source re-foundation — DONE, PR #10 open

**Branch `m9-digest-logs`** (off M9 stack tip), 5 commits, all green:

| Commit | What |
|--------|------|
| `714f6b8` | doyle-vetted plan |
| `82ead73` | **T7a** projection primitive + published digest-record contract (8 units) |
| `6033326` | **T7a** `api digest-entry` door + separate `digest.log` |
| `5a09577` | **T7b-core** on-demand projector + hub `project_and_publish` + reproject + CC-fixture E2E |
| `ba8e3c1` | **T7b** retire parser/`PtyDigest`/`[pty_digest]` + REQ-TERM-4 re-point + docs sweep |

**Architecture:** digest is now an on-demand **projection of normalized session logs** — topology-independent (Claude-Code-hosted endpoint with no broker PTY gets a digest). The byte-feed had zero production callers (never live-wired), so retire was clean. History stays opaque; the digest-record contract is spt-core's published overlay.

**doyle rulings folded in:** §2 approach A (contract published, fixture migrated); T7b scope = snapshot-pull is the G3 bar, autonomous-freshness + Option-C deferred to the consuming-frontend milestone (logged `docs/DEFERRED.md`).

**Verified:** `traceable-reqs check` EXIT=0 (REQ-TERM-4 `[OK]` doc+impl+unit+int) · clippy -D workspace · `cargo test --workspace` · `xtask check` OK.

**Side-check confirmed:** T5 twohost honored-path int already logged REQUIRED-BEFORE-LECTURN on REQ-MSG-6 + ROADMAP.

PR **#10** opened (base `m9-gateway-wan`), doyle pinged for the **G3 gate**. Memory updated. M9: G1 ✓ G2 ✓ gateway-wan ✓ · G3 pending gate → then milestone close.

Standing by for doyle.