320	The context is a local git repo with `a-<endpoint_id>` (per-agent) and `p-<project_id>` (per-project) branch views. Sync has **two transport modes** (full mechanics: `docs/STORAGE.md`):
321	- **P2P (default):** the data syncs between a user's nodes over the built-in P2P networking — **no `gh`/account/setup**, the no-central-operator promise. Replaces modern SPT's required GitHub-remote sync.
322	- **Hub (opt-in, `spt context-github-setup`):** every node pull→merge→pushes against a private GitHub remote — an always-online sync hub *and* a convenient GUI view. Uses a **shared deploy key** distributed as subnet secret material, so only the setup node needs a GitHub account; other nodes use the key over SSH, with **P2P as backstop**. Hub mode is the **context-repo sync transport only** — messaging, registry, remote-drive, pairing, and presence always ride P2P; gh-on never removes the P2P requirement (ADR-0002, amended).
323
324	Merge never line-merges a mind file: a custom merge driver resolves context files **per file by the precedence marker**, which carries `source`, **`node`**, and a per-node **version vector** (entries from each node's monotonic epoch source — wall-clock is never the ordering authority, at most a human tiebreaker hint). A dominating write supersedes; a dominated write drops; **concurrent writes (neither dominates) surface as an explicit conflict — never silent newest-wins**: both versions persist as replicated conflict artifacts and the endpoint's **own Psyche reconciles its own mind** in one bounded turn, run by the **active instance's node only** (single reconciler; the merged write dominates both parents and clears the conflict subnet-wide; reconcile unavailable ⟹ the artifacts simply persist, nothing is lost). KNOWN-HAZARDS 6.5 generalized; mechanics: ADR-0013 + `docs/STORAGE.md`. The single-writer invariant (active-instance-authoritative) makes true conflicts rare. The per-project branch view lets any agent synthesize broader context from *other* agents' contexts in the same project and supports query-routing ("which endpoint owns this task"). The two-tier sync maps onto the views: live context (`a-`) → all instances; project context (`p-`) → same-project instances.
325
326	### Subnet notifications
327
328	**notification (notif)**:
329	A first-class kind **distinct from an inter-agent message**: a **user-directed, dismissable, resurfacing** subnet event (e.g. *new node paired*, *external-subnet pairing request*, *update available*, *consent needed*). Messages route agent→agent and are consumed once; notifs route **to the user** — via whichever endpoint is active — and **persist until dismissed**. The notif is the general primitive that the previously-ad-hoc "deliver to the user's most-recently-active session" flows are special cases of: **the self-update prompt and the consent escalation are refactored to be notif producers** (one primitive, many producers — kills the duplicated delivery paths; the registry-resolution precursor to PresenceChannel is this primitive's delivery step).
330
331	- **Spool: per-subnet, replicated across that subnet's nodes** (reuses subnet-registry distribution). Each notif is tagged with its subnet; **dismiss-state replicates subnet-wide**, eventually consistent (dismiss on node A clears it on node B). "Node notif spool" = each node's local replica. *(Forward seam: dismissal — and the spool key — generalize to **per-(subnet, user)** when the cross-user model lands.)*
332	- **First-fire** targets the user's most-recently-active endpoint *in that subnet* via presence resolution.
333	- **Two states, not one:** **seen** (surfaced at least once — tracked **per-endpoint**) vs **dismissed** (explicitly acknowledged → removed from the resurface set). Surfacing alone ≠ dismissal. Dismissal is explicit: `spt notif dismiss <id>`, or **the agent marks it dismissed** once it judges the user acknowledged (the agent is the default surfacer, so it is well-placed to dismiss).
334	- **Resurface** re-delivers *undismissed* notifs at boundaries, gated to avoid nagging: skip if already **seen on this endpoint**, and skip if surfaced anywhere within a **global suppression timeout** (default ~1h, configurable) — the timeout is cross-endpoint so a notif can't bounce between endpoints. **Resurface boundaries** (reuse existing reported events, no new plumbing): state→active (`wake` from offline/suspended/dormant), `api boundary clear`, `api boundary compact`, and **new-session-start**. The daemon injects undismissed notifs into the activating/cleared/compacted/fresh session's context.
335	- **Scope:** an endpoint only ever sees/resurfaces notifs for subnets it is **visible in** (excluded endpoints never receive that subnet's notifs).
336	- **Delivery.** Default: a notif to an agent is **just normal SPT messaging to the agent's perch with a notif-flavored envelope** — the existing delivery/inject path handles it and the agent surfaces it conversationally (no new delivery machinery). Optional: a **`notif_command` manifest template** for endpoint-native rendering (OS toast, etc.), substitution keys from the envelope, **combinable** with the agent-surface path (blanket template, not per-notif-type). The `notif_command` seam lives on **both harness-adapter and shell-adapter manifests** — if presence resolves the user not just to an endpoint but to a **Shell attached to it**, the notif renders via *that shell's* `notif_command` (e.g. GameRobot `alert-symbol`). This is the forward generalization and v1 seam in one.
337	- **Producers (open set):** system events (node-paired, pairing-request, update-available, consent-needed) + agent-issued `spt notify`. New producers register later.
338	- **Envelope `from`** — for agent-issued notifs carries the issuer's **endpoint id, node, and subnet** (the subnet is **surfaced to the receiver only if its node is on multiple subnets** — otherwise redundant). Makes notifs attributable now and targetable once the per-(subnet, user) model lands.
339
340	**`spt notify` (agent-issued subnet notif)**:
341	A command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.
342
343	## Self-update
344
345	Seamless, realtime self-updating is a day-one pillar. After a plugin performs the initial spt-core bootstrap, `spt.exe` self-updates from then on and **ripple-updates registered adapters** via each adapter's manifest update declaration.
346
347	**delivery** — peer-propagated over P2P, layered on self-fetch, with out-of-band still supported. One node learns of / obtains an update (marketplace drop *or* self-fetch from a release channel), gossips availability across the subnet, and peers pull the new binary over the networking layer — the subnet self-heals to latest. **All binaries are signature-verified before handoff regardless of source** (peer-propagation otherwise lets a compromised node poison the subnet). spt-core has its own release signing key, distinct from any OS-publisher code-signing (the Windows-publisher-trust question is separate).

The context is a local git repo with `a-<endpoint_id>` (per-agent) and `p-<project_id>` (per-project) branch views. Sync has **two transport modes** (full mechanics: `docs/STORAGE.md`):
- **P2P (default):** the data syncs between a user's nodes over the built-in P2P networking — **no `gh`/account/setup**, the no-central-operator promise. Replaces modern SPT's required GitHub-remote sync.
- **Hub (opt-in, `spt context-github-setup`):** every node pull→merge→pushes against a private GitHub remote — an always-online sync hub *and* a convenient GUI view. Uses a **shared deploy key** distributed as subnet secret material, so only the setup node needs a GitHub account; other nodes use the key over SSH, with **P2P as backstop**. Hub mode is the **context-repo sync transport only** — messaging, registry, remote-drive, pairing, and presence always ride P2P; gh-on never removes the P2P requirement (ADR-0002, amended).

Merge never line-merges a mind file: a custom merge driver resolves context files **per file by the precedence marker**, which carries `source`, **`node`**, and a per-node **version vector** (entries from each node's monotonic epoch source — wall-clock is never the ordering authority, at most a human tiebreaker hint). A dominating write supersedes; a dominated write drops; **concurrent writes (neither dominates) surface as an explicit conflict — never silent newest-wins**: both versions persist as replicated conflict artifacts and the endpoint's **own Psyche reconciles its own mind** in one bounded turn, run by the **active instance's node only** (single reconciler; the merged write dominates both parents and clears the conflict subnet-wide; reconcile unavailable ⟹ the artifacts simply persist, nothing is lost). KNOWN-HAZARDS 6.5 generalized; mechanics: ADR-0013 + `docs/STORAGE.md`. The single-writer invariant (active-instance-authoritative) makes true conflicts rare. The per-project branch view lets any agent synthesize broader context from *other* agents' contexts in the same project and supports query-routing ("which endpoint owns this task"). The two-tier sync maps onto the views: live context (`a-`) → all instances; project context (`p-`) → same-project instances.

### Subnet notifications

**notification (notif)**:
A first-class kind **distinct from an inter-agent message**: a **user-directed, dismissable, resurfacing** subnet event (e.g. *new node paired*, *external-subnet pairing request*, *update available*, *consent needed*). Messages route agent→agent and are consumed once; notifs route **to the user** — via whichever endpoint is active — and **persist until dismissed**. The notif is the general primitive that the previously-ad-hoc "deliver to the user's most-recently-active session" flows are special cases of: **the self-update prompt and the consent escalation are refactored to be notif producers** (one primitive, many producers — kills the duplicated delivery paths; the registry-resolution precursor to PresenceChannel is this primitive's delivery step).

- **Spool: per-subnet, replicated across that subnet's nodes** (reuses subnet-registry distribution). Each notif is tagged with its subnet; **dismiss-state replicates subnet-wide**, eventually consistent (dismiss on node A clears it on node B). "Node notif spool" = each node's local replica. *(Forward seam: dismissal — and the spool key — generalize to **per-(subnet, user)** when the cross-user model lands.)*
- **First-fire** targets the user's most-recently-active endpoint *in that subnet* via presence resolution.
- **Two states, not one:** **seen** (surfaced at least once — tracked **per-endpoint**) vs **dismissed** (explicitly acknowledged → removed from the resurface set). Surfacing alone ≠ dismissal. Dismissal is explicit: `spt notif dismiss <id>`, or **the agent marks it dismissed** once it judges the user acknowledged (the agent is the default surfacer, so it is well-placed to dismiss).
- **Resurface** re-delivers *undismissed* notifs at boundaries, gated to avoid nagging: skip if already **seen on this endpoint**, and skip if surfaced anywhere within a **global suppression timeout** (default ~1h, configurable) — the timeout is cross-endpoint so a notif can't bounce between endpoints. **Resurface boundaries** (reuse existing reported events, no new plumbing): state→active (`wake` from offline/suspended/dormant), `api boundary clear`, `api boundary compact`, and **new-session-start**. The daemon injects undismissed notifs into the activating/cleared/compacted/fresh session's context.
- **Scope:** an endpoint only ever sees/resurfaces notifs for subnets it is **visible in** (excluded endpoints never receive that subnet's notifs).
- **Delivery.** Default: a notif to an agent is **just normal SPT messaging to the agent's perch with a notif-flavored envelope** — the existing delivery/inject path handles it and the agent surfaces it conversationally (no new delivery machinery). Optional: a **`notif_command` manifest template** for endpoint-native rendering (OS toast, etc.), substitution keys from the envelope, **combinable** with the agent-surface path (blanket template, not per-notif-type). The `notif_command` seam lives on **both harness-adapter and shell-adapter manifests** — if presence resolves the user not just to an endpoint but to a **Shell attached to it**, the notif renders via *that shell's* `notif_command` (e.g. GameRobot `alert-symbol`). This is the forward generalization and v1 seam in one.
- **Producers (open set):** system events (node-paired, pairing-request, update-available, consent-needed) + agent-issued `spt notify`. New producers register later.
- **Envelope `from`** — for agent-issued notifs carries the issuer's **endpoint id, node, and subnet** (the subnet is **surfaced to the receiver only if its node is on multiple subnets** — otherwise redundant). Makes notifs attributable now and targetable once the per-(subnet, user) model lands.

**`spt notify` (agent-issued subnet notif)**:
A command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.

## Self-update

Seamless, realtime self-updating is a day-one pillar. After a plugin performs the initial spt-core bootstrap, `spt.exe` self-updates from then on and **ripple-updates registered adapters** via each adapter's manifest update declaration.

**delivery** — peer-propagated over P2P, layered on self-fetch, with out-of-band still supported. One node learns of / obtains an update (marketplace drop *or* self-fetch from a release channel), gossips availability across the subnet, and peers pull the new binary over the networking layer — the subnet self-heals to latest. **All binaries are signature-verified before handoff regardless of source** (peer-propagation otherwise lets a compromised node poison the subnet). spt-core has its own release signing key, distinct from any OS-publisher code-signing (the Windows-publisher-trust question is separate).


Edit

**`spt notify` (agent-issued subnet notif)**:
A command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.

<!-- [doc->REQ-NOTIF-2] -->
**`spt notify` (agent-issued subnet notif)**:
A command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.


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

<!-- [doc->REQ-NOTIF-2] -->
**`spt notify` (agent-issued subnet notif)**:
A command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.

- **Producers (open set):** system events (node-paired, pairing-request, update-available, consent-needed) + agent-issued `spt notify`. New producers register later.

- **Envelope `from`** — for agent-issued notifs carries the issuer's **endpoint id, node, and subnet** (the subnet is **surfaced to the receiver only if its node is on multiple subnets** — otherwise redundant). Makes notifs attributable now and targetable once the per-(subnet, user) model lands.


id = "REQ-NOTIF-2"
title = "spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)"
required_stages = []

id = "REQ-NOTIF-2"
title = "spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)"
required_stages = ["doc", "impl", "unit"]


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

id = "REQ-NOTIF-2"
title = "spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)"
required_stages = []

id = "REQ-NOTIF-2"
title = "spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)"
required_stages = ["doc", "impl", "unit"]

+required_stages = ["doc", "impl", "unit"]

# ───────────────────────────── Self-update ──────────────────────────────────


Bash

traceable-reqs check 2>&1 | grep -E "NOTIF" ; traceable-reqs check >/dev/null 2>&1; echo "exit=$?"

Verify REQ-NOTIF-2 coverage


  [OK] REQ-NOTIF-1  required: [impl, unit]  stages: -doc +impl +unit -int
  [OK] REQ-NOTIF-2  required: [doc, impl, unit]  stages: +doc +impl +unit -int
exit=0


grep -n "doc" .github/workflows/*.yml | head -10

Check CI for doc-drift gates


(Bash completed with no output)


cargo clippy --workspace --all-targets -- -D warnings 2>&1 | tail -2 && cargo test -p spt-runtime -p spt-daemon -p spt 2>&1 | grep -E "FAILED|[1-9][0-9]* failed" | head -5; echo "gates-done"

Workspace clippy + touched-crate suites


    Checking spt v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.99s
gates-done


git add crates/spt-runtime/src/manifest.rs crates/spt-daemon/src/notif.rs crates/spt/src/cli.rs crates/spt/src/api/reporting.rs crates/spt/src/api/startup.rs docs/MANIFEST.md CONTEXT.md traceable-reqs.toml && git commit -m "feat(spt,runtime,daemon): M4-D8-4 spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)

spt notify [--subnet] [--from] [body|stdin]: produce an agent-kind notif
(envelope from = <issuer>@<node-hex>, subnet on the envelope attr) +
first-fire; subnet resolution never guesses (sole subnet auto, several
require --subnet, zero/non-member refused — the show-totp stance). spt
notif list/dismiss = the ack surface (dismiss latches + replicates).
Manifest seam: [session.notif] SessionRole (keys {notif_id} {notif_from}
{notif_subnet} {notif_body}) on harness + shell adapters; surfacing at the
listen boundary renders it detached via spawn_session when declared,
combinable with agent-surface delivery (opt-in — undeclared = agent-surface
only). Render proven via the shared command_for fill (bounded-run capture).
Docs: MANIFEST.md role + keys, CONTEXT.md spt-notify doc tag.
REQ-NOTIF-2 activated [doc, impl, unit].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>" && git push && git log --oneline -1

Commit and push D8-4


warning: in the working copy of 'CONTEXT.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/src/notif.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-runtime/src/manifest.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt/src/api/reporting.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt/src/api/startup.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt/src/cli.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs/MANIFEST.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'traceable-reqs.toml', LF will be replaced by CRLF the next time Git touches it
[dev-freeform 126d843] feat(spt,runtime,daemon): M4-D8-4 spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)
 8 files changed, 390 insertions(+), 9 deletions(-)
To https://github.com/SaberMage/spt-core.git
   4548410..126d843  dev-freeform -> dev-freeform
126d843 feat(spt,runtime,daemon): M4-D8-4 spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)


for i in $(seq 1 60); do out=$(gh run list --branch dev-freeform --limit 1 --json headSha,status,conclusion --jq '.[0] | "\(.headSha[0:7]) \(.status) \(.conclusion)"'); echo "$out"; case "$out" in 126d843\ completed*) break;; esac; sleep 30; done

Poll CI until run for 126d843 completes


Command running in background with ID: bf63u93a9. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\648159b6-16b2-4568-b643-7b0c98c1871d\tasks\bf63u93a9.output. You will be notified when it completes. To check interim output, use Read on that file path.


CI polling in background. While waiting, prepare the plan-status edit (committed only after FINAL green):


Edit

# M4-D8 — subnet notification primitive (JIT plan)

**Status:** DRAFT 2026-06-04 — authored against M4-PLAN §D8, ADR-0007, and the
shipped D3/D4d/D6/D7.5 machinery.

# M4-D8 — subnet notification primitive (JIT plan)

**Status:** COMPLETE 2026-06-04 — D8-1 `9a6548f` (notif store + semilattice
merge) + D8-2 `f612b3f` (replication record + gated feed drivers + two-broker
E2E, FAULT-MATRIX rows 26–29) + D8-3 `4548410` (first-fire + resurface engine
+ consent/psyche producers + boundary wiring, **REQ-NOTIF-1 activated
`[impl, unit]`**) + D8-4 `126d843` (`spt notify` / `spt notif list|dismiss` +
`[session.notif]` manifest seam, **REQ-NOTIF-2 activated `[doc, impl,
unit]`**), all CI-green FINAL. Acceptance met: a notif first-fires to the
most-recently-active visible endpoint; resurfaces at `api boundary` /
new-session gated seen-per-endpoint + ~1h cross-endpoint suppression;
dismiss replicates subnet-wide over the wire (two-broker E2E); `spt notify`
produces + fires through the same engine. The D7.5 psyrelay notify leg and
the REQ-UPD-4 consent prompt are now notif producers (one primitive). Wake
edge, production feed-pump loop, and `int` legs ride D9.


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

# M4-D8 — subnet notification primitive (JIT plan)

**Status:** DRAFT 2026-06-04 — authored against M4-PLAN §D8, ADR-0007, and the
shipped D3/D4d/D6/D7.5 machinery.

# M4-D8 — subnet notification primitive (JIT plan)

**Status:** COMPLETE 2026-06-04 — D8-1 `9a6548f` (notif store + semilattice
merge) + D8-2 `f612b3f` (replication record + gated feed drivers + two-broker
E2E, FAULT-MATRIX rows 26–29) + D8-3 `4548410` (first-fire + resurface engine
+ consent/psyche producers + boundary wiring, **REQ-NOTIF-1 activated
`[impl, unit]`**) + D8-4 `126d843` (`spt notify` / `spt notif list|dismiss` +
`[session.notif]` manifest seam, **REQ-NOTIF-2 activated `[doc, impl,
unit]`**), all CI-green FINAL. Acceptance met: a notif first-fires to the
most-recently-active visible endpoint; resurfaces at `api boundary` /
new-session gated seen-per-endpoint + ~1h cross-endpoint suppression;
dismiss replicates subnet-wide over the wire (two-broker E2E); `spt notify`
produces + fires through the same engine. The D7.5 psyrelay notify leg and
the REQ-UPD-4 consent prompt are now notif producers (one primitive). Wake
edge, production feed-pump loop, and `int` legs ride D9.

-**Status:** DRAFT 2026-06-04 — authored against M4-PLAN §D8, ADR-0007, and the

+**Status:** COMPLETE 2026-06-04 — D8-1 `9a6548f` (notif store + semilattice

+merge) + D8-2 `f612b3f` (replication record + gated feed drivers + two-broker

+E2E, FAULT-MATRIX rows 26–29) + D8-3 `4548410` (first-fire + resurface engine

++ consent/psyche producers + boundary wiring, **REQ-NOTIF-1 activated

+`[impl, unit]`**) + D8-4 `126d843` (`spt notify` / `spt notif list|dismiss` +

+`[session.notif]` manifest seam, **REQ-NOTIF-2 activated `[doc, impl,

+unit]`**), all CI-green FINAL. Acceptance met: a notif first-fires to the

+most-recently-active visible endpoint; resurfaces at `api boundary` /

+new-session gated seen-per-endpoint + ~1h cross-endpoint suppression;

+dismiss replicates subnet-wide over the wire (two-broker E2E); `spt notify`

+produces + fires through the same engine. The D7.5 psyrelay notify leg and

+the REQ-UPD-4 consent prompt are now notif producers (one primitive). Wake

+edge, production feed-pump loop, and `int` legs ride D9.


Research D8 prerequisites (M4-PLAN §D8, D3 distribution, ADR-0007, boundaries, update/consent producers)

Read M4-PLAN §D8, ADR-0007, D3/D4d replication machinery (replicate.rs, registry.rs, ndjson.rs), spool.rs, consent.rs most_recently_active, psyrelay.rs notify leg, visibility.rs, update-prompt producer (M3c), reported boundaries (api boundary/wake/new-session) — enough to draft M4-D8-PLAN.md.

Draft M4-D8-PLAN.md (slices D8a–D8d)

Author the D8 phase plan: notif first-class kind + per-subnet replicated spool (D8a, REQ-NOTIF-1), first-fire/resurface/suppression (D8b), refactor update+consent producers + swap D7.5 notify leg (D8c), spt notify CLI + notif_command manifest seam (D8d, REQ-NOTIF-2). Commit plan.

D8-1: notif store + semilattice merge (spt-store/src/notif.rs)

NotifRow + NotifStore over identity/notifs.db: produce (EpochSource-minted notif_id), list/undismissed/get, dismiss latch, mark_seen union, touch_surfaced max, merge_row semilattice join + MergeOutcome. Unit tests per M4-D8-PLAN slice 1. Tag [impl/unit->REQ-NOTIF-1]. Commit, push, watch CI FINAL.

D8-2: notif replication record + drivers + E2E (spt-net/net/notif.rs, spt-daemon/notifsync.rs)

NotifRecord NDJSON + NotifDecoder; emit_notif_feed/apply_notif_feed under member+trust gate; loopback two-broker E2E (converge, dismiss replicates, replay no-ops, untrusted dropped); FAULT-MATRIX rows. Push after D8-1 green.

D8-3: first-fire + resurface engine + producers (spt-daemon/notif.rs, psyrelay swap, boundary wiring)

Engine done (first_fire/resurface/most_recently_active_where + consent producer). Remaining: psyrelay Notify-arm swap onto primitive, boundary wiring (cmd_boundary + cmd_listen), activate REQ-NOTIF-1 [impl,unit], tests, CI green.

Building D8-3 surfacing engine + producers

D8-4: spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)

CLI done in tree (notify/notif list/dismiss + resolve_notif_subnet). Remaining: notif_command spawn wiring at listen resurface, CLI tests, MANIFEST.md + CONTEXT.md docs with [doc->REQ-NOTIF-2], activate REQ-NOTIF-2, CI green, then mark plan complete.


1	126d843 queued
2

126d843 queued


CI queued (self-hosted runners — can sit minutes). Background poll will re-invoke me when it completes; then: plan-status commit → commune → D9 next.

**D8 status so far:**
- D8-1 `9a6548f` — notif store + semilattice merge (CI-green)
- D8-2 `f612b3f` — replication record + gated feed drivers + two-broker E2E, FAULT-MATRIX 26–29 (CI-green)
- D8-3 `4548410` — first-fire + resurface engine, consent/psyche producers, boundary wiring, REQ-NOTIF-1 `[impl, unit]` (CI-green)
- D8-4 `126d843` — `spt notify` CLI + `[session.notif]` manifest seam, REQ-NOTIF-2 `[doc, impl, unit]` (CI pending)


<task-notification>
<task-id>bf63u93a9</task-id>
<tool-use-id>toolu_012FbPbVqaa1FKwoVsthc3h1</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\648159b6-16b2-4568-b643-7b0c98c1871d\tasks\bf63u93a9.output</output-file>
<status>completed</status>
<summary>Background command "Poll CI until run for 126d843 completes" completed (exit code 0)</summary>
</task-notification>


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


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


Bash

gh run list --branch dev-freeform --limit 1 --json headSha,status,conclusion --jq '.[0]'

Check D8-4 CI FINAL conclusion


D8-4 CI-green FINAL. D8 complete. Plan-status commit:


Updated task #6 status


git add M4-D8-PLAN.md && git commit -m "docs(plan): mark M4-D8 complete (all four slices CI-green)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>" && git push && git log --oneline -3

Commit + push D8 plan-status


warning: in the working copy of 'M4-D8-PLAN.md', LF will be replaced by CRLF the next time Git touches it
[dev-freeform b8d75af] docs(plan): mark M4-D8 complete (all four slices CI-green)
 1 file changed, 13 insertions(+), 2 deletions(-)
To https://github.com/SaberMage/spt-core.git
   126d843..b8d75af  dev-freeform -> dev-freeform
b8d75af docs(plan): mark M4-D8 complete (all four slices CI-green)
126d843 feat(spt,runtime,daemon): M4-D8-4 spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)
4548410 feat(daemon,spt): M4-D8-3 notif first-fire + resurface engine + producers (REQ-NOTIF-1)


D8 done. Commune to Psyche, then self-clear into D9:


Write

Generation 15. D8 COMPLETE — all four slices CI-green FINAL, plan-status @ b8d75af. D9 (the M4 closeout) next.

## D8 COMPLETE (subnet notification primitive, ADR-0007)
- 9a6548f D8-1: spt-store/notif.rs — NotifRow/NotifStore over identity/notifs.db. **Merge model = join-semilattice, NOT D6b vector** (deliberate): row insert idempotent on notif_id = `<node-hex>:<epoch>` (EpochSource-minted), dismissed = OR latch, seen = endpoint-set union, last_surfaced_ms = max. merge_row idempotent + commutative; no conflict surface exists (all fields monotone). Wall-clock only inside suppression heuristic.
- f612b3f D8-2: spt-net/net/notif.rs NotifRecord (kind-tagged full-row NDJSON, no origin field — KH 7.5) + spt-daemon/notifsync.rs emit/apply under fail-closed gate (member subnet ∧ origin trusted IN THAT subnet, subject = handshake-proven stream-table origin). Push-feed posture (D4d, not D6c pull): full-row re-presentation, replay IS recovery. Two-broker E2E: converge over QUIC split mid-record, dismiss replicates back, replay wholly Unchanged, untrusted writes zero rows. FAULT-MATRIX rows 26–29.
- 4548410 D8-3: spt-daemon/notif.rs engine — most_recently_active_where (consent.rs resolver core moved here, consent delegates unscoped call), first_fire → most-recently-active LIVE endpoint VISIBLE in subnet (hidden never targets; none = typed NoTarget, row stays fresh), resurface_at_boundary gates visibility → seen-per-endpoint → ~1h cross-endpoint suppression on max-merged last_surfaced_ms (subnet-wide guarantee). produce_and_first_fire = open producer front door. produce_consent_notif wraps REQ-UPD-4 decide (NeedsConsent ⇒ consent-kind notif; AutoApproved ⇒ none). psyrelay notify leg SWAPPED onto primitive (PsycheNotifCtx carries daemon-supplied own subnet; NotifiedPending variant when no live endpoint). Boundary wiring: api boundary clear|compact + listen READY + post-spawn bind (reporting::resurface_notifs_with). REQ-NOTIF-1 activated [impl, unit].
- 126d843 D8-4: spt notify CLI (issuer from = `<id>@<node-hex>`; subnet resolution never guesses — sole auto, several require --subnet) + spt notif list/dismiss + [session.notif] manifest role (keys {notif_id} {notif_from} {notif_subnet} {notif_body}; spawn_session detached at surfacing when declared, opt-in). MANIFEST.md + CONTEXT.md doc tags. REQ-NOTIF-2 activated [doc, impl, unit].
- b8d75af: plan-status (M4-D8-PLAN.md COMPLETE).

Design invariants (carry):
- Notif state is monotone — never add a non-monotone field to NotifRow without revisiting the merge model.
- Replication gate subject = handshake origin, never payload (KH 7.5); trust is per-subnet.
- Visibility gates surfacing (ADR-0006 §6); seen-per-endpoint gates re-show (dismissal = only ack).
- Psyche notify constrained by construction: subnet daemon-supplied via PsycheNotifCtx, never Psyche markup.

## Deferred to D9 (append to existing list)
- Wake-edge (state→active) resurface hook — no edge detection today (touch_active stamps every tick).
- Notif feed-pump production loop (when feeds emit/apply between nodes).
- REQ-NOTIF int legs if any (two-host).

## Current Focus: D9 — activation sweep + cross-node E2E + closeout
Per M4-PLAN §D9: D9a dormancy resource budget (red-team #9, REQ-INST-3/4); D9b two-host E2E on real rig (gravity-linux ↔ Windows: pair → register → cross-node message → remote-drive → Psyche sync → update self-heal); D9c REQ-INST-14/15 register-or-document; D9d full required_stages sweep + traceable-reqs green + ROADMAP/CONTEXT amend + CI matrix green. Plus the deferred-to-D9 list: production trigger loops (sync, update, psyche-turn, notif feeds), VerifyPolicy assembly, spt send WAN CLI wiring, gh-repo interim retirement, wake-edge resurface. Read M4-PLAN §D9 + deferred list first → draft M4-D9-PLAN.md.

## Workflow constraints, gotchas 1–10, flake ledger, CI topology: unchanged, carry all.

## No Blockers


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

Generation 15. D8 COMPLETE — all four slices CI-green FINAL, plan-status @ b8d75af. D9 (the M4 closeout) next.

## D8 COMPLETE (subnet notification primitive, ADR-0007)
- 9a6548f D8-1: spt-store/notif.rs — NotifRow/NotifStore over identity/notifs.db. **Merge model = join-semilattice, NOT D6b vector** (deliberate): row insert idempotent on notif_id = `<node-hex>:<epoch>` (EpochSource-minted), dismissed = OR latch, seen = endpoint-set union, last_surfaced_ms = max. merge_row idempotent + commutative; no conflict surface exists (all fields monotone). Wall-clock only inside suppression heuristic.
- f612b3f D8-2: spt-net/net/notif.rs NotifRecord (kind-tagged full-row NDJSON, no origin field — KH 7.5) + spt-daemon/notifsync.rs emit/apply under fail-closed gate (member subnet ∧ origin trusted IN THAT subnet, subject = handshake-proven stream-table origin). Push-feed posture (D4d, not D6c pull): full-row re-presentation, replay IS recovery. Two-broker E2E: converge over QUIC split mid-record, dismiss replicates back, replay wholly Unchanged, untrusted writes zero rows. FAULT-MATRIX rows 26–29.
- 4548410 D8-3: spt-daemon/notif.rs engine — most_recently_active_where (consent.rs resolver core moved here, consent delegates unscoped call), first_fire → most-recently-active LIVE endpoint VISIBLE in subnet (hidden never targets; none = typed NoTarget, row stays fresh), resurface_at_boundary gates visibility → seen-per-endpoint → ~1h cross-endpoint suppression on max-merged last_surfaced_ms (subnet-wide guarantee). produce_and_first_fire = open producer front door. produce_consent_notif wraps REQ-UPD-4 decide (NeedsConsent ⇒ consent-kind notif; AutoApproved ⇒ none). psyrelay notify leg SWAPPED onto primitive (PsycheNotifCtx carries daemon-supplied own subnet; NotifiedPending variant when no live endpoint). Boundary wiring: api boundary clear|compact + listen READY + post-spawn bind (reporting::resurface_notifs_with). REQ-NOTIF-1 activated [impl, unit].
- 126d843 D8-4: spt notify CLI (issuer from = `<id>@<node-hex>`; subnet resolution never guesses — sole auto, several require --subnet) + spt notif list/dismiss + [session.notif] manifest role (keys {notif_id} {notif_from} {notif_subnet} {notif_body}; spawn_session detached at surfacing when declared, opt-in). MANIFEST.md + CONTEXT.md doc tags. REQ-NOTIF-2 activated [doc, impl, unit].
- b8d75af: plan-status (M4-D8-PLAN.md COMPLETE).

Design invariants (carry):
- Notif state is monotone — never add a non-monotone field to NotifRow without revisiting the merge model.
- Replication gate subject = handshake origin, never payload (KH 7.5); trust is per-subnet.
- Visibility gates surfacing (ADR-0006 §6); seen-per-endpoint gates re-show (dismissal = only ack).
- Psyche notify constrained by construction: subnet daemon-supplied via PsycheNotifCtx, never Psyche markup.

## Deferred to D9 (append to existing list)
- Wake-edge (state→active) resurface hook — no edge detection today (touch_active stamps every tick).
- Notif feed-pump production loop (when feeds emit/apply between nodes).
- REQ-NOTIF int legs if any (two-host).

## Current Focus: D9 — activation sweep + cross-node E2E + closeout
Per M4-PLAN §D9: D9a dormancy resource budget (red-team #9, REQ-INST-3/4); D9b two-host E2E on real rig (gravity-linux ↔ Windows: pair → register → cross-node message → remote-drive → Psyche sync → update self-heal); D9c REQ-INST-14/15 register-or-document; D9d full required_stages sweep + traceable-reqs green + ROADMAP/CONTEXT amend + CI matrix green. Plus the deferred-to-D9 list: production trigger loops (sync, update, psyche-turn, notif feeds), VerifyPolicy assembly, spt send WAN CLI wiring, gh-repo interim retirement, wake-edge resurface. Read M4-PLAN §D9 + deferred list first → draft M4-D9-PLAN.md.

## Workflow constraints, gotchas 1–10, flake ledger, CI topology: unchanged, carry all.

## No Blockers


Skill

D8 complete CI-green (b8d75af). Next: read M4-PLAN §D9 + deferred-to-D9 list, draft M4-D9-PLAN.md, then execute slices. Commune just dropped.


Launching skill: self-clear


Base directory for this skill: C:\Users\decid\.ccs\instances\bigscreen\skills\self-clear

# /self-clear

Wipes THIS agent's conversation (`/clear`), then ~200ms later TYPES a self-note
as a fresh user prompt so the agent wakes on a clean context and proceeds.

## Run it

One script does everything. Just run:

```powershell
& "C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1"
```

Expect: `SELF-CLEAR armed: id=<agent_id> winpid=<n> hwnd=<n> — /clear queued; self-note typed in ~200ms`

Then stop — end your turn promptly so the queued `/clear` fires.

## What the script does

1. `owl whoami` → resolves this session's `agent_id` (context-independent, survives the wipe). No perch → it aborts WITHOUT clearing and tells you to `/spt:ready` or `/spt:live` first.
2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor **pid that owns a visible top-level window** (enumerated by owner pid — not the program's default "main window", which is wrong when a host serves several windows). Since each WT window is its own process, that pid uniquely identifies this session's window. **Reliably focuses it** via `AttachThreadInput` (a background process can't otherwise hold the foreground) and **hard-verifies it genuinely holds the foreground** — aborts WITHOUT clearing and WITHOUT sending any keys if it can't.
3. Re-verifies foreground with **zero gap**, then types `/clear{ENTER}` in one SendKeys — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 200ms (past turn-end + wipe), reliably re-focuses the captured `hwnd`, and — **only if that exact HWND genuinely holds the foreground** — **types** `Proceed with next steps` + Enter as a fresh user prompt. Otherwise it aborts the SendKeys (HWND identity is the guard; no title-match — Claude Code's terminal title animates and changes across `/clear`).

Why this matters: `SendKeys` hits whatever window is foreground *at send time*. The fix attaches our input queue to the foreground thread so the focus steal actually holds, confirms the target is truly foreground, and aborts rather than type blind — so the note can never leak into the window you're working in.

The delay + detachment are deliberate: the prompt must be typed **after**
`/clear` takes effect, so it lands on the clean context and starts a new turn.

Why typed, not `owl send`: after `/clear` the session sits idle awaiting user
input. A spooled owl message can't self-start a turn — it surfaces only during an
already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer
`/clear` wipes). So a self-addressed owl note never woke the agent. A typed prompt
deterministically starts a turn AND is the re-seed.

## Caveats

- Focus targets the window owned by this session's ancestor pid (enumerated by owner pid). It still can't pick a specific tab when one window hosts multiple tabs; clean when the agent's tab is active.
- The waker types only when our exact window is foreground. If you switch to a different window/tab during the ~200ms gap, the wake-note SendKeys is aborted (the agent stays cleared but unwoken) — stay in the agent window for the wake.
- The note is typed via SendKeys, so the terminal must stay the foreground-able window of `winpid`. Don't close/replace the terminal in the gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- The waker waits only ~200ms after the script returns, so stop promptly — if your turn runs longer, the prompt could be typed before the wipe completes.

ARGUMENTS: D8 complete CI-green (b8d75af). Next: read M4-PLAN §D9 + deferred-to-D9 list, draft M4-D9-PLAN.md, then execute slices. Commune just dropped.


PowerShell

Arm self-clear: queue /clear + wake note


/clear


SELF-CLEAR armed: id=doyle winpid=29328 hwnd=199014 title="⠐ Proceed with next steps" — focus held + /clear queued; self-note typed in ~200ms (waker aborts unless our window holds foreground)


Proceed with next steps


Self-clear armed. /clear queued; wake note types in ~200ms. Ending turn now.

D8 closed: 4 slices CI-green, REQ-NOTIF-1 + REQ-NOTIF-2 activated, commune dropped. Fresh context wakes into D9 planning.