Your questions have been answered: "D6b: when two nodes wrote the same context file concurrently (version vectors conflict — neither dominates), how should the conflict surface? Locked already: never silent newest-wins; wall-clock only a human hint."="psyche auto-reconcile. be sure to note it in the appropriate docs using /grill-with-docs as the guiding text", "D6 sync transport shape: how does context move between nodes? STORAGE.md currently sketches git-branch fetch over Iroh + --allow-unrelated-histories merges; implementation reality offers a simpler proven path. Doc amended same change either way."="git-native bundles. having divergent histories per-node is not an option.", "BranchStore v1 backing (REQ-STORE-1, ADR-0011): drive the system `git` binary, or pull in gitoxide library? ADR-0011 suggested a spike to validate; D6 is the first consumer."="git CLI (Recommended)". You can now continue with these answers in mind.

Question: D6b: when two nodes wrote the same context file concurrently (version vectors conflict — neither dominates), how should the conflict surface? Locked already: never silent newest-wins; wall-clock only a human hint.

Choices:
Local file untouched (active mind never clobbered). Remote version saved as conflict artifact beside it with both vectors + wall-clock hints. `spt context conflicts` lists; `resolve --keep local|remote` settles. Event seam emitted for D8 notif. Psyche-assisted semantic merge = later upgrade on same seam.
Write both versions into the context file under explicit '## CONFLICT' section headers (never git <<<< markers). Agent sees it next resume and self-heals via commune. Risk: bloated/confusing mind file until reconciled.
Feed both versions to a Psyche turn immediately; its output becomes the merged file. No human step. Risk: needs live-Psyche driver machinery (D7.5, unbuilt), silent semantic drift, no audit pause.

Question: D6 sync transport shape: how does context move between nodes? STORAGE.md currently sketches git-branch fetch over Iroh + --allow-unrelated-histories merges; implementation reality offers a simpler proven path. Doc amended same change either way.

Choices:
ContextUpdate NDJSON over broker QUIC streams — exact D4d registry / D5a wanmsg pattern. File content + version vector in the record; receiver runs D6b merge, commits to its own local tracked/ repo. Single-writer invariant makes state-transfer sufficient. Simple, hermetic-testable. Tradeoff: each node's git history is local-only (no shared DAG).
git bundle over the D5c xfer channel; receiver fetches + merges with vector-aware driver + --allow-unrelated-histories. Pros: shared cross-node history DAG, cleaner future hub-mode interop. Cons: much more plumbing (bundle exchange, multi-parent commit construction), git CLI on the wire path, matches STORAGE.md prose as written.

Question: BranchStore v1 backing (REQ-STORE-1, ADR-0011): drive the system `git` binary, or pull in gitoxide library? ADR-0011 suggested a spike to validate; D6 is the first consumer.

Choices:
Shell out to system git (sister project's proven approach; `git pull` handoff seam already assumes git present). BranchStore trait shaped so gitoxide swap is later drop-in. Zero new deps, fastest to land.
In-process library — no external binary dependency, faster per-commit, hermetic. Cons: big new dep tree, API learning curve, ADR-0011 spike becomes part of D6 critical path.

psyche auto-reconcile. be sure to note it in the appropriate docs using /grill-with-docs as the guiding text

git-native bundles. having divergent histories per-node is not an option.


Skill

M4-D6 cross-node Psyche sync decisions just made: (1) concurrent-write conflicts (version vectors, neither dominates) resolve via Psyche auto-reconcile — feed both versions to a bounded Psyche turn, merged output becomes the file; (2) sync transport = git-native bundles over Iroh (shared history DAG mandatory, no per-node divergent histories); (3) BranchStore v1 = git CLI backed. Need: grill these against CONTEXT.md/ADRs/KNOWN-HAZARDS (esp. 6.5 precedence, D7.5 Psyche-driver dependency, ADR-0011/0012), sharpen the model, update docs inline.


Launching skill: grill-with-docs


Base directory for this skill: C:\Users\decid\.ccs\instances\bigscreen\skills\grill-with-docs

<what-to-do>

Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.

Ask the questions one at a time, waiting for feedback on each question before continuing.

If a question can be answered by exploring the codebase, explore the codebase instead.

</what-to-do>

<supporting-info>

## Domain awareness

During codebase exploration, also look for existing documentation:

### File structure

Most repos have a single context:

```
/
├── CONTEXT.md
├── docs/
│   └── adr/
│       ├── 0001-event-sourced-orders.md
│       └── 0002-postgres-for-write-model.md
└── src/
```

If a `CONTEXT-MAP.md` exists at the root, the repo has multiple contexts. The map points to where each one lives:

```
/
├── CONTEXT-MAP.md
├── docs/
│   └── adr/                          ← system-wide decisions
├── src/
│   ├── ordering/
│   │   ├── CONTEXT.md
│   │   └── docs/adr/                 ← context-specific decisions
│   └── billing/
│       ├── CONTEXT.md
│       └── docs/adr/
```

Create files lazily — only when you have something to write. If no `CONTEXT.md` exists, create one when the first term is resolved. If no `docs/adr/` exists, create it when the first ADR is needed.

## During the session

### Challenge against the glossary

When the user uses a term that conflicts with the existing language in `CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?"

### Sharpen fuzzy language

When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things."

### Discuss concrete scenarios

When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts.

### Cross-reference with code

When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?"

### Update CONTEXT.md inline

When a term is resolved, update `CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md).

`CONTEXT.md` should be totally devoid of implementation details. Do not treat `CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.

### Offer ADRs sparingly

Only offer to create an ADR when all three are true:

1. **Hard to reverse** — the cost of changing your mind later is meaningful
2. **Surprising without context** — a future reader will wonder "why did they do it this way?"
3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons

If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md).

</supporting-info>

ARGUMENTS: M4-D6 cross-node Psyche sync decisions just made: (1) concurrent-write conflicts (version vectors, neither dominates) resolve via Psyche auto-reconcile — feed both versions to a bounded Psyche turn, merged output becomes the file; (2) sync transport = git-native bundles over Iroh (shared history DAG mandatory, no per-node divergent histories); (3) BranchStore v1 = git CLI backed. Need: grill these against CONTEXT.md/ADRs/KNOWN-HAZARDS (esp. 6.5 precedence, D7.5 Psyche-driver dependency, ADR-0011/0012), sharpen the model, update docs inline.


AskUserQuestion

Question: Psyche auto-reconcile depends on the bounded stdout-captured Psyche turn (D7.5a, unbuilt, currently scheduled after D6). How do we sequence, and what holds the conflict while reconcile is unavailable/failing?

Choices:
Reorder: D7.5a (the bounded stdout-captured turn driver) becomes a prerequisite slice inside D6b. D7.5b/c (event taxonomy + daemon relay/sanitize) stay at D7.5. Conflict holding state: both versions persisted as durable conflict artifacts; reconcile turn consumes them; if the turn fails or the harness is unavailable, artifacts persist + retry on next sync/activation + event seam fires (D8 notif later). Never lose either version.
D6b builds its own minimal 'run one bounded turn, capture stdout' helper; D7.5a later generalizes it. Faster D6 start, but two implementations of the same hazardous machinery (7.3) — duplication risk.
D6b ships conflict detection + durable artifacts + event seam only; the Psyche reconcile resolver is wired when D7.5 lands. The mind shows a conflict marker until then. Cleanest layering, but conflicts stay unresolved for a milestone.


Your questions have been answered: "Psyche auto-reconcile depends on the bounded stdout-captured Psyche turn (D7.5a, unbuilt, currently scheduled after D6). How do we sequence, and what holds the conflict while reconcile is unavailable/failing?"="Pull D7.5a forward (Recommended)". You can now continue with these answers in mind.

Question: Psyche auto-reconcile depends on the bounded stdout-captured Psyche turn (D7.5a, unbuilt, currently scheduled after D6). How do we sequence, and what holds the conflict while reconcile is unavailable/failing?

Choices:
Reorder: D7.5a (the bounded stdout-captured turn driver) becomes a prerequisite slice inside D6b. D7.5b/c (event taxonomy + daemon relay/sanitize) stay at D7.5. Conflict holding state: both versions persisted as durable conflict artifacts; reconcile turn consumes them; if the turn fails or the harness is unavailable, artifacts persist + retry on next sync/activation + event seam fires (D8 notif later). Never lose either version.
D6b builds its own minimal 'run one bounded turn, capture stdout' helper; D7.5a later generalizes it. Faster D6 start, but two implementations of the same hazardous machinery (7.3) — duplication risk.
D6b ships conflict detection + durable artifacts + event seam only; the Psyche reconcile resolver is wired when D7.5 lands. The mind shows a conflict marker until then. Cleanest layering, but conflicts stay unresolved for a milestone.


Question: Which node runs the Psyche reconcile turn for a given conflict?

Choices:
The endpoint's ACTIVE instance's node reconciles — matches the existing active-instance-authoritative rule (STORAGE.md single-writer invariant) and the Psyche lives there anyway. No active instance anywhere → deterministic fallback: lowest node id among conflict holders. Dormant nodes hold artifacts + wait for the dominating merged write.
Pure deterministic election, ignores activity. Simpler but the reconciling node may be dormant/headless with a cold Psyche — the merge happens away from where the mind is actually being used.
Whoever detects first claims a reconcile lock (a synced lock file). Race-prone over eventual consistency — the lock itself can conflict. Not recommended.


Your questions have been answered: "Which node runs the Psyche reconcile turn for a given conflict?"="Active instance (Recommended)". You can now continue with these answers in mind.

Question: Which node runs the Psyche reconcile turn for a given conflict?

Choices:
The endpoint's ACTIVE instance's node reconciles — matches the existing active-instance-authoritative rule (STORAGE.md single-writer invariant) and the Psyche lives there anyway. No active instance anywhere → deterministic fallback: lowest node id among conflict holders. Dormant nodes hold artifacts + wait for the dominating merged write.
Pure deterministic election, ignores activity. Simpler but the reconciling node may be dormant/headless with a cold Psyche — the merge happens away from where the mind is actually being used.
Whoever detects first claims a reconcile lock (a synced lock file). Race-prone over eventual consistency — the lock itself can conflict. Not recommended.


Question: Does D6 build the opt-in GitHub hub mode in-core, or P2P sync only?

Choices:
D6 delivers P2P bundle sync (REQ-NET-3: 'P2P replaces gh-repo-sync'). Hub mode stays a documented opt-in seam — git-native shared DAG makes later hub interop natural (same merge driver, just a GitHub remote). Interim gh-repo skill retires once P2P proves out at D9 two-host. Keeps D6 milestone-sized; it already swallowed D7.5a.
Build context-github-setup + deploy-key distribution + prefer-GitHub/P2P-backstop now. Pros: always-online hub immediately, interim skill retires sooner. Cons: D6 balloons (gh auth flows, key rotation, SSH-over-443 fallbacks) — a full extra slice of non-P2P plumbing.


Your questions have been answered: "Does D6 build the opt-in GitHub hub mode in-core, or P2P sync only?"="P2P only, hub deferred (Recommended)". You can now continue with these answers in mind.

Question: Does D6 build the opt-in GitHub hub mode in-core, or P2P sync only?

Choices:
D6 delivers P2P bundle sync (REQ-NET-3: 'P2P replaces gh-repo-sync'). Hub mode stays a documented opt-in seam — git-native shared DAG makes later hub interop natural (same merge driver, just a GitHub remote). Interim gh-repo skill retires once P2P proves out at D9 two-host. Keeps D6 milestone-sized; it already swallowed D7.5a.
Build context-github-setup + deploy-key distribution + prefer-GitHub/P2P-backstop now. Pros: always-online hub immediately, interim skill retires sooner. Cons: D6 balloons (gh auth flows, key rotation, SSH-over-443 fallbacks) — a full extra slice of non-P2P plumbing.


Grep

Cross-node Psyche sync|psyche sync|merge driver|newest wins|newest-wins


316:### Cross-node Psyche sync
324:[Omitted long matching line]
389:[Omitted long matching line]
392:[Omitted long matching line]
409:[Omitted long matching line]
412:[Omitted long matching line]
443:- **In v1:** the endpoint-ID / instance data-model split; the subnet registry; resolution policy; remote-drive of *already-running* instances (network session-surface); cross-node Psyche sync (full — replaces gh-repo-sync); cross-instance context-freshness auto-feed; off-node file transfer; off-node remote-drive detection.
513:- **Ungated:** remote-drive of your *own running* instance (low risk — your own instance; a light "node X is driving `ling@desktop`" notification suffices), cross-node context/Psyche sync (your own data).


310	- Traffic is end-to-end encrypted by node keypairs; relays never see plaintext. The trust question is availability + metadata (who-talks-to-whom-when), not confidentiality.
311	- The relay binary is open-source and self-hostable; spt-core ships a config knob to point at a user-operated relay instead of n0's.
312	- v1 ships with n0 as default + the self-host escape hatch + plain-language disclosure in docs. Hiding the dependency is rejected.
313
314	First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.
315
316	### Cross-node Psyche sync
317
318	**Sync scope is subnet-exclusive by default.** An endpoint's mind replicates only within **one subnet** unless told otherwise. Endpoint config carries a **subnet-membership list** for sync: the context syncs across the nodes of *every* subnet in that list, defaulting to just the endpoint's home subnet. This is distinct from registry *visibility* (*endpoint visibility*) — being addressable in a subnet does not imply replicating your mind there; mind-replication into a subnet requires that subnet to be in the sync list. The default keeps a `home`-fleet agent's mind off the `work` fleet even when a node bridges both. (The subnet scope composes with the live/project tier split below: a tier syncs to the eligible instances *that are also in an in-scope subnet*.)
319
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 is conflict-free: a custom merge driver resolves context files by the precedence marker (`spt:source`/`routed_at`/**`node`**, newest-wins — KNOWN-HAZARDS 6.5), never a git line-merge; 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

- Traffic is end-to-end encrypted by node keypairs; relays never see plaintext. The trust question is availability + metadata (who-talks-to-whom-when), not confidentiality.
- The relay binary is open-source and self-hostable; spt-core ships a config knob to point at a user-operated relay instead of n0's.
- v1 ships with n0 as default + the self-host escape hatch + plain-language disclosure in docs. Hiding the dependency is rejected.

First network socket bind triggers an OS firewall prompt (Windows Defender, some Linux); unavoidable without a signed binary + pre-declared exception. Framed in CLI UX, not suppressible.

### Cross-node Psyche sync

**Sync scope is subnet-exclusive by default.** An endpoint's mind replicates only within **one subnet** unless told otherwise. Endpoint config carries a **subnet-membership list** for sync: the context syncs across the nodes of *every* subnet in that list, defaulting to just the endpoint's home subnet. This is distinct from registry *visibility* (*endpoint visibility*) — being addressable in a subnet does not imply replicating your mind there; mind-replication into a subnet requires that subnet to be in the sync list. The default keeps a `home`-fleet agent's mind off the `work` fleet even when a node bridges both. (The subnet scope composes with the live/project tier split below: a tier syncs to the eligible instances *that are also in an in-scope subnet*.)

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 is conflict-free: a custom merge driver resolves context files by the precedence marker (`spt:source`/`routed_at`/**`node`**, newest-wins — KNOWN-HAZARDS 6.5), never a git line-merge; 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.


CONTEXT.md:324 is the stale glossary line (newest-wins). Amending docs now — ADR-0013 first.


Write

# Psyche-reconciled context merge (version vectors, single active reconciler)

## Status

accepted (2026-06-03) — confirmed with user at D6 build start (the M4-PLAN §D6b
scheduled touchpoint). Builds on ADR-0003 (multi-instance), ADR-0011 (BranchStore),
ADR-0012 (Psyche trust boundary), KNOWN-HAZARDS 6.5.

## Context

Cross-node Psyche sync (M4-D6) makes the context store multi-writer across
machines. The local precedence guard (6.5: `source` + `routed_at` marker, direct
beats stale LLM) must generalize to distributed writes. The M4-PLAN D6b decision
already locked the *ordering* model: per-node **monotonic epoch counters + a
version vector** (one counter per node — the same `EpochSource` that feeds the
D3b registry lease), dominate→accept, dominated→drop, **concurrent→surface, never
silent newest-wins**; wall-clock is at most a human tiebreaker hint.

What remained open was the **conflict-surface UX**: what happens to a concurrent
pair `(vA, vB)` once detected. Options considered:

1. **Quarantine + manual CLI resolve** — local file untouched, remote version
   parked, `spt context conflicts` + `resolve --keep local|remote`. Safe but the
   mind stays forked until a human notices.
2. **Inline both versions** under explicit section headers — self-heals via the
   next commune but bloats/confuses the live mind file in the interim.
3. **Psyche auto-reconcile** — the mind's own author merges its own mind.

A second fork: sync transport. State-record transfer (NDJSON snapshots, each
node's git history local-only) is simpler, but produces **divergent per-node
histories** — rejected outright; the context DAG must be shared. Third fork:
BranchStore backing (git CLI vs gitoxide).

## Decision

**Concurrent context writes are reconciled by the endpoint's own Psyche** — a
bounded, stdin-fed, stdout-captured Psyche turn receives both versions (plus
vector + wall-clock hints) and emits the merged file. Mechanics:

- **Holding state is durable and replicated.** On detecting a concurrent pair,
  both versions persist as **tracked conflict artifacts** (deterministic
  content-hash names, in-repo, so conflict state itself syncs). The local
  working file stays untouched — the active mind is never clobbered by an
  unresolved conflict. An event seam fires (a D8 notif producer later).
- **Exactly one reconciler: the active instance's node** (the existing
  active-instance-authoritative rule — the Psyche lives where the mind is being
  used). No active instance anywhere → deterministic fallback: lowest node id
  among conflict holders. This kills reconcile storms (two nodes merging the
  same pair into textually-different results, conflicting again).
- **The merged write carries `join(vA, vB)` + a bump of the reconciler's own
  vector entry** — it strictly dominates both parents, so it propagates as a
  plain accept everywhere and clears the artifacts subnet-wide.
- **Failure is safe by construction.** The turn is bounded (timeout, 5.3) and
  validated (non-empty, marker-parseable); any failure — harness absent, turn
  error, garbage output — leaves the artifacts in place for retry at the next
  sync/activation. Both versions are never lost. The reconciled output is an
  `llm`-source write, so the 6.5 direct-write protection window still applies
  (a fresher direct write suppresses it; artifacts persist; retry re-merges
  against the new local version).
- **Dependency re-sequenced:** the bounded stdout-captured Psyche turn driver is
  **ADR-0012/D7.5a machinery pulled forward into D6b** (the relay/sanitize
  halves, D7.5b/c, stay at D7.5). The interim `Stdio::null()` spawn is unusable
  here (7.3).

**Transport: git-native bundles over Iroh — the context DAG is shared.** Peers
exchange incremental `git bundle`s over broker QUIC streams, **pull-based and
ref-scoped**: a node requests `a-<id>` for endpoints it has `synced(E,S)` and
`p-<project>` only for projects it hosts (the requester names refs — two-tier
scoping falls out of the pull model, no new registry fields). First contact
merges unrelated seed histories once (`--allow-unrelated-histories`); thereafter
one DAG, one merge commit per sync. The merge driver resolves **per file by the
vector rule** — never a git line-merge, never a conflict marker in a mind file.

**BranchStore v1 drives the system `git` binary** (the `git pull` handoff seam
already assumes git on the node), behind a trait shaped so a gitoxide swap is a
later drop-in. ADR-0011's commit-latency spike rides D6a empirically.

**Hub mode (ADR-0002 amendment) stays a deferred opt-in seam** — D6 is P2P only.
The shared-DAG choice is what keeps later hub interop natural (same driver, a
GitHub remote is just another peer).

## Consequences

- The mind self-heals: conflicts resolve in one Psyche turn instead of waiting
  on a human, while the durable-artifact holding state keeps the never-lose-data
  invariant when reconcile is unavailable.
- 6.5 generalizes cleanly: marker grows `spt:node` + `spt:vector`; the existing
  parse/render extends, not rewrites (anticipated in `spt-live/src/context.rs`).
- D6b swallows D7.5a — D7.5 shrinks to taxonomy + relay/sanitize.
- The reconciler election leans on registry liveness (D3); a partition with two
  "active" claims is bounded by the D3b epoch lease, and worst case produces a
  re-conflict of merged outputs — detected and re-reconciled, not lost.
- State-record sync and CRDT machinery are rejected for context (text snapshots,
  ≤N infrequent writers — vectors + reconcile are sufficient and cheaper).


File created successfully at: C:\Users\decid\Documents\projects\spt-core\docs\adr\0013-psyche-reconciled-context-merge.md (file state is current in your context — no need to Read it back)

# Psyche-reconciled context merge (version vectors, single active reconciler)

## Status

accepted (2026-06-03) — confirmed with user at D6 build start (the M4-PLAN §D6b
scheduled touchpoint). Builds on ADR-0003 (multi-instance), ADR-0011 (BranchStore),
ADR-0012 (Psyche trust boundary), KNOWN-HAZARDS 6.5.

## Context

Cross-node Psyche sync (M4-D6) makes the context store multi-writer across
machines. The local precedence guard (6.5: `source` + `routed_at` marker, direct
beats stale LLM) must generalize to distributed writes. The M4-PLAN D6b decision
already locked the *ordering* model: per-node **monotonic epoch counters + a
version vector** (one counter per node — the same `EpochSource` that feeds the
D3b registry lease), dominate→accept, dominated→drop, **concurrent→surface, never
silent newest-wins**; wall-clock is at most a human tiebreaker hint.

What remained open was the **conflict-surface UX**: what happens to a concurrent
pair `(vA, vB)` once detected. Options considered:

1. **Quarantine + manual CLI resolve** — local file untouched, remote version
   parked, `spt context conflicts` + `resolve --keep local|remote`. Safe but the
   mind stays forked until a human notices.
2. **Inline both versions** under explicit section headers — self-heals via the
   next commune but bloats/confuses the live mind file in the interim.
3. **Psyche auto-reconcile** — the mind's own author merges its own mind.

A second fork: sync transport. State-record transfer (NDJSON snapshots, each
node's git history local-only) is simpler, but produces **divergent per-node
histories** — rejected outright; the context DAG must be shared. Third fork:
BranchStore backing (git CLI vs gitoxide).

## Decision

**Concurrent context writes are reconciled by the endpoint's own Psyche** — a
bounded, stdin-fed, stdout-captured Psyche turn receives both versions (plus
vector + wall-clock hints) and emits the merged file. Mechanics:

- **Holding state is durable and replicated.** On detecting a concurrent pair,
  both versions persist as **tracked conflict artifacts** (deterministic
  content-hash names, in-repo, so conflict state itself syncs). The local
  working file stays untouched — the active mind is never clobbered by an
  unresolved conflict. An event seam fires (a D8 notif producer later).
- **Exactly one reconciler: the active instance's node** (the existing
  active-instance-authoritative rule — the Psyche lives where the mind is being
  used). No active instance anywhere → deterministic fallback: lowest node id
  among conflict holders. This kills reconcile storms (two nodes merging the
  same pair into textually-different results, conflicting again).
- **The merged write carries `join(vA, vB)` + a bump of the reconciler's own
  vector entry** — it strictly dominates both parents, so it propagates as a
  plain accept everywhere and clears the artifacts subnet-wide.
- **Failure is safe by construction.** The turn is bounded (timeout, 5.3) and
  validated (non-empty, marker-parseable); any failure — harness absent, turn
  error, garbage output — leaves the artifacts in place for retry at the next
  sync/activation. Both versions are never lost. The reconciled output is an
  `llm`-source write, so the 6.5 direct-write protection window still applies
  (a fresher direct write suppresses it; artifacts persist; retry re-merges
  against the new local version).
- **Dependency re-sequenced:** the bounded stdout-captured Psyche turn driver is
  **ADR-0012/D7.5a machinery pulled forward into D6b** (the relay/sanitize
  halves, D7.5b/c, stay at D7.5). The interim `Stdio::null()` spawn is unusable
  here (7.3).

**Transport: git-native bundles over Iroh — the context DAG is shared.** Peers
exchange incremental `git bundle`s over broker QUIC streams, **pull-based and
ref-scoped**: a node requests `a-<id>` for endpoints it has `synced(E,S)` and
`p-<project>` only for projects it hosts (the requester names refs — two-tier
scoping falls out of the pull model, no new registry fields). First contact
merges unrelated seed histories once (`--allow-unrelated-histories`); thereafter
one DAG, one merge commit per sync. The merge driver resolves **per file by the
vector rule** — never a git line-merge, never a conflict marker in a mind file.

**BranchStore v1 drives the system `git` binary** (the `git pull` handoff seam
already assumes git on the node), behind a trait shaped so a gitoxide swap is a
later drop-in. ADR-0011's commit-latency spike rides D6a empirically.

**Hub mode (ADR-0002 amendment) stays a deferred opt-in seam** — D6 is P2P only.
The shared-DAG choice is what keeps later hub interop natural (same driver, a
GitHub remote is just another peer).

## Consequences

- The mind self-heals: conflicts resolve in one Psyche turn instead of waiting
  on a human, while the durable-artifact holding state keeps the never-lose-data
  invariant when reconcile is unavailable.
- 6.5 generalizes cleanly: marker grows `spt:node` + `spt:vector`; the existing
  parse/render extends, not rewrites (anticipated in `spt-live/src/context.rs`).
- D6b swallows D7.5a — D7.5 shrinks to taxonomy + relay/sanitize.
- The reconciler election leans on registry liveness (D3); a partition with two
  "active" claims is bounded by the D3b epoch lease, and worst case produces a
  re-conflict of merged outputs — detected and re-reconciled, not lost.
- State-record sync and CRDT machinery are rejected for context (text snapshots,
  ≤N infrequent writers — vectors + reconcile are sufficient and cheaper).


Edit

Merge is conflict-free: a custom merge driver resolves context files by the precedence marker (`spt:source`/`routed_at`/**`node`**, newest-wins — KNOWN-HAZARDS 6.5), never a git line-merge; the single-writer invariant (active-instance-authoritative) makes true conflicts rare.

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

- **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 is conflict-free: a custom merge driver resolves context files by the precedence marker (`spt:source`/`routed_at`/**`node`**, newest-wins — KNOWN-HAZARDS 6.5), never a git line-merge; 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.

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


**Merge = precedence marker, never raw 3-way.** A raw git merge of LLM-authored markdown would inject `<<<<<<<` conflict markers into an agent's mind. Instead `tracked/` ships a `.gitattributes` declaring context files use a **custom spt merge driver** that resolves by the precedence marker on every write — `<!-- spt:source=direct|llm spt:routed_at=<iso> spt:node=<node_id> -->` (node-id added for cross-node) — **newest wins, per file**. Never a conflict marker. The marker is the merge authority; git topology is just versioning + the branch-worktree views.

**Merge = precedence marker, never raw 3-way.** A raw git merge of LLM-authored markdown would inject `<<<<<<<` conflict markers into an agent's mind. Instead `tracked/` ships a `.gitattributes` declaring context files use a **custom spt merge driver** that resolves by the precedence marker, per file — `<!-- spt:source=direct|llm spt:routed_at_ms=<ms> spt:node=<node_id> spt:vector=<node:epoch,…> -->` (node id + **version vector** added for cross-node; vector entries come from each node's monotonic `EpochSource`, the same counter behind the D3b registry lease — wall-clock is never the ordering authority). Rule (ADR-0013): incoming vector **dominates** → accept; **dominated** → drop (stale); **concurrent** (neither dominates) → **surface, never silent newest-wins** — both versions persist as **tracked conflict artifacts** (deterministic content-hash names, replicated like any context file) while the local working file stays untouched, then the endpoint's **own Psyche reconciles** them in one bounded stdout-captured turn on the **active instance's node** (single reconciler; fallback lowest node id). The merged write carries `join(vA,vB)` + a bump of the reconciler's entry — it dominates both parents and clears the artifacts subnet-wide. Reconcile failure/unavailability leaves the artifacts for retry; both versions are never lost. Never a conflict marker in a mind file. The marker is the merge authority; git topology is just versioning + the branch-worktree views.


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

**Single-writer invariant.** Each context file has exactly one logical writer — the owning agent's *active* instance (the active-instance-authoritative rule). Two nodes never legitimately write the same file simultaneously, so sync is mostly **propagation**, and disjoint files (node A has `agents/ling`, node B has `agents/momo`) simply **union**.

-**Merge = precedence marker, never raw 3-way.** A raw git merge of LLM-authored markdown would inject `<<<<<<<` conflict markers into an agent's mind. Instead `tracked/` ships a `.gitattributes` declaring context files use a **custom spt merge driver** that resolves by the precedence marker on every write — `<!-- spt:source=direct|llm spt:routed_at=<iso> spt:node=<node_id> -->` (node-id added for cross-node) — **newest wins, per file**. Never a conflict marker. The marker is the merge authority; git topology is just versioning + the branch-worktree views.

+**Merge = precedence marker, never raw 3-way.** A raw git merge of LLM-authored markdown would inject `<<<<<<<` conflict markers into an agent's mind. Instead `tracked/` ships a `.gitattributes` declaring context files use a **custom spt merge driver** that resolves by the precedence marker, per file — `<!-- spt:source=direct|llm spt:routed_at_ms=<ms> spt:node=<node_id> spt:vector=<node:epoch,…> -->` (node id + **version vector** added for cross-node; vector entries come from each node's monotonic `EpochSource`, the same counter behind the D3b registry lease — wall-clock is never the ordering authority). Rule (ADR-0013): incoming vector **dominates** → accept; **dominated** → drop (stale); **concurrent** (neither dominates) → **surface, never silent newest-wins** — both versions persist as **tracked conflict artifacts** (deterministic content-hash names, replicated like any context file) while the local working file stays untouched, then the endpoint's **own Psyche reconciles** them in one bounded stdout-captured turn on the **active instance's node** (single reconciler; fallback lowest node id). The merged write carries `join(vA,vB)` + a bump of the reconciler's entry — it dominates both parents and clears the artifacts subnet-wide. Reconcile failure/unavailability leaves the artifacts for retry; both versions are never lost. Never a conflict marker in a mind file. The marker is the merge authority; git topology is just versioning + the branch-worktree views.

**Divergent / unrelated histories are expected.** Each node independently seeds its own `tracked/` repo on first run, so `a-ling` on A and on B share **no common ancestor**. Sync therefore merges with **`--allow-unrelated-histories`** + the driver. History becomes a multi-parent DAG (one merge commit per sync) — cosmetic; periodic `git gc` keeps it tidy.


- **P2P (default — gh off):** peers fetch each other's `a-`/`p-` branches over Iroh; merge with driver + `--allow-unrelated-histories`. No central point; the "no account" core promise.

- **P2P (default — gh off):** peers exchange **incremental `git bundle`s over broker QUIC streams** — **pull-based, ref-scoped**: a node requests `a-<id>` for endpoints it has `synced(E,S)` and `p-<project>` only for projects it hosts (the requester names refs, so two-tier scoping falls out of the pull model); receiver fetches from the bundle + merges with driver + `--allow-unrelated-histories`. **The context DAG is shared across nodes** (one history, one merge commit per sync — divergent per-node histories are rejected, ADR-0013). No central point; the "no account" core promise.


-- **P2P (default — gh off):** peers fetch each other's `a-`/`p-` branches over Iroh; merge with driver + `--allow-unrelated-histories`. No central point; the "no account" core promise.

+- **P2P (default — gh off):** peers exchange **incremental `git bundle`s over broker QUIC streams** — **pull-based, ref-scoped**: a node requests `a-<id>` for endpoints it has `synced(E,S)` and `p-<project>` only for projects it hosts (the requester names refs, so two-tier scoping falls out of the pull model); receiver fetches from the bundle + merges with driver + `--allow-unrelated-histories`. **The context DAG is shared across nodes** (one history, one merge commit per sync — divergent per-node histories are rejected, ADR-0013). No central point; the "no account" core promise.

- **Hub (opt-in — gh on, via `spt context-github-setup`):** every node **pull → merge → push** against a private GitHub remote — always-online sync hub *and* live GUI view (no stale-mirror-node problem). Push-retry on non-fast-forward contention. **Prefer-GitHub, P2P backstop** when GitHub/SSH is unreachable (the P2P stack is always present anyway).

**Scope:** hub mode is the **context-repo sync transport only.** Messaging, the subnet registry, remote-drive, pairing, and presence always ride P2P/Iroh — GitHub never carries real-time agent traffic, and gh-on never removes the P2P requirement.


- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity, and the freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the distributed generalization of this guard. Highest-value carryover for the sync design.

- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity **plus a per-node version vector** (entries from each node's monotonic `EpochSource`; wall-clock never orders). Distributed rule (ADR-0013, M4-D6): dominate→accept, dominated→drop, **concurrent→surface as durable replicated conflict artifacts and Psyche-reconcile on the active instance's node — never silent newest-wins, never lose either version**. The freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the same guard read as vector dominance. Highest-value carryover for the sync design.


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

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


214	- **Sister cite:** CHANGELOG v1.11.7; `src/live/context.rs:615` (removed delete).
215
216	### 6.5 Direct-write precedence guard against stale LLM overwrites
217	- **Failure:** the LLM emits an older snapshot that clobbers a fresher direct write to a context file.
218	- **Invariant:** every context write carries a source+timestamp precedence marker; LLM writes within a protection window after a recent direct write are suppressed (logged); direct writes always proceed.
219	- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity, and the freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the distributed generalization of this guard. Highest-value carryover for the sync design.
220	- **Sister cite:** CHANGELOG v1.11.6; `src/owl/echo_commune.rs`.
221
222	---
223

- **Sister cite:** CHANGELOG v1.11.7; `src/live/context.rs:615` (removed delete).

### 6.5 Direct-write precedence guard against stale LLM overwrites
- **Failure:** the LLM emits an older snapshot that clobbers a fresher direct write to a context file.
- **Invariant:** every context write carries a source+timestamp precedence marker; LLM writes within a protection window after a recent direct write are suppressed (logged); direct writes always proceed.
- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity, and the freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the distributed generalization of this guard. Highest-value carryover for the sync design.
- **Sister cite:** CHANGELOG v1.11.6; `src/owl/echo_commune.rs`.

---


- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity, and the freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the distributed generalization of this guard. Highest-value carryover for the sync design.

- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity **plus a per-node version vector** (entries from each node's monotonic `EpochSource`; wall-clock never orders). Distributed rule (ADR-0013, M4-D6): dominate→accept, dominated→drop, **concurrent→surface as durable replicated conflict artifacts + Psyche-reconcile on the active instance's node — never silent newest-wins, never lose either version**. The freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the same guard read as vector dominance. Highest-value carryover for the sync design.


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

### 6.5 Direct-write precedence guard against stale LLM overwrites

- **Failure:** the LLM emits an older snapshot that clobbers a fresher direct write to a context file.

- **Invariant:** every context write carries a source+timestamp precedence marker; LLM writes within a protection window after a recent direct write are suppressed (logged); direct writes always proceed.

-- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity, and the freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the distributed generalization of this guard. Highest-value carryover for the sync design.

+- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity **plus a per-node version vector** (entries from each node's monotonic `EpochSource`; wall-clock never orders). Distributed rule (ADR-0013, M4-D6): dominate→accept, dominated→drop, **concurrent→surface as durable replicated conflict artifacts + Psyche-reconcile on the active instance's node — never silent newest-wins, never lose either version**. The freshness rule (newest-and-newer-than-mine, per the cross-instance context-freshness feature) is the same guard read as vector dominance. Highest-value carryover for the sync design.

- **Sister cite:** CHANGELOG v1.11.6; `src/owl/echo_commune.rs`.


  stays only as a human-facing tiebreaker hint inside a flagged conflict, never as the
  ordering authority. Confirm with user at D6 build start before locking the conflict-surface UX.

  stays only as a human-facing tiebreaker hint inside a flagged conflict, never as the
  ordering authority. **Conflict-surface UX CONFIRMED with user (2026-06-03, ADR-0013):
  Psyche auto-reconcile** — both versions persist as tracked conflict artifacts (never
  lose either), the endpoint's own Psyche merges them in one bounded stdout-captured turn
  on the **active instance's node** (single reconciler; fallback lowest node id); merged
  write = `join(vA,vB)` + reconciler bump (dominates both parents, clears subnet-wide).
  **D7.5a (the bounded stdout-captured Psyche turn driver) is pulled forward into D6b**
  as its prerequisite; D7.5b/c stay at D7.5. Transport confirmed git-native: incremental
  bundles over broker streams, pull-based ref-scoped, shared DAG (no per-node divergent
  histories); BranchStore v1 = git-CLI-backed (REQ-STORE-1); hub mode stays a deferred
  opt-in seam.


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

  stays only as a human-facing tiebreaker hint inside a flagged conflict, never as the
  ordering authority. Confirm with user at D6 build start before locking the conflict-surface UX.

  stays only as a human-facing tiebreaker hint inside a flagged conflict, never as the
  ordering authority. **Conflict-surface UX CONFIRMED with user (2026-06-03, ADR-0013):
  Psyche auto-reconcile** — both versions persist as tracked conflict artifacts (never
  lose either), the endpoint's own Psyche merges them in one bounded stdout-captured turn
  on the **active instance's node** (single reconciler; fallback lowest node id); merged
  write = `join(vA,vB)` + reconciler bump (dominates both parents, clears subnet-wide).
  **D7.5a (the bounded stdout-captured Psyche turn driver) is pulled forward into D6b**
  as its prerequisite; D7.5b/c stay at D7.5. Transport confirmed git-native: incremental
  bundles over broker streams, pull-based ref-scoped, shared DAG (no per-node divergent
  histories); BranchStore v1 = git-CLI-backed (REQ-STORE-1); hub mode stays a deferred
  opt-in seam.

version vector gives both cheaply and **unifies with D3b's epoch-lease (#8)** — one

per-node monotonic epoch source serves registry leases *and* sync precedence. Wall-clock

-  ordering authority. Confirm with user at D6 build start before locking the conflict-surface UX.

+  ordering authority. **Conflict-surface UX CONFIRMED with user (2026-06-03, ADR-0013):

+  Psyche auto-reconcile** — both versions persist as tracked conflict artifacts (never

+  lose either), the endpoint's own Psyche merges them in one bounded stdout-captured turn

+  on the **active instance's node** (single reconciler; fallback lowest node id); merged

+  write = `join(vA,vB)` + reconciler bump (dominates both parents, clears subnet-wide).

+  **D7.5a (the bounded stdout-captured Psyche turn driver) is pulled forward into D6b**

+  as its prerequisite; D7.5b/c stay at D7.5. Transport confirmed git-native: incremental

+  bundles over broker streams, pull-based ref-scoped, shared DAG (no per-node divergent

+  histories); BranchStore v1 = git-CLI-backed (REQ-STORE-1); hub mode stays a deferred

- **D6c** Subnet-exclusive sync honored (hidden ⟹ not synced; per-endpoint membership list).

- **Reqs:** REQ-NET-3, REQ-INST-2, REQ-INST-5; `REQ-HAZARD-DIRECT-WRITE-PRECEDENCE` (extend

coverage to multi-writer/node-id). · **Acceptance:** the mind follows the user across two


- **D7.5a** Drive the live-Psyche turn as a **bounded, stdin-fed, stdout-CAPTURED** harness
  invocation (per-turn `--resume`, mirroring the sister) — **not** the detached
  `Stdio::null()` `spawn_session`. The captured stdout is the reply/notify channel.

- **D7.5a** Drive the live-Psyche turn as a **bounded, stdin-fed, stdout-CAPTURED** harness
  invocation (per-turn `--resume`, mirroring the sister) — **not** the detached
  `Stdio::null()` `spawn_session`. The captured stdout is the reply/notify channel.
  **Pulled forward into D6b** (ADR-0013: the conflict-reconcile turn needs it); D7.5
  consumes the driver built there.


- **D7.5a** Drive the live-Psyche turn as a **bounded, stdin-fed, stdout-CAPTURED** harness
  invocation (per-turn `--resume`, mirroring the sister) — **not** the detached
  `Stdio::null()` `spawn_session`. The captured stdout is the reply/notify channel.
  **Pulled forward into D6b** (ADR-0013: the conflict-reconcile turn needs it); D7.5
  consumes the driver built there.

+  **Pulled forward into D6b** (ADR-0013: the conflict-reconcile turn needs it); D7.5

- **D7.5b** Parse the Psyche's stdout `<EVENT type="reply">` / `<EVENT type="notify">`

envelopes; extend the `spt-proto::event` type taxonomy (`+reply`, `+notify`).

- **D7.5c** Daemon relay + **sanitize** (the trust boundary): strip any Psyche-supplied


Docs synced. Now the JIT plan — `M4-D6-PLAN.md`:


Write

# M4-D6 — cross-node Psyche sync (P2P), retire gh-repo interim (JIT plan)

**Status:** drafted 2026-06-03 at D6 start. D5 complete (D5a `caab433`, D5b `4ea3b37`,
D5c `269b8ef`+`fcad41c`, D5d `f49aba1` — all CI-green). All D6 design forks resolved
with user 2026-06-03 → **ADR-0013** (Psyche-reconciled context merge); decisions
mirrored into M4-PLAN §D6b/§D7.5a, CONTEXT.md §Cross-node Psyche sync, STORAGE.md
§Cross-node context sync, KNOWN-HAZARDS 6.5.

## Goal

The mind follows the user across machines: an endpoint's **live context** replicates to
all its instances, **project context** only to same-project instances (loopback
two-daemon in CI; two-host at D9). A concurrent cross-node write is **surfaced and
Psyche-reconciled, never silently dropped**. Reqs: REQ-NET-3, REQ-INST-2, REQ-INST-5,
REQ-STORE-1, REQ-HAZARD-DIRECT-WRITE-PRECEDENCE (distributed generalization).

## Decisions (locked — ADR-0013, user-confirmed 2026-06-03)

- **Merge order = version vectors, wall-clock never orders.** Marker grows
  `spt:node=<node_id> spt:vector=<node:epoch,…>`; entries from each node's monotonic
  `EpochSource` (`spt-store/src/epoch.rs` — the same counter behind the D3b lease, the
  planned unification). Dominate→accept, dominated→drop, concurrent→surface. A
  missing/legacy vector = ⊥ (dominated by any vector; two ⊥s fall back to the local
  6.5 window rule).
- **Concurrent → Psyche auto-reconcile, single reconciler.** Both versions persist as
  **tracked conflict artifacts** (deterministic content-hash names — idempotent,
  replicate like any context file; local working file untouched). The **active
  instance's node** runs one bounded, stdin-fed, **stdout-captured** Psyche turn over
  both versions; fallback reconciler = lowest node id among holders. Merged write =
  `join(vA,vB)` + reconciler bump — dominates both parents, clears artifacts
  subnet-wide. Turn bounded (5.3) + output validated (non-empty, marker-parseable);
  any failure leaves artifacts for retry at next sync/activation + fires the event
  seam (D8 notif producer later). Reconciled output is `source=llm` — the 6.5
  direct-write window still applies.
- **D7.5a pulled forward into D6b.** The bounded stdout-captured Psyche turn driver
  (NOT the `Stdio::null()` interim, 7.3) is built here; D7.5 keeps b/c (taxonomy +
  relay/sanitize). Driver code tagged `[impl->REQ-HAZARD-PSYCHE-OUTBOUND-PROXY]` as
  evidence, but that req **activates at D7.5** (full invariant needs the relay).
- **Transport = git-native bundles over broker QUIC streams; the DAG is shared.**
  Pull-based, **ref-scoped**: a node requests `a-<id>` for endpoints with
  `synced(E,S)` (the D3e gate, finally consumed) and `p-<project>` only for projects
  it hosts — two-tier scoping falls out of the pull model, no new registry fields.
  Responder builds an incremental `git bundle` (`^have` exclusions); requester fetches
  from it + merges with the vector driver; first contact merges unrelated seeds once
  (`--allow-unrelated-histories`). Divergent per-node histories rejected.
- **BranchStore v1 = git-CLI-backed** (system `git`; the `git pull` handoff seam
  already assumes it) behind a trait so gitoxide is a later drop-in. Commit-per-write
  latency measured in D6a tests (the ADR-0011 spike, empirically).
- **Hub mode (gh opt-in) stays a deferred seam** — D6 is P2P only. Interim
  gh-repo-sync skill retires after the D9 two-host proof.

## Pieces (build order)

1. **D6a — context store realization** (REQ-STORE-1, REQ-INST-2).
   `spt-store::branchstore`: ensure-repo/ensure-branch, **commit = checkpoint, tip =
   resume**, linked worktree views with relative paths (`worktree.useRelativePaths`,
   Git ≥ 2.48; else `git worktree repair` fallback — STORAGE §Worktree portability).
   `tracked/` layout + path fns in `perch.rs` (`agents/<id>/live-context.md` on
   `a-<id>`; `projects/<pid>/<id>/project-context.md` on `p-<pid>` —
   REQ-HAZARD-SINGLE-PATH-SOURCE tags). `project_id` derivation (git remote URL →
   folder-name fallback, STORAGE §project_id). Ingest migration: commune drops'
   `<live-context>`/`<project-context>` slices route into the tier files
   (precedence-guarded writes, `ingest.rs` folds there instead of flat `context.md`);
   resume/preload reads follow. `spt rename` context-branch arm (the REQ-INST-11
   seam noted at D3f: `a-<old>` → `a-<new>`).
2. **D6b — distributed precedence + Psyche reconcile** (extends
   REQ-HAZARD-DIRECT-WRITE-PRECEDENCE; new hazard req below).
   `VersionVector` type + dominance (pure, hermetic); marker v2 parse/render
   (back-compat: v1 markers = ⊥ vector); per-file vector merge fn; conflict artifact
   write/clear; reconciler election (registry Active row → that node; none → lowest
   node id); **the bounded stdout-captured Psyche turn driver** (per-turn `--resume`,
   timeout 5.3, captured stdout = the merged file candidate); reconcile flow + output
   validation + retry-on-failure; event seam record. Negative tests: turn timeout,
   garbage output, harness absent — artifacts persist every time.
3. **D6c — bundle sync over the wire + gates** (REQ-NET-3, REQ-INST-5; consumes
   REQ-INST-13). NDJSON control records (`SyncRequest { refs, have_tips }` /
   bundle-chunk records riding the D5c xfer chunking) over broker streams; responder
   ref-scope enforcement (**`synced(E,S)` + visibility gate checked server-side too**
   — a peer must not be able to pull a hidden/unsynced mind, fail-closed); requester
   fetch + driver merge + two-parent merge commit; sync triggers: context-write
   notify → peers pull, **activation freshness feed** (dormant→active pulls first —
   the cross-instance context-freshness auto-feed + the D5c handoff seam's
   fresh-with-preload "mind" leg), periodic anti-entropy backstop. Loopback
   two-daemon tests: two-tier scoping (live everywhere, project same-project only),
   unsynced/hidden refused server-side, concurrent-write conflict → artifacts on both
   nodes → reconcile on active node → merged write converges everywhere.
4. **FAULT-MATRIX rows** (same change as the piece that lands them): stream loss
   mid-bundle (re-pull idempotent), partition concurrent writes (conflict path),
   reconcile turn failure (artifacts persist), registry ambiguity during election
   (re-conflict of merged outputs detected, not lost).

## Requirement activation (rule 5)

- **REQ-STORE-1, REQ-INST-2** → `[impl, unit]` at D6a.
- **New (rule 3): `REQ-HAZARD-CONFLICT-BOTH-PRESERVED`** — a surfaced concurrent pair
  is durably preserved (both versions) until a dominating write clears it; no
  reconcile path may discard an unmerged version. Register in `traceable-reqs.toml`
  at D6b start; `[impl, unit]` at D6b.
- **REQ-HAZARD-DIRECT-WRITE-PRECEDENCE** — stays `[impl, unit]`; D6b adds the
  multi-writer/node-id evidence.
- **REQ-NET-3, REQ-INST-5** → `[impl, unit]` at D6c. `int` = D9 two-host E2E.
- **REQ-HAZARD-PSYCHE-OUTBOUND-PROXY** — evidence tagged at D6b, **activates at D7.5**.

## NOT in D6

- Hub mode (gh opt-in) — deferred seam; interim skill retires at D9.
- D7.5b/c (event taxonomy + daemon relay/sanitize), D8 notif (D6 emits event seam only).
- memformat tier-tagged topics / block-level OCC (CONTEXT-MEMORY §Deferred) — the
  two-tier *files* land here; the schema evolution is post-v1.
- Two-host integration — D9 (loopback shapes only here).

## Conventions (carried)

- NO `cargo fmt`. Tag `[impl->REQ-…]` / `[unit->REQ-…]` on real evidence.
  `traceable-reqs check` from repo root before done. Linux CI clippy `-D warnings` is
  the real gate. Push each slice → watch green before next (verify FINAL via
  `gh run list --json conclusion`).
  Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.


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

# M4-D6 — cross-node Psyche sync (P2P), retire gh-repo interim (JIT plan)

**Status:** drafted 2026-06-03 at D6 start. D5 complete (D5a `caab433`, D5b `4ea3b37`,
D5c `269b8ef`+`fcad41c`, D5d `f49aba1` — all CI-green). All D6 design forks resolved
with user 2026-06-03 → **ADR-0013** (Psyche-reconciled context merge); decisions
mirrored into M4-PLAN §D6b/§D7.5a, CONTEXT.md §Cross-node Psyche sync, STORAGE.md
§Cross-node context sync, KNOWN-HAZARDS 6.5.

## Goal

The mind follows the user across machines: an endpoint's **live context** replicates to
all its instances, **project context** only to same-project instances (loopback
two-daemon in CI; two-host at D9). A concurrent cross-node write is **surfaced and
Psyche-reconciled, never silently dropped**. Reqs: REQ-NET-3, REQ-INST-2, REQ-INST-5,
REQ-STORE-1, REQ-HAZARD-DIRECT-WRITE-PRECEDENCE (distributed generalization).

## Decisions (locked — ADR-0013, user-confirmed 2026-06-03)

- **Merge order = version vectors, wall-clock never orders.** Marker grows
  `spt:node=<node_id> spt:vector=<node:epoch,…>`; entries from each node's monotonic
  `EpochSource` (`spt-store/src/epoch.rs` — the same counter behind the D3b lease, the
  planned unification). Dominate→accept, dominated→drop, concurrent→surface. A
  missing/legacy vector = ⊥ (dominated by any vector; two ⊥s fall back to the local
  6.5 window rule).
- **Concurrent → Psyche auto-reconcile, single reconciler.** Both versions persist as
  **tracked conflict artifacts** (deterministic content-hash names — idempotent,
  replicate like any context file; local working file untouched). The **active
  instance's node** runs one bounded, stdin-fed, **stdout-captured** Psyche turn over
  both versions; fallback reconciler = lowest node id among holders. Merged write =
  `join(vA,vB)` + reconciler bump — dominates both parents, clears artifacts
  subnet-wide. Turn bounded (5.3) + output validated (non-empty, marker-parseable);
  any failure leaves artifacts for retry at next sync/activation + fires the event
  seam (D8 notif producer later). Reconciled output is `source=llm` — the 6.5
  direct-write window still applies.
- **D7.5a pulled forward into D6b.** The bounded stdout-captured Psyche turn driver
  (NOT the `Stdio::null()` interim, 7.3) is built here; D7.5 keeps b/c (taxonomy +
  relay/sanitize). Driver code tagged `[impl->REQ-HAZARD-PSYCHE-OUTBOUND-PROXY]` as
  evidence, but that req **activates at D7.5** (full invariant needs the relay).
- **Transport = git-native bundles over broker QUIC streams; the DAG is shared.**
  Pull-based, **ref-scoped**: a node requests `a-<id>` for endpoints with
  `synced(E,S)` (the D3e gate, finally consumed) and `p-<project>` only for projects
  it hosts — two-tier scoping falls out of the pull model, no new registry fields.
  Responder builds an incremental `git bundle` (`^have` exclusions); requester fetches
  from it + merges with the vector driver; first contact merges unrelated seeds once
  (`--allow-unrelated-histories`). Divergent per-node histories rejected.
- **BranchStore v1 = git-CLI-backed** (system `git`; the `git pull` handoff seam
  already assumes it) behind a trait so gitoxide is a later drop-in. Commit-per-write
  latency measured in D6a tests (the ADR-0011 spike, empirically).
- **Hub mode (gh opt-in) stays a deferred seam** — D6 is P2P only. Interim
  gh-repo-sync skill retires after the D9 two-host proof.

## Pieces (build order)

1. **D6a — context store realization** (REQ-STORE-1, REQ-INST-2).
   `spt-store::branchstore`: ensure-repo/ensure-branch, **commit = checkpoint, tip =
   resume**, linked worktree views with relative paths (`worktree.useRelativePaths`,
   Git ≥ 2.48; else `git worktree repair` fallback — STORAGE §Worktree portability).
   `tracked/` layout + path fns in `perch.rs` (`agents/<id>/live-context.md` on
   `a-<id>`; `projects/<pid>/<id>/project-context.md` on `p-<pid>` —
   REQ-HAZARD-SINGLE-PATH-SOURCE tags). `project_id` derivation (git remote URL →
   folder-name fallback, STORAGE §project_id). Ingest migration: commune drops'
   `<live-context>`/`<project-context>` slices route into the tier files
   (precedence-guarded writes, `ingest.rs` folds there instead of flat `context.md`);
   resume/preload reads follow. `spt rename` context-branch arm (the REQ-INST-11
   seam noted at D3f: `a-<old>` → `a-<new>`).
2. **D6b — distributed precedence + Psyche reconcile** (extends
   REQ-HAZARD-DIRECT-WRITE-PRECEDENCE; new hazard req below).
   `VersionVector` type + dominance (pure, hermetic); marker v2 parse/render
   (back-compat: v1 markers = ⊥ vector); per-file vector merge fn; conflict artifact
   write/clear; reconciler election (registry Active row → that node; none → lowest
   node id); **the bounded stdout-captured Psyche turn driver** (per-turn `--resume`,
   timeout 5.3, captured stdout = the merged file candidate); reconcile flow + output
   validation + retry-on-failure; event seam record. Negative tests: turn timeout,
   garbage output, harness absent — artifacts persist every time.
3. **D6c — bundle sync over the wire + gates** (REQ-NET-3, REQ-INST-5; consumes
   REQ-INST-13). NDJSON control records (`SyncRequest { refs, have_tips }` /
   bundle-chunk records riding the D5c xfer chunking) over broker streams; responder
   ref-scope enforcement (**`synced(E,S)` + visibility gate checked server-side too**
   — a peer must not be able to pull a hidden/unsynced mind, fail-closed); requester
   fetch + driver merge + two-parent merge commit; sync triggers: context-write
   notify → peers pull, **activation freshness feed** (dormant→active pulls first —
   the cross-instance context-freshness auto-feed + the D5c handoff seam's
   fresh-with-preload "mind" leg), periodic anti-entropy backstop. Loopback
   two-daemon tests: two-tier scoping (live everywhere, project same-project only),
   unsynced/hidden refused server-side, concurrent-write conflict → artifacts on both
   nodes → reconcile on active node → merged write converges everywhere.
4. **FAULT-MATRIX rows** (same change as the piece that lands them): stream loss
   mid-bundle (re-pull idempotent), partition concurrent writes (conflict path),
   reconcile turn failure (artifacts persist), registry ambiguity during election
   (re-conflict of merged outputs detected, not lost).

## Requirement activation (rule 5)

- **REQ-STORE-1, REQ-INST-2** → `[impl, unit]` at D6a.
- **New (rule 3): `REQ-HAZARD-CONFLICT-BOTH-PRESERVED`** — a surfaced concurrent pair
  is durably preserved (both versions) until a dominating write clears it; no
  reconcile path may discard an unmerged version. Register in `traceable-reqs.toml`
  at D6b start; `[impl, unit]` at D6b.
- **REQ-HAZARD-DIRECT-WRITE-PRECEDENCE** — stays `[impl, unit]`; D6b adds the
  multi-writer/node-id evidence.
- **REQ-NET-3, REQ-INST-5** → `[impl, unit]` at D6c. `int` = D9 two-host E2E.
- **REQ-HAZARD-PSYCHE-OUTBOUND-PROXY** — evidence tagged at D6b, **activates at D7.5**.

## NOT in D6

- Hub mode (gh opt-in) — deferred seam; interim skill retires at D9.
- D7.5b/c (event taxonomy + daemon relay/sanitize), D8 notif (D6 emits event seam only).
- memformat tier-tagged topics / block-level OCC (CONTEXT-MEMORY §Deferred) — the
  two-tier *files* land here; the schema evolution is post-v1.
- Two-host integration — D9 (loopback shapes only here).

## Conventions (carried)

- NO `cargo fmt`. Tag `[impl->REQ-…]` / `[unit->REQ-…]` on real evidence.
  `traceable-reqs check` from repo root before done. Linux CI clippy `-D warnings` is
  the real gate. Push each slice → watch green before next (verify FINAL via
  `gh run list --json conclusion`).
  Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.


Plan drafted. Verify traceability + commit:


Bash

traceable-reqs check && git status --short

Run traceability check + show changes


128 requirements: 128 complete, 0 incomplete, 0 findings
  [OK] REQ-API-1  required: [impl, unit, int]  stages: -doc +impl +unit +int
       api prefix and adapter_name on every machinery invocation
  [OK] REQ-API-2  required: [impl, unit, int]  stages: -doc +impl +unit +int
       The api subcommand surface (bind/listen/poll/state/worker/boundary/...)
  [OK] REQ-API-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       commune/signoff are file-drops, not commands
  [OK] REQ-ARCH-1  required: [impl]  stages: -doc +impl -unit -int
       Many small acyclically-layered crates
  [OK] REQ-ARCH-2  required: [impl]  stages: -doc +impl -unit -int
       Public SDK surface is spt-proto, spt-runtime, spt-msg
  [OK] REQ-ARCH-3  required: [impl, unit]  stages: -doc +impl +unit -int
       Wire-protocol version independent of crate semver, N-1 compat window
  [OK] REQ-ARCH-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Copy-verbatim the commodity layer from the sister project
  [OK] REQ-DAEMON-1  required: [impl, unit, int]  stages: -doc +impl +unit +int
       One per-machine spt-daemon owning all per-machine state
  [OK] REQ-DAEMON-2  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Broker/brain split for seamless self-update
  [OK] REQ-DAEMON-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Any api invocation auto-starts the daemon if absent
  [OK] REQ-DAEMON-4  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Honor every KNOWN-HAZARDS invariant
  [OK] REQ-DOCS-1  required: []  stages: -doc -impl -unit -int
       Dual-audience docs (human + AI dev-agent), markdown once / two depths
  [OK] REQ-DOCS-2  required: []  stages: -doc -impl -unit -int
       Sub-10-minute runnable killer quickstart per audience
  [OK] REQ-DOCS-3  required: []  stages: -doc -impl -unit -int
       Diátaxis structure; one canonical way to do X
  [OK] REQ-DOCS-4  required: []  stages: -doc -impl -unit -int
       Agent-consumable layer (llms.txt, manifest schema, MCP, CLI help)
  [OK] REQ-DOCS-5  required: []  stages: -doc -impl -unit -int
       Anti-drift: rustdoc/schema/exports/CLI-help generated + CI-checked
  [OK] REQ-EP-1  required: [impl, unit]  stages: -doc +impl +unit -int
       Day-one endpoint types; open type system
  [OK] REQ-EP-2  required: [impl, unit]  stages: -doc +impl +unit -int
       Agent endpoints vs Shells distinction in the type model
  [OK] REQ-EP-3  required: [impl, unit]  stages: -doc +impl +unit -int
       Messaging payloads carry typed operation commands + file blobs
  [OK] REQ-EP-4  required: [impl, unit]  stages: -doc +impl +unit -int
       PresenceChannel broker endpoint (seam day-one)
  [OK] REQ-EP-5  required: []  stages: -doc -impl -unit -int
       Concrete shell instantiation model: spawn-mints-instance (vs relink/online), registered-on-node permission + broadcast-is-discovery, per-shell require_approval gate, max_instances_per_owner + over_cap, instance aliasing, discovery scope
  [OK] REQ-FRONT-1  required: []  stages: -doc -impl -unit -int
       Day-one launcher/manager frontend (list/launch/attach/init)
  [OK] REQ-HAZARD-CASCADE-WIPE-GUARD  required: [impl, unit]  stages: -doc +impl +unit -int
       No hard-delete of a parent hosting non-empty children (6.3)
  [OK] REQ-HAZARD-CONPTY-DSR  required: [impl, unit]  stages: -doc +impl +unit +int
       ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)
  [OK] REQ-HAZARD-DAEMON-HOSTED-LIVENESS  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)
  [OK] REQ-HAZARD-DAEMON-SCHED-NONBLOCKING  required: []  stages: -doc -impl -unit -int
       Per-agent pulse/psyche/echo-commune scheduling must not serialize across agents: each agent's bounded LLM call (echo-commune summarizer, Psyche turn) runs off the shared scheduler so one slow/hung call cannot stall another agent's tick (7.4)
  [OK] REQ-HAZARD-DEFERRED-DRAIN  required: [impl, unit]  stages: -doc +impl +unit -int
       Deferred spool rows excluded from the event-stream drain (1.4)
  [OK] REQ-HAZARD-DEFERRED-SURVIVE-DRAIN  required: [impl, unit]  stages: -doc +impl +unit -int
       Deferred rows survive poll drain (4.4)
  [OK] REQ-HAZARD-DIRECT-WRITE-PRECEDENCE  required: [impl, unit]  stages: -doc +impl +unit -int
       Direct-write precedence marker (with node id) guards stale overwrite (6.5)
  [OK] REQ-HAZARD-DROP-FILE-SINGLE-WRITER  required: [impl, unit]  stages: -doc +impl +unit -int
       Drop files are daemon-owned single-writer (6.4)
  [OK] REQ-HAZARD-EBUSY-RENAME  required: [impl, unit]  stages: -doc +impl +unit -int
       tmp-write + atomic-rename + retry on Windows EBUSY (5.2)
  [OK] REQ-HAZARD-ECHO-BEFORE-SIGNOFF  required: [impl, unit]  stages: -doc +impl +unit -int
       Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)
  [OK] REQ-HAZARD-ENVELOPE-DECODE-ORDER  required: [impl, unit]  stages: -doc +impl +unit -int
       Envelope decode order, ampersand decoded last (4.1)
  [OK] REQ-HAZARD-ENVELOPE-PARSER-SAFE  required: [impl, unit]  stages: -doc +impl +unit -int
       Two-slice envelope parser is panic-free and tolerant (4.2)
  [OK] REQ-HAZARD-EPHEMERAL-CLEANUP  required: [impl, unit]  stages: -doc +impl +unit -int
       Ephemeral perch cleanup on every ring exit path (3.1)
  [OK] REQ-HAZARD-EVENTPART-REASSEMBLY  required: [impl, unit]  stages: -doc +impl +unit -int
       EVENT-PART split/reassembly is byte-exact; orphan parts dropped silently
  [OK] REQ-HAZARD-GEN-START-NOW  required: [impl, int]  stages: -doc +impl -unit +int
       gen_start = now() on cold-start and handoff (2.4)
  [OK] REQ-HAZARD-GRACE-BEFORE-SIGNOFF  required: [impl, unit]  stages: -doc +impl +unit -int
       Grace-period wait completes before composing INIT_SIGNOFF (1.1)
  [OK] REQ-HAZARD-HANDOFF-ARGV-COMPAT  required: [impl, unit]  stages: -doc +impl +unit -int
       Broker/brain IPC + handoff argv version-tolerant (2.3)
  [OK] REQ-HAZARD-ID-CHARSET  required: [impl, unit]  stages: +doc +impl +unit -int
       Addressable-id charset reserves :/@ delimiters; validated at every creation seam (4.6)
  [OK] REQ-HAZARD-INBOX-NO-DOUBLE  required: [impl, unit]  stages: -doc +impl +unit -int
       No double-delivery via legacy inbox (4.5)
  [OK] REQ-HAZARD-INFO-JSON-TORN-READ  required: [impl, unit]  stages: -doc +impl +unit -int
       State-file reads tolerate concurrent writes (1.2)
  [OK] REQ-HAZARD-LOCAL-API-AUTH  required: [impl, unit]  stages: -doc +impl +unit -int
       Every local `api` mutation authenticated to an endpoint/session (codex #13)
  [OK] REQ-HAZARD-PAIR-RATE-LIMIT  required: [impl, unit]  stages: -doc +impl +unit -int
       Subnet-global pairing rate limit: one active ceremony per subnet, shared attempt counter, exponential backoff — a public pre-trust relay + multiple seed-holders otherwise enables distributed SPAKE2 guessing (and ±1 TOTP window triples the valid-password space) (ADR-0005 #11)
  [OK] REQ-HAZARD-PAIR-SEED-ROTATION  required: [impl, unit]  stages: -doc +impl +unit -int
       Removing a node rotates the subnet seed (epoch bump) so an old node/old seed cannot rejoin; trust-store delete alone is NOT revocation because the seed is replicated to every trusted node (ADR-0005 #10)
  [OK] REQ-HAZARD-PAIR-TRANSCRIPT-BIND  required: [impl, unit]  stages: -doc +impl +unit -int
       Pairing transcript binds roles, both node pubkeys, subnet ID, seed epoch, TOTP time-step, and confirmation MACs — or unknown-key-share/reflection/wrong-subnet/replay pairing remain possible (ADR-0005 #12)
  [OK] REQ-HAZARD-PARENT-PID-PREFER  required: []  stages: -doc -impl -unit -int
       Prefer stable parent PID / broker handle over ephemeral PID (2.1)
  [OK] REQ-HAZARD-PSYCHE-OUTBOUND-PROXY  required: []  stages: -doc -impl -unit -int
       Psyche outbound captured + sanitized: the live-Psyche turn driver captures stdout (never Stdio::null), and the daemon strips/re-stamps Psyche-supplied from=/target and constrains routing (reply→__REPLY_TO__ sender, notify→own user/subnet) (7.3)
  [OK] REQ-HAZARD-REGISTRY-CONCURRENT  required: [impl, unit]  stages: +doc +impl +unit -int
       Concurrent SQLite openers (registry/spool) must not fail with 'database is locked' (4.7)
  [OK] REQ-HAZARD-REGISTRY-DIR-CREATE  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       SQLite store opens create their parent dir themselves — a fresh-home registry op must not SQLITE_CANTOPEN (4.9)
  [OK] REQ-HAZARD-REGISTRY-EPOCH-LEASE  required: [impl, unit]  stages: +doc +impl +unit -int
       Registry merge ordered by per-node monotonic epoch, never wall-clock — a stale Active can't clobber a newer Offline (4.8, red-team #8)
  [OK] REQ-HAZARD-REGISTRY-STALE-CLEAN  required: [impl, unit]  stages: -doc +impl +unit -int
       Stale registry entries degrade to fallback, never hard-fail (4.3)
  [OK] REQ-HAZARD-RESTART-IDEMPOTENT  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)
  [OK] REQ-HAZARD-SINGLE-PATH-SOURCE  required: [impl, unit]  stages: -doc +impl +unit -int
       Single path/registry source of truth; no layout ambiguity (6.1)
  [OK] REQ-HAZARD-SOFT-CLEANUP  required: [impl, unit]  stages: -doc +impl +unit -int
       Soft-cleanup preserves state, removes only the ready marker (6.2)
  [OK] REQ-HAZARD-STALE-INDEX-LOCK  required: []  stages: -doc -impl -unit -int
       Sweep stale lockfiles on daemon boot (1.3)
  [OK] REQ-HAZARD-STALE-SIGNOFF-SENTINEL  required: [impl, unit]  stages: -doc +impl +unit -int
       Stale signoff sentinel does not kill a fresh start (3.2)
  [OK] REQ-HAZARD-STDIN-SESSION-ID  required: []  stages: -doc -impl -unit -int
       Stdin session_id precedence over env (2.2)
  [OK] REQ-HAZARD-SUBPROCESS-TIMEOUT  required: [impl, unit]  stages: -doc +impl +unit -int
       Every harness/git subprocess has a timeout (5.3)
  [OK] REQ-HAZARD-UNC-PATH-STRIP  required: [impl, unit]  stages: -doc +impl +unit -int
       Strip Windows UNC prefix on serialized paths (5.4)
  [OK] REQ-HAZARD-UPDATE-ROLLBACK  required: [impl, unit]  stages: -doc +impl +unit -int
       Self-update rejects version rollback; metadata expiry + adapter content signing (codex #5)
  [OK] REQ-HAZARD-WAN-ORIGIN-AUTH  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       WAN-inbound origin is transport truth, never payload: the access gate's subject (ADR-0009 origin-node whitelist) is the QUIC handshake-proven remote node id from the broker's conn/stream table — a forged origin/node field inside record bytes is inert (7.5)
  [OK] REQ-HAZARD-WINDOWS-PID-RECYCLE  required: [impl, unit]  stages: -doc +impl +unit -int
       Windows PID-recycling false positives guarded (5.1)
  [OK] REQ-HAZARD-WORKER-PATH  required: [impl, unit]  stages: -doc +impl +unit -int
       Single source of truth for Worker/Psyche perch location (1.5)
  [OK] REQ-INFRA-1  required: []  stages: -doc -impl -unit -int
       GitHub issue tracking for v1; tangled.org as migration target
  [OK] REQ-INST-1  required: []  stages: -doc -impl -unit -int
       endpoint ID vs instance split (adapter-agnostic ID)
  [OK] REQ-INST-10  required: [impl, unit]  stages: -doc +impl +unit -int
       Qualified addressing [subnet:]id[@node] + ambiguity forces qualification
  [OK] REQ-INST-11  required: [impl, unit]  stages: -doc +impl +unit -int
       spt rename <id> rippled to all instances (collision-checked, 6.5-reconciled)
  [OK] REQ-INST-12  required: [impl, unit]  stages: -doc +impl +unit -int
       Endpoint visibility per-(endpoint,subnet): excluded semantics, OR-of-defaults + override, gates sync
  [OK] REQ-INST-13  required: [impl, unit]  stages: -doc +impl +unit -int
       Subnet-exclusive sync + per-endpoint subnet-membership list
  [OK] REQ-INST-14  required: []  stages: -doc -impl -unit -int
       Resource advertisement (subnet resource registry): free-text blurb, both-authored, registry projection, visibility/whitelist-gated
  [OK] REQ-INST-15  required: []  stages: -doc -impl -unit -int
       Immutable home subnet (assigned at creation: auto-if-one/ask-if-many) + spt fork (cross-subnet clone to a new identity, copy-then-diverge, not re-home); adapter chosen at creation from registered hostable adapters, changed only via launch/resume-under-new (ADR-0010)
  [OK] REQ-INST-2  required: []  stages: -doc -impl -unit -int
       Per-node files, synced Psyche mind
  [OK] REQ-INST-3  required: []  stages: -doc -impl -unit -int
       Dormant (warm) / suspended (cold) resting states
  [OK] REQ-INST-4  required: []  stages: -doc -impl -unit -int
       active to dormant/suspended fires a transition echo commune
  [OK] REQ-INST-5  required: []  stages: -doc -impl -unit -int
       Two-tier context sync (live to all, project to same-project)
  [OK] REQ-INST-6  required: []  stages: -doc -impl -unit -int
       Deferred messages not delivered to dormant/suspended instances
  [OK] REQ-INST-7  required: [impl, unit]  stages: -doc +impl +unit -int
       Subnet registry + bare-id resolution policy
  [OK] REQ-INST-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Remote-control mode distinct from local operation
  [OK] REQ-INST-9  required: [impl, unit]  stages: -doc +impl +unit -int
       Multi-subnet membership (same-user N subnets; cross-user seam)
  [OK] REQ-INSTALL-1  required: []  stages: -doc -impl -unit -int
       Two install paths; signed one-line script; OS-service registration
  [OK] REQ-INSTALL-2  required: []  stages: -doc -impl -unit -int
       Marketplace-repackaging-friendly install
  [OK] REQ-INSTALL-3  required: []  stages: -doc -impl -unit -int
       Idempotent + interactive-optional first run
  [OK] REQ-INSTALL-4  required: []  stages: -doc -impl -unit -int
       Adapter registration lifecycle: spt adapter add (--github, manifest-first, install-is-first-update) + soft-deregister remove + optional manifest uninstall template; node-local registered-adapter set self-update ripples over
  [OK] REQ-MANIFEST-1  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       Per-adapter manifest with adapter_name and min_spt_core_version
  [OK] REQ-MIGRATE-1  required: []  stages: -doc -impl -unit -int
       Auto-detect and migrate a legacy claude_skill_owl install
  [OK] REQ-MSG-1  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Local message delivery: TCP-first to a registered address, spool fallback when offline; id->address via registry (stale-clean first); reply routing (__REPLY_TO__)
  [OK] REQ-MSG-2  required: [impl, unit]  stages: -doc +impl +unit -int
       spt binary CLI surface: send/ring/ready/poll/list/stop/whoami, stable arg shapes + exit codes
  [OK] REQ-MSG-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Ready-agent lifecycle: register perch (info.json + listener + registry address) on ready, drain spooled backlog on startup, clean teardown
  [OK] REQ-NET-1  required: [impl, unit]  stages: -doc +impl +unit -int
       WAN messaging first-class, behind default-on net feature flag
  [OK] REQ-NET-2  required: [impl]  stages: -doc +impl +unit -int
       n0 relay default + self-host knob + plain-language disclosure
  [OK] REQ-NET-3  required: []  stages: -doc -impl -unit -int
       Cross-node Psyche sync over P2P replaces gh-repo-sync
  [OK] REQ-NODE-IDENTITY  required: [impl, unit]  stages: -doc +impl +unit -int
       Ed25519 identity primitive: keypair, detached sign/verify, stable pubkey<->hex
  [OK] REQ-NOTIF-1  required: []  stages: -doc -impl -unit -int
       Notification primitive: per-subnet replicated spool, seen/dismissed, resurface-at-boundary, subsumes update+consent prompts
  [OK] REQ-NOTIF-2  required: []  stages: -doc -impl -unit -int
       spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)
  [OK] REQ-PAIR-1  required: [impl, unit]  stages: -doc +impl +unit -int
       TOTP-seeded SPAKE2 pairing
  [OK] REQ-PAIR-2  required: [impl, unit]  stages: -doc +impl +unit -int
       Local trust store with TOFU + warn-on-change
  [OK] REQ-PAIR-3  required: [impl, unit]  stages: -doc +impl +unit -int
       Fetch current pairing code from any paired node
  [OK] REQ-PAIR-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Subnet naming on first pairing
  [OK] REQ-PAIR-5  required: [impl, unit]  stages: -doc +impl +unit -int
       Multi-subnet pairing: subnet-name discovery input, create-new-names-up-front, rendezvous-token hashing
  [OK] REQ-PAIR-6  required: [impl, unit]  stages: -doc +impl +unit -int
       Elevation-gated per-subnet code fetch (UAC/root or elevated agent; else authenticator app)
  [OK] REQ-PAIR-7  required: []  stages: -doc -impl -unit -int
       Subnet icon (inline image metadata, GUI-only consumer)
  [OK] REQ-REACH-1  required: [impl, unit]  stages: -doc +impl +unit -int
       Off-node remote-drive detection + file transfer
  [OK] REQ-REACH-2  required: []  stages: -doc -impl -unit -int
       Remote command execution (deferred, consent-gated)
  [OK] REQ-SEAM-ACTIVITY  required: [impl, unit]  stages: -doc +impl +unit -int
       Activity/idle reported via api sentinels, not PTY quiescence
  [OK] REQ-SEAM-CAPABILITY  required: [impl, unit]  stages: -doc +impl +unit -int
       Hostable endpoint-types capability declaration
  [OK] REQ-SEAM-HISTORY  required: [impl, unit, int]  stages: -doc +impl +unit +int
       History subsystem (fetcher / locate-normalize / native store)
  [OK] REQ-SEAM-INJECT  required: [impl, unit]  stages: -doc +impl +unit -int
       inject-input methods configurable per activity-state
  [OK] REQ-SEAM-POSTSPAWN  required: [impl, unit]  stages: -doc +impl +unit -int
       post-spawn / api bind seam with boot nonce
  [OK] REQ-SEAM-PSYCHE  required: [impl, unit, int]  stages: -doc +impl +unit +int
       spawn-psyche seam (fresh + resume templates)
  [OK] REQ-SEAM-RESUME  required: [impl, unit]  stages: -doc +impl +unit -int
       resume-session seam (fresh-with-preload / continue-existing)
  [OK] REQ-SEAM-SPAWN  required: [impl, unit]  stages: -doc +impl +unit -int
       spawn-session seam
  [OK] REQ-SEAM-UPDATE  required: [impl, unit]  stages: -doc +impl +unit -int
       Adapter-update avenue (file-pull / delegated command)
  [OK] REQ-SEC-1  required: [impl, unit]  stages: -doc +impl +unit -int
       Per-endpoint access whitelist: origin-node gate, stateful-firewall (reply/outbound exempt), node-now/user-later, outer gate before grants
  [OK] REQ-START-1  required: [impl, unit]  stages: -doc +impl +unit -int
       Adapters never resolve SPT_HOME; binary on PATH; api bridging only
  [OK] REQ-START-2  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Harness-hosted startup: api seed then listen
  [OK] REQ-START-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       spt-hosted startup: spawn-session then api bind (no file)
  [OK] REQ-START-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Adapter-injected env aliases (SPT/OWL/LIVE)
  [OK] REQ-STORE-1  required: []  stages: -doc -impl -unit -int
       spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)
  [OK] REQ-TERM-1  required: [impl, unit]  stages: -doc +impl +unit -int
       Process-supervisor terminal wrapper hosting broker PTYs
  [OK] REQ-TERM-2  required: [impl, unit]  stages: -doc +impl +unit -int
       session-surface abstraction; send-keys + send-line injection
  [OK] REQ-TERM-3  required: [impl, unit]  stages: -doc +impl +unit -int
       Byte-stream remote terminal streaming for v1
  [OK] REQ-TERM-4  required: [impl, unit, int]  stages: +doc +impl +unit +int
       Live activity buffer (PTY digest): adapter-supplied patterns over broker PTY, spt digest pull + delta-stream, opt-in Path-B log
  [OK] REQ-UPD-1  required: []  stages: -doc -impl -unit -int
       Peer-propagated update over P2P
  [OK] REQ-UPD-2  required: [impl, unit]  stages: -doc +impl +unit -int
       All binaries signature-verified before handoff
  [OK] REQ-UPD-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       No endpoint process terminates/suspends during self-update
  [OK] REQ-UPD-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Update gated on user confirmation by default; opt-in full-auto
  [OK] REQ-UPD-5  required: [impl, unit]  stages: -doc +impl +unit -int
       spt-core ripple-updates registered adapters
 M CONTEXT.md
 M M4-PLAN.md
 M docs/KNOWN-HAZARDS.md
 M docs/STORAGE.md
?? M4-D6-PLAN.md
?? docs/adr/0013-psyche-reconciled-context-merge.md


cd C:/Users/decid/Documents/projects/spt-core && git add CONTEXT.md M4-PLAN.md M4-D6-PLAN.md docs/KNOWN-HAZARDS.md docs/STORAGE.md docs/adr/0013-psyche-reconciled-context-merge.md && git commit -m "docs: M4-D6 design locked — ADR-0013 Psyche-reconciled context merge

D6 build-start decisions confirmed with user (the M4-PLAN D6b scheduled
touchpoint), grilled against CONTEXT.md/ADRs/KNOWN-HAZARDS:

- Concurrent context writes (version vectors, neither dominates) resolve by
  Psyche auto-reconcile: durable tracked conflict artifacts hold both versions,
  the active instance's node runs one bounded stdout-captured Psyche turn,
  merged write = join(vA,vB)+bump dominates both parents. Never silent
  newest-wins; never lose either version.
- D7.5a (bounded stdout-captured Psyche turn driver) pulled forward into D6b.
- Sync transport = git-native incremental bundles over broker QUIC streams,
  pull-based ref-scoped (synced(E,S) + project-hosting scope the refs);
  shared context DAG mandatory.
- BranchStore v1 = git-CLI-backed (REQ-STORE-1); hub mode stays deferred seam.

New: docs/adr/0013, M4-D6-PLAN.md (JIT plan). Amended: CONTEXT.md sync
glossary, STORAGE.md merge/transport prose, KNOWN-HAZARDS 6.5 mapping,
M4-PLAN D6b/D7.5a.

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

Commit + push D6 design docs