The capability + sensory vocabularies live **here**, not on the perch — spt-core resolves them by `adapter_name`. Command-delivery (`command_receipt`) reuses the agent inject-input modes. The shell binary binds via `api bind` (type=Shell, owner from the link) and pushes sensory via `api emit --type <t>`.

**Sleep/wake:** when offline (+ `wake_command` set), spt-core runs the wake-watcher (mutually exclusive with the shell binary); `exit(wake-opcode)` → wake resolution; crash-exit → respawn with exponential backoff + give-up. `persistent` auto-onlines the shell with its owner. `can_shutdown` authorizes `api owner-shutdown`. Cross-node *fresh-spawn* to wake an owner requires the owner's `shell_wake_spawn_anywhere` settings flag (pre-consent).

# Runtime manifest schema

> Draft design doc (2026-05-31). Resolves PRD §17.2. Implementation detail — see [`../CONTEXT.md`](../CONTEXT.md) for the model. Schema grounded in the sister project's actual functional surface.

## Principle

**SPT is not a harness.** The manifest declares only what *varies per harness*. Command templates are **opaque strings** — spt-core never parses out a model, tool list, or flag; the adapter writes the full command line and spt-core just runs it with substitution keys filled. Anything spt-core owns (sentinels, spool/registry/perch schemas, the event-block vocabulary, the version-trampoline mechanism, config knobs) is **not** in the manifest.

## What is NOT in the manifest (spt-core-owned)

- **Sentinels** (`.idle`-equiv, echo-gate) — managed by `api state`/`api echo-gate`; the adapter only *calls* them.
- **Spool / registry / perch `info.json` / daemon-state schemas** — core.
- **Event-block vocabulary** — the XML tags spt-core surfaces to agents (e.g. `<SPT_EVENT>`, `<owl_messages>`-equivalent) are a **fixed, documented constant**, not configurable. Adapters pass spt-core's output through unchanged.
- **File-drop filenames** — statically `<endpoint_id>-commune.md` / `<endpoint_id>-signoff.md`. Only the *watched directory* is declared (in `[session]`).
- **Config knobs** (pulse period, echo-commune window, route-guard window, daily-refresh) — spt-core **global settings**, with optional **per-endpoint override** for endpoints with unique needs. Not per-adapter.
- **Cross-adapter fallback** (psyche/echo invocation falls back to another adapter on rate-limit) — a **node-wide setting**, not a manifest field, because it references *multiple* adapters (under spt-core, `ccs` is its own adapter, not a binary-swap within one). Adapter-agnostic IDs make this safe — an endpoint's Psyche can run under a fallback adapter temporarily.
- **Plugin-cache / version-trampoline layout** — none of SPT's concern. A CC-plugin adapter updates via `claude plugin update` (delegated command).

## Manifest sections

### `[adapter]` — header
```toml
[adapter]
name = "claude-spt"
version = "2.0.0"
min_spt_core_version = "1.0.0"     # readable before any update; compat gate
hostable_types = ["LiveAgent", "ReadyAgent", "Psyche", "Worker"]
```

### `[hooks.<event>]` — inbound hook table
Per harness event: the `api` command it fires, the stdin fields it maps in, and its **output capability** — `can_inject` (whether this hook can surface context to the agent). When `can_inject = false`, spt-core falls back to a sentinel + relay/poll path instead of expecting injection. This is the load-bearing harness-varying fact (CC's Stop hook can't inject).

```toml
[hooks.SessionStart]
fires = "api seed --pid {parent_pid} --session-id {session_id} --adapter {adapter_name}"
reads = ["session_id", "source", "agent_type"]
can_inject = true                  # via additionalContext

[hooks.Stop]
fires = "api state idle"
reads = []
can_inject = false                 # no hookSpecificOutput schema → sentinel/relay fallback
```

### `[session.<role>]` — outbound session role templates
One opaque command template per role: `self`, `psyche_init`, `psyche_resume`, `echo_commune`, `signoff`. Plus `cwd`, env inject/remove, detachment, and the recursion-guard env var. **Model, tools, flags, dir-grants all live inside `command`** — not as fields.

```toml
[session.psyche_init]
command = "claude -p --agents {agents_json} --agent owl-psyche --name {session_name} --model sonnet --fallback-model opus --effort medium --dangerously-skip-permissions --disable-slash-commands --tools Read,Edit,Write --output-format json"
cwd = "{psyche_dir}"
env_remove = ["OWL_SESSION_ID"]
recursion_guard_env = "SPT_ECHO_COMMUNE"   # set on summarizer children so their own hooks bail
detach = true
keys = ["agents_json", "session_name", "psyche_dir"]   # substitution keys spt-core fills
```
(`psyche_resume` adds `{session_id}`; both carry the spt-core-owned `{psyche_prompt}`. `signoff` is a `psyche_resume` variant firing a final context-save. `echo_commune` is a cheap-model template consuming history.)

### `[env]` — env-var table
Vars to inject/read, and **how**. With `spt` on PATH the table is small. The injection *channel* is asymmetric: **spt-hosted** sessions inherit env from the broker that spawns them (no channel needed); **harness-hosted** sessions need the harness's declared channel (the broker didn't spawn them). File-bridge-only-when-not-launcher, applied to env.

```toml
[env.OWL_SESSION_ID]
direction = "inject"
value = "{session_id}"
channel = "CLAUDE_ENV_FILE"        # harness-hosted only; spt-hosted inherits from broker
```

### `[history]` — transcript access
Three strategies; pick one. Pull-based (spt-core asks) or push-based (adapter sends).
```toml
[history]
strategy = "fetcher"               # fetcher | locate_normalize | native
fetcher = "claude-spt-history --session {session_id}"   # adapter binary emits normalized history
# locate_normalize: locate_template + normalize_command
# native: adapter pushes via `api history-log`; spt-core stores (also used for Shells)
```

### `[inject]` — inject-input methods
Per activity-state, any combination: `pty`, `hook`, `relay`, `http`.
```toml
[inject]
activity = ["hook"]                # non-disruptive during work
idle     = ["pty", "hook"]
```

### `[session]` watched dirs (file-drop)
```toml
[session]
commune_dir = ".claude"            # watched for <id>-commune.md (filename fixed by spt-core)
signoff_dir = ".claude"            # watched for <id>-signoff.md
```

### `[identity]`
```toml
[identity]
session_id_source = "post_spawn"   # post_spawn | uuid_inject
parent_ancestor_name = "claude"    # process-tree anchor when session_id absent
```

### `[update]` — adapter self-update
```toml
[update]
avenue = "delegated"               # delegated | file_pull
command = "claude plugin update spt"          # delegated
# file_pull: repo + path_regex
version_check = true               # verify spt-core satisfies min_spt_core_version before/after
```

---

## Shell adapters (`kind = "shell"`)

A second kind of adapter. Provides a `Shell` endpoint (a driven surface) rather than hosting agents. Same file location (`adapters/`), different manifest body.

```toml
[adapter]
name = "GameRobot"
kind = "shell"                     # vs "harness"
version = "1.0.0"
min_spt_core_version = "1.0.0"

[shell]
spawn = "gamerobot-shell --link {link_token}"   # broker-launched; opaque command template
ephemeral = false                  # manifest property, NOT an agent choice; ephemeral ⇒ no offline perch + no history retention
broadcast = "subnet"               # subnet | same-node | none
command_receipt = "http"           # http | stdin | relay   (how it receives agent commands)
pre_close = "park-and-save"        # optional instruction sent to the binary on link-break
close_timeout_ms = 3000            # graceful termination window before force-close
persistent = true                  # auto-online whenever the owner endpoint is online
wake_command = "gamerobot-waker --link {link_token}"  # long-running wake-watcher run WHILE offline; exit(wake-opcode) ⇒ revive
can_shutdown = false               # if true, the shell may fire `api owner-shutdown` to suspend its owner directly

[shell.capabilities]               # the command vocabulary (agent→shell) — the toolset
move      = { args = ["direction", "distance"] }
gesture   = { args = ["name"] }
alert     = { args = ["symbol"] }
screenshot = {}

[shell.sensory]                    # the sensory vocabulary (shell→agent, REST-only)
types = ["image", "sound", "event"]   # "event" = arbitrary descriptive payloads
```

The capability + sensory vocabularies live **here**, not on the perch — spt-core resolves them by `adapter_name`. Command-delivery (`command_receipt`) reuses the agent inject-input modes. The shell binary binds via `api bind` (type=Shell, owner from the link) and pushes sensory via `api emit --type <t>`.

## Worked example: `claude-spt` (today's behavior, expressed)

Maps the sister project's actual surface onto the schema.

| Concern | Today (modern SPT) | Manifest expression |
|---|---|---|
| 7 CC hooks | hooks.json → `owl.exe <subcmd>` | `[hooks.*]` table; `Stop.can_inject=false` drives the echo-gate fallback |
| Psyche init | `claude -p --agents … --model sonnet --fallback-model opus …` | `[session.psyche_init].command` (opaque) |
| Psyche resume | `claude -p --resume <uuid> --model sonnet …` | `[session.psyche_resume]` + `{session_id}` |
| Echo-commune | `claude -p --model haiku --effort low …`, `OWL_ECHO_COMMUNE=1` guard | `[session.echo_commune]` + `recursion_guard_env` |
| `ccs` fallback | tier-2 binary swap | **node-wide cross-adapter fallback** (not manifest; `ccs` is its own adapter) |
| Transcript read | `.claude/projects/<enc-cwd>/<uuid>.jsonl` | `[history]` (fetcher or locate_normalize) |
| Commune/signoff | `.claude/<id>-commune.md` watched | `[session].commune_dir/signoff_dir`; filenames fixed |
| Env | `$OWL`/`$LIVE`/`OWL_SESSION_ID` via `CLAUDE_ENV_FILE` | `[env]`; PATH removes most; channel only harness-hosted |
| Identity | `OWL_SESSION_ID` + `claude.exe` ancestor walk | `[identity]` |
| Update | (today: cplugs) | `[update] avenue="delegated" command="claude plugin update"` |
| period / echo-window / route-guard / 24h refresh | hardcoded/flags | spt-core **global knobs** (per-endpoint override), not manifest |

## Open sub-items
- Exact substitution-key catalog (which keys spt-core guarantees to fill per role).
- TOML vs YAML final choice; schema-validation + helpful errors for adapter authors.
- The fixed event-block vocabulary's final tag names (`<SPT_EVENT>` etc.).
- Capability-declaration shape beyond `hostable_types` (per-type operation advertisement).

+**Sleep/wake:** when offline (+ `wake_command` set), spt-core runs the wake-watcher (mutually exclusive with the shell binary); `exit(wake-opcode)` → wake resolution; crash-exit → respawn with exponential backoff + give-up. `persistent` auto-onlines the shell with its owner. `can_shutdown` authorizes `api owner-shutdown`. Cross-node *fresh-spawn* to wake an owner requires the owner's `shell_wake_spawn_anywhere` settings flag (pre-consent).


Edit

| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design | After remote-drive ships and consent model is designed |

| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design. (The shell-wake `shell_wake_spawn_anywhere` endpoint flag is a *pre-consent* variant of this same deferred mechanism — flag set ⇒ shells may fresh-spawn the owner on any node to wake it.) | After remote-drive ships and consent model is designed |


1. agreed. the process has exactly one goal: fire a wake. name makes sense. 2. agree. 3. agree. 4. agree. watcher respawn should have exponential backoff (and eventual give-up until next shell activit…


Design spt-core binary and messaging library architecture


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

# Deferred Features

Intended features explicitly cut from v1 scope but committed for later. Not backlog noise — these are decided directions parked for sequencing reasons. Keep this list short and real.

| Feature | Cut from | Why deferred | Trigger to revisit |
|---|---|---|---|
| Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any "scroll back further than the buffer" user need |
| Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hooks (needs streaming or stateful coordination) |
| Manifest `include` / composition (option C) | manifest v1 | Single flat manifest per harness is enough until a harness wants to override only part of a section | First sub-project that needs to inherit spt-core defaults but override one section |
| Concrete Shell types (GameRobot, OS-notification, in-session-inject) | Shell concept v1 | Model is locked; concrete surfaces are large, platform-specific, and not needed for core | First real use case for a driven surface (e.g. the 2D-world experiment) |
| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1 | Once Shells exist and the subnet registry is stable |
| Presence gossip (`last_active_node/endpoint/timestamp` across subnet) | networking v1 | Needs the multi-instance registry first; agent-interaction heartbeat (7a-B) is the chosen signal | When PresenceChannel work begins |
| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |
| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design | After remote-drive ships and consent model is designed |
| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly) | Designed alongside instantiate-anywhere's consent gate |
| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |
| Concrete Shell adapters (e.g. GameRobot) + the shell binaries themselves | Shell concept v1 | The Shell *model*, manifest schema, perch layout, and `api` surface are now specified; concrete shells are large, platform-specific, and not needed for core | First real driven-surface use case |

-| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design | After remote-drive ships and consent model is designed |

+| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works; consent UX is its own design. (The shell-wake `shell_wake_spawn_anywhere` endpoint flag is a *pre-consent* variant of this same deferred mechanism — flag set ⇒ shells may fresh-spawn the owner on any node to wake it.) | After remote-drive ships and consent model is designed |


Shell sleep/wake fully captured.

**Landed:**
- **Wake:** `persistent` (online-with-owner) + `wake_command` (wake-watcher while offline, runs on shell's node, single goal = fire a wake). Online/offline = mutually-exclusive processes. Exit-opcode → wake; crash → respawn w/ exponential backoff + give-up.
- **Wake resolution (state-keyed):** dormant owner → just online shell; suspended → revive owner; active elsewhere → attach; no reachable instance → no-op *unless* `shell_wake_spawn_anywhere` endpoint flag (pre-consent for cross-node fresh-spawn).
- **Shutdown:** `spt shutdown` (agent self-shutdown → suspended, cascades shells offline) + **`api owner-shutdown`** (shell directly suspends owner, gated by `can_shutdown`). The from-shell shutdown channel = that machinery `api` command (not the content channels).
- Manifest fields, DEFERRED note (spawn-anywhere flag = pre-consent variant of deferred instantiate-anywhere).

**Repo note:** the published commit (`9515e2e`) predates all the Shell deep-dive + wake/shutdown work — those edits (CONTEXT, MANIFEST, STORAGE, DEFERRED, PRD) are **uncommitted** in the working tree. Want me to commit + push them now, or keep going and batch a commit later?

Then the remaining open items: **17.3** (Psyche live/project authoring directives), **17.4** (dormancy default + suspend surface), **17.5** (consent/security-gate UX). Which direction?


commit and push now


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


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


Bash

cd "C:/Users/decid/Documents/projects/spt-core" && git add -A && git status --short

Stage changes, show status


warning: in the working copy of 'CONTEXT.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs/DEFERRED.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs/MANIFEST.md', LF will be replaced by CRLF the next time Git touches it
M  CONTEXT.md
M  docs/DEFERRED.md
M  docs/MANIFEST.md


Three files changed since the initial commit (STORAGE/PRD were already in `9515e2e`). Committing + pushing.


cd "C:/Users/decid/Documents/projects/spt-core" && git commit -q -F - <<'EOF'
docs: shell model, sleep/wake, and from-shell owner-shutdown

- Shell adapters as a second adapter kind (kind="shell"); three channels
  (command, text+file, sensory); owner-linked exclusive; nested non-registry
  perch; online/offline/torn-down lifecycle; local-link auth; binary-trust
  disclosure
- Sleep/wake: persistent + wake_command (wake-watcher), online/offline as
  mutually-exclusive processes, exit-opcode supervision with backoff,
  state-keyed wake resolution, shell_wake_spawn_anywhere pre-consent flag
- Shutdown: agent-facing `spt shutdown`; shell `api owner-shutdown` gated by
  can_shutdown
- MANIFEST: kind="shell" fields incl persistent/wake_command/can_shutdown
- DEFERRED: shell/harness binary sandboxing; spawn-anywhere as pre-consent
  variant of instantiate-anywhere

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
git push -q origin main && echo "pushed" && git log --oneline -2

Commit and push shell changes


pushed
1d30876 docs: shell model, sleep/wake, and from-shell owner-shutdown
9515e2e docs: initial spt-core planning artifacts


Pushed → `1d30876` on `origin/main`. https://github.com/SaberMage/spt-core now reflects the full Shell model + sleep/wake/shutdown work.

Ready for the next open item — **17.3** (Psyche live/project authoring directives — the live problem that makes two-tier sync actually hold), **17.4** (dormancy default policy + suspend command surface), or **17.5** (consent/security-gate UX, now also touched by the shell `can_shutdown` / spawn-anywhere flags). Which way?


first: need to cover how node context across two machines cleanly combines when both have the same agents/projects folders


Good — this is the cross-node merge problem for the `tracked/` repo. The clean answer rests on one invariant + a custom merge driver.

**The invariant that makes it clean: one logical writer per context file.**
Every context file belongs to exactly one agent, written only by that agent's **active** instance (the active-instance-authoritative rule). So two nodes don't *legitimately* write the same file at the same time — sync is mostly **propagation**, not reconciliation. Disjoint files (node A has `agents/ling`, node B has `agents/momo`) simply **union**.

**The mechanism: precedence-marker merge driver, never raw git merge.**
A raw 3-way git merge of LLM-authored markdown would inject `<<<<<<<` conflict markers into an agent's mind — unacceptable. Instead:
- `tracked/` ships a `.gitattributes` declaring context files use a **custom spt merge driver**.
- The driver resolves by the **precedence marker** already on every write (KNOWN-HAZARDS 6.5: `<!-- spt:source=direct|llm spt:routed_at=<ts> spt:node=<id> -->` — extended with **node-id**). **Newest marker wins**, per file. No conflict markers, ever.
- Cross-node sync = pull the peer's `a-<id>` / `p-<project_id>` branches → merge with the driver → deterministic newest-wins per file + union of disjoint files.

So "combine" = **union of files + per-file newest-precedence-wins**. The result is coherent: `projects/<pid>/` ends up holding *every* agent's latest project-context (the cross-agent-synthesis + query-routing view), `agents/<id>/` holds each agent's latest live-context.

**The one genuine-conflict case** — the same agent active on the *same project* on two nodes simultaneously (the rare divergent-simultaneous work). The driver still resolves deterministically (newest wins), and the **transition echo-commune** (fired when an instance goes dormant/suspended) captures the outgoing instance's final delta *before* the other takes over — so the loser's content was already folded forward, minimizing actual loss. Two-tier scoping (live→all, project→same-project) keeps this to genuinely-overlapping work only.

**One design pick to confirm:** **shared branches** (`a-ling` on every node) resolved by the merge driver — my recommendation — **vs.** per-node branch namespaces (`a-ling@desktop`, `a-ling@laptop`) with a computed "current." Shared-branch + driver is far simpler and the single-writer invariant makes it safe; per-node namespaces add bookkeeping for no real gain. Agree on shared-branch + merge-driver?

Capturing into STORAGE.md (cross-node merge section) + the node-id addition to the precedence marker once you confirm.


-what happens if the same branch has a different commit history during the node-link sync? -can the tracked branch worktrees i.e. `a-ling`, `p-spt-core` set their origin to be relative filepath, so it doesn't break across operating systems and nodes where the user account is different? -agreed on shared-branch + merge-driver


Both are real git-plumbing questions. Answers:

**Q1 — divergent (or unrelated) commit histories on the same branch.**

Expected and fine — because **content resolution is driven by the precedence marker, not by history.** Two flavors:
- **Common ancestor, both advanced** (normal divergence) → 3-way merge; the custom driver resolves any same-file overlap by newest-marker. Reconciling merge commit, clean.
- **No common ancestor** (the *realistic* case — each node ran its own `git init`/seed on first run, so `a-ling` on A and on B share no root) → git refuses by default (`refusing to merge unrelated histories`). So spt-core's sync uses **`--allow-unrelated-histories`** + the driver. The driver still resolves content per-file by marker; union for disjoint files.

So sync = fetch peer branch into a remote-tracking ref → merge with `--allow-unrelated-histories` + spt merge driver. The working tree is always the newest-precedence content regardless of how the histories relate. History becomes a multi-parent DAG (one merge commit per sync) — purely cosmetic (the marker is the source of truth, not topology); periodic `git gc`/squash keeps it tidy. No corruption, never a conflict marker.

Key point: **git here is versioning + the branch-worktree views; the precedence marker is the actual merge authority.** History divergence is just plumbing the driver papers over.

**Q2 — relative worktree paths for cross-OS / different-user portability.**

Yes — and this is the right instinct. The `a-<id>`/`p-<pid>` branch views are **linked git worktrees** off the bare seed repo, and linked worktrees store **absolute** back-links by default (`/home/<user>/…`, `C:\Users\<user>\…`) → break on relocation, a different user account, or migration.

Fix: **relative worktree paths** — `git config worktree.useRelativePaths true` (+ `git worktree add --relative-paths`), available **Git ≥ 2.48**. Stores the worktree↔gitdir links relative, so the whole `tracked/` tree is relocatable. **Fallback for older git:** spt-core runs `git worktree repair` after any relocation to re-point the absolute links locally (spt-core detects git version and picks).

Important scope note: the **cross-node sync transfers branch *objects* (fetch/merge), not the worktree plumbing** — so sync itself is already path-agnostic. The absolute-path problem only bites on (a) `$SPT_HOME` relocation, (b) backup/restore to a different path, (c) legacy-migration import. Relative worktrees (or `repair`) cover exactly those.

(Note: there's no `origin` *remote* — P2P sync replaced it. "Relative" here means the worktree↔seed links, which is what actually needs to be portable.)

Confirm those, and I'll write the cross-node-merge + worktree-portability mechanics into STORAGE.md (and add **`spt:node=<id>`** to the precedence marker).


-what happens if the same branch has a different commit history during the node-link sync? -can the tracked branch worktrees i.e. `a-ling`, `p-spt-core` set their origin to be relative filepath, so it…


confirm all. also: i've since seen additional value for having a context repo origin *remote*-->the GitHub GUI is a convenient way to view it. so i'd like that to remain an opt-in feature for spt-core. should be fully binary-driven rather than agent-driven, i.e. `spt context-github-setup`


Confirmed all. And the opt-in GitHub mirror is a good call — it's cleanly *additive* and doesn't contradict ADR-0002, because the distinction is **sync vs view**:

- **P2P remains the sole sync authority** (ADR-0002 stands — gh is not required for core, no account dependency for cross-node).
- **`spt context-github-setup`** adds an **opt-in, one-way, view-only mirror**: spt-core *pushes* the merged context to a private GitHub repo for convenient GUI browsing. It **never pulls** from GitHub (that would create a second sync authority and re-introduce the dependency). Binary-driven, not agent-driven — agents don't manage it.

Design specifics I'd lock:
- **Designated mirror node:** the node where you run `context-github-setup` becomes the pusher. Other nodes sync to it over P2P (so it holds everyone's latest union), and it pushes upward. Single pusher avoids divergent-history push races on the remote. (Multi-node-push is possible only with force-with-lease semantics — not worth it for a view mirror; recommend single mirror node.)
- **Private repo** (`gh repo create --private`) — it's the user's agent minds.
- Pushes the merged worktree state on context updates / a cadence.

So the model: **P2P = the nervous system; GitHub = an optional window into it.**

Confirm the single-mirror-node choice, and I'll write the full cross-node mechanics into STORAGE.md — single-writer invariant, `.gitattributes` + spt merge driver, `--allow-unrelated-histories`, relative worktrees + `git worktree repair` fallback, the `spt:node=<id>` precedence-marker addition, and the opt-in view-only GitHub mirror — plus a one-line clarifier in CONTEXT that the mirror is view-only and ADR-0002 (P2P-is-sync) still holds.


designated mirror node can be problematic for users who don't leave at least one PC running at all times of the day. they would be looking at stale context on github until the mirror node comes back online. so i think we need 2 sync archs depending on whether gh remote is set up. P2P if it isn't, everything on the subnet pulls-then-pushes if it is.


Agreed — and your reasoning kills my mirror-node idea cleanly: GitHub is *always* online, the user's PCs aren't. So two sync modes for the context repo:

- **P2P mode (default, gh off):** peer-to-peer branch sync over Iroh; no central point; the "no account" core promise.
- **Hub mode (opt-in, gh on):** every node **pull→merge→push** against the GitHub remote. GitHub is the always-online sync hub *and* the live view — never stale, no designated node. Mechanics: pull → merge (spt driver + `--allow-unrelated-histories`, conflict-free via precedence marker) → push, **retry-on-reject** for concurrent-push contention. Standard distributed-git, made conflict-free by the marker driver.

**One scope clarification I want to lock:** hub mode is the **context-repo sync transport only.** Everything else — messaging, the subnet registry, remote-drive, pairing, presence — still rides P2P/Iroh regardless. GitHub can't carry real-time agent traffic. So **gh-on does *not* remove the P2P requirement**; it only swaps how `tracked/` syncs. The P2P stack is always present. Agreed?

**This amends ADR-0002** — gh goes from "retired as sync" to "**opt-in alternative for context-repo sync**." The default stays P2P + no-account; opting into gh is the user's choice to use a hub. I'll record that as an amendment.

Two sub-questions before I write it up:

1. **P2P fallback in hub mode?** Since the P2P stack is always running anyway, should a node that *can't reach GitHub* (but *can* reach peers) fall back to P2P context-sync — i.e. hub mode = "prefer GitHub, P2P as backstop"? Or strict XOR (gh-on ⇒ GitHub-only, a node with no GitHub access just waits)? I lean **prefer-GitHub-with-P2P-backstop** (resilience for free, since the stack's there). Your call.

2. **Per-node auth.** Pushing/pulling a *private* GitHub repo needs git credentials on **every** node (gh auth or a shared deploy key). `context-github-setup` distributes the remote URL over the subnet, but each node still needs its own auth. Accept that per-node setup cost (the user opted into gh), or have `context-github-setup` provision a shared deploy key it distributes over the subnet to automate it?

Answer those two and I'll write the full cross-node mechanics (both modes) into STORAGE.md, the ADR-0002 amendment, and the CONTEXT update.


designated mirror node can be problematic for users who don't leave at least one PC running at all times of the day. they would be looking at stale context on github until the mirror node comes back o…


agreed on the scope clarification. P2P always present. -sub-qs: 1. agree on prefer-GH, fallback-P2P. 2. how would the shared deploy key work? sounds ideal. otherwise i think the user should be forced to choose whenever they link a new node: link won't succeed until A) `gh` cli confirmed authenticated or B) user chooses to reset all nodes to P2P sync.


Deploy key is exactly right — and it **obviates the forced-choice gate** entirely. How it works:

**GitHub deploy keys** grant access to **one repo** (not the account). So:
- `spt context-github-setup` runs on one **gh-authed** node → creates the private repo, generates a **read-write deploy key**, registers its public half on the repo (via `gh` API), and stores the **private half as subnet secret material**.
- The private key is **distributed over the subnet like the TOTP seed** — it travels with pairing / the P2P trust mesh. Every other node uses the deploy key over **SSH; no GitHub account needed on those nodes.** Only the *setup* node ever needs gh auth.
- **New-node linking never blocks:** a freshly-paired node auto-receives the deploy key (if hub mode is on) and has the **P2P backstop** if GitHub/SSH is unreachable. So your option (B) "force gh-auth or reset to P2P" isn't needed — the key + backstop make it just-work.
- **Revocation:** de-trust a node or disable hub mode → rotate the key (remove from repo settings, regenerate, redistribute). Same trust boundary as the context itself.

So both sub-qs resolved: **(1) prefer-GitHub, P2P-backstop; (2) shared deploy key, distributed as subnet secret, no per-node account.** Writing the full mechanics in now.


Edit

## Cross-node context sync

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

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

**Two transport modes:**
- **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.
- **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.

**Hub auth — shared deploy key.** The gh-authed setup node creates the private repo, generates a **read-write deploy key**, registers its public half on the repo, and distributes the **private half as subnet secret material** (via pairing / P2P, like the TOTP seed). Other nodes need **no GitHub account** — they use the deploy key over SSH. New-node linking never blocks (auto key + P2P backstop). Revocation = rotate the key (remove from repo, regenerate, redistribute) when a node is de-trusted or hub mode is disabled.

## Worktree portability

The `a-<id>` / `p-<project_id>` branch views are **linked git worktrees** off the bare seed repo. Linked worktrees store **absolute** back-links by default (`/home/<user>/…`, `C:\Users\<user>\…`) → break on relocation, a different user account, or migration. Use **relative worktree paths** (`git config worktree.useRelativePaths true`, Git ≥ 2.48) so `tracked/` is relocatable. **Fallback** for older git: spt-core runs `git worktree repair` after any relocation to re-point links locally (spt-core detects git version and picks). Cross-node *sync* transfers branch **objects**, not worktree plumbing, so sync is already path-agnostic — relative paths matter for `$SPT_HOME` relocation, backup/restore, and legacy migration.

## project_id


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)

## Cross-node context sync

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

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

**Two transport modes:**
- **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.
- **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.

**Hub auth — shared deploy key.** The gh-authed setup node creates the private repo, generates a **read-write deploy key**, registers its public half on the repo, and distributes the **private half as subnet secret material** (via pairing / P2P, like the TOTP seed). Other nodes need **no GitHub account** — they use the deploy key over SSH. New-node linking never blocks (auto key + P2P backstop). Revocation = rotate the key (remove from repo, regenerate, redistribute) when a node is de-trusted or hub mode is disabled.

## Worktree portability

The `a-<id>` / `p-<project_id>` branch views are **linked git worktrees** off the bare seed repo. Linked worktrees store **absolute** back-links by default (`/home/<user>/…`, `C:\Users\<user>\…`) → break on relocation, a different user account, or migration. Use **relative worktree paths** (`git config worktree.useRelativePaths true`, Git ≥ 2.48) so `tracked/` is relocatable. **Fallback** for older git: spt-core runs `git worktree repair` after any relocation to re-point links locally (spt-core detects git version and picks). Cross-node *sync* transfers branch **objects**, not worktree plumbing, so sync is already path-agnostic — relative paths matter for `$SPT_HOME` relocation, backup/restore, and legacy migration.

## project_id

# $SPT_HOME on-disk layout

> Draft design doc (2026-05-30). Implementation detail — neither glossary ([`../CONTEXT.md`](../CONTEXT.md)) nor decision ([`adr/`](./adr/)). Resolves PRD §17.1.

## Principles

1. **Single relocatable root.** One `$SPT_HOME` dir holds everything (default `%LOCALAPPDATA%\spt` / `~/.spt`; override via `$SPT_HOME`). One dir to back up, move, or repackage for a marketplace/handheld. No XDG three-way split.
2. **Endpoint IDs are adapter-agnostic.** `ling` can run under `claude-spt`, then later under `spt-codex` or `spt-pi`. Perches and context key on bare `endpoint_id`; the currently-running `adapter_name` is a *property of the instance* recorded in `info.json`, never a path segment. **At most one instance of an ID per node** — a second attempt (any adapter) is rejected.
3. **Two separate trees: `tracked/` vs `perches/`.** The synced *mind* (context, a git repo) and the node-local *plumbing* (spool/status/info) are physically separate. Perch runtime is **never** git-tracked — otherwise node-local spools and statuses would clobber across nodes when the context repo syncs.
4. **Durable vs ephemeral is explicit.** `identity/`, `tracked/`, `history/` are durable (the backup/migrate set). `registry/`, `daemon/`, `updates/` are regenerable. Migration carries the durable set only.
5. **One path resolver.** A single centralized, idempotent resolver maps `(endpoint_id[, child][, project_id][, kind])` → path. Nothing constructs paths ad-hoc, so any future relayout carries to all dependents (KNOWN-HAZARDS 6.1 — no flat/nested ambiguity).

## Layout

```
$SPT_HOME/
  identity/                      ── node-local, durable, NEVER git-tracked ──
    node.key                     Ed25519 node identity (per-node; must not sync)
    trust/                       authorized peer pubkeys (trust store)
    subnet.json                  subnet name + TOTP seed (replicated via pairing, not git)
  tracked/                       ── git repo; synced across nodes over P2P ──
    agents/<endpoint_id>/
      meta.json                  endpoint identity metadata: type, ordered adapter history, created
      live-context.md            per-agent live context           (branch a-<endpoint_id>)
    projects/<project_id>/
      <endpoint_id>/
        project-context.md       per-(agent,project) context      (branch p-<project_id>)
  perches/                       ── node-local runtime, NEVER tracked/synced ──
    <endpoint_id>/
      info.json                  identity, type, status, CURRENT adapter_name (+ capability set if Shell)
      spool.db                   SQLite spool (typed + binary payloads)
      status                     active | dormant | suspended    (agent endpoints)
      nested/<child_id>/         Worker / Psyche perches (own info.json, spool)
      shells/<adapter>-<n>/      Shell perches owned by this endpoint (GameRobot-0, GameRobot-1…)
        info.json                type=Shell, owner, adapter_name, status (online|offline)
        spool.db                 command + durable text/file (sensory is REST-only, unspooled)
  history/                       node-local logs (mirrors perch structure)
    <endpoint_id>/
      sessions/<session_id>.jsonl    agent Path-B native logs
      shells/<adapter>-<n>.jsonl     shell-link command/text/sensory exchange log
  registry/                      ── ephemeral ── local cache of the subnet registry
  daemon/                        ── ephemeral ── brain rehydration state, broker handle metadata
  updates/                       ── ephemeral ── staged binaries pending handoff
  adapters/                      registered adapter manifests / pointers
```

## tracked/ — the context git repo

- A local **git repository** (carried forward from modern SPT's mechanism). Two branch views: **`a-<endpoint_id>`** (per-agent) and **`p-<project_id>`** (per-project). The per-project tree lets any agent synthesize a broader context from *other* agents' contexts in the same project, and supports query-routing ("which endpoint is most responsible for this task").
- **Two-tier sync maps onto the structure:** `agents/<id>/live-context.md` (the `a-` view) syncs to **all** instances of the endpoint; `projects/<project_id>/<id>/project-context.md` (the `p-` view) syncs only to instances sharing that project.
- **P2P replaces the *remote*, not the local git.** Modern SPT pushed this repo to a private GitHub remote; spt-core keeps the local git tracking + branch views and moves the data over the built-in P2P layer instead (no `gh`/account/setup). The precedence/freshness guard (KNOWN-HAZARDS 6.5) — with node identity in the marker — arbitrates concurrent writes from multiple nodes/instances.

## project_id

Derived **from the git remote URL** of the project if it has one (stable + path-independent, so the *same* project at different absolute paths on different machines — `/home/x/proj` vs `C:\Users\x\proj` — resolves to the *same* `project_id`, which is what makes project-context sync match across instances). If the project has no git repo, fall back to the project **folder name**. (Folder-name fallback is not guaranteed path-independent across machines — a known limitation for non-git projects.)

## perches/ — node-local runtime

- Holds the clobber-sensitive, node-specific state: `info.json` (identity, type, current `adapter_name`, status), the SQLite `spool.db`, the `status` marker, and `nested/` Worker/Psyche child perches.
- **Never** part of `tracked/` and never synced — each node owns its own perch runtime. This is the clean alternative to mixing perch files into the context repo and excluding them via gitignore.

## Endpoint metadata & adapter history (`tracked/agents/<id>/meta.json`)

- Records the endpoint's durable identity: type, creation, and an **ordered adapter history** (most-recent first). Synced, so any node knows it.
- Drives the **resume UX**: the latest adapter is the default resume offer, the next-latest is second, … the oldest is second-to-last, and "choose a different adapter" is the final option.
- The *currently-running* `adapter_name` lives in node-local `perches/<id>/info.json`; the *history* is endpoint identity in `tracked/`.

## Shells in the layout

Shells are endpoints but a different shape from agent endpoints:
- **Nested perch under the owner:** `perches/<endpoint_id>/shells/<adapter>-<n>/` — one agent owns many shells. `info.json` carries `type=Shell`, `owner`, `adapter_name`, `status` (online|offline) — **not** the capability set (that lives in the shell adapter manifest, resolved by `adapter_name`). `spool.db` holds the durable command + text/file channels; the sensory channel is REST-only and never spooled.
- **Not in the subnet registry** — private to the agent↔shell link, not a general messaging surface.
- **No `tracked/` context** — a Shell has no mind; never synced.
- **Logs** in node-local `history/<endpoint_id>/shells/<adapter>-<n>.jsonl`.
- **Adapter/platform-bound, not adapter-agnostic** — capability surface comes from the providing shell adapter; it does not move harnesses.
- **Lifecycle = online / offline / torn-down.** Link-break always closes the binary (optional pre-close instruction + termination timeout). **Ephemeral** (manifest property) ⇒ perch torn down *and removed from the agent's shell history*; **persistent** ⇒ perch kept offline for relink.
- **Cross-node links:** the shell perch lives on the shell's node (nested under the owner id as a label there); commands ride Iroh from the owner's node.

## Migration & backup

- **Back up / migrate:** `identity/` + `tracked/` (+ `history/` if wanted). That's identity, trust, subnet, and the full agent/project mind.
- **Regenerate on the target:** `perches/`, `registry/`, `daemon/`, `updates/` rebuild from the durable set + the live subnet.
- Legacy `claude_skill_owl` migration maps its `psyches/tracked/` git repo into `tracked/` and its node identity/agents into `identity/` + `perches/`.

## Open sub-items

- Exact carry-over of modern SPT's git branch mechanics (worktrees vs branch-checkout) into `tracked/`.
- Whether `history/` ever needs cross-node fetch (today node-local; history-lookup over the network would serve remote reads).

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

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

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

+**Hub auth — shared deploy key.** The gh-authed setup node creates the private repo, generates a **read-write deploy key**, registers its public half on the repo, and distributes the **private half as subnet secret material** (via pairing / P2P, like the TOTP seed). Other nodes need **no GitHub account** — they use the deploy key over SSH. New-node linking never blocks (auto key + P2P backstop). Revocation = rotate the key (remove from repo, regenerate, redistribute) when a node is de-trusted or hub mode is disabled.

+The `a-<id>` / `p-<project_id>` branch views are **linked git worktrees** off the bare seed repo. Linked worktrees store **absolute** back-links by default (`/home/<user>/…`, `C:\Users\<user>\…`) → break on relocation, a different user account, or migration. Use **relative worktree paths** (`git config worktree.useRelativePaths true`, Git ≥ 2.48) so `tracked/` is relocatable. **Fallback** for older git: spt-core runs `git worktree repair` after any relocation to re-point links locally (spt-core detects git version and picks). Cross-node *sync* transfers branch **objects**, not worktree plumbing, so sync is already path-agnostic — relative paths matter for `$SPT_HOME` relocation, backup/restore, and legacy migration.


agreed on the scope clarification. P2P always present. -sub-qs: 1. agree on prefer-GH, fallback-P2P. 2. how would the shared deploy key work? sounds ideal. otherwise i think the user should be forced…


Today's modern SPT backs up its tracked-context **git repo** to a remote GitHub repo (`/spt:psyche-sync-setup`, requires `gh` + account + setup). In spt-core, **only the remote is replaced** — the context stays a local git repo with its `a-<endpoint_id>` (per-agent) and `p-<project_id>` (per-project) branch views; the *data* syncs between a user's nodes over the built-in P2P networking instead of being pushed to GitHub (no `gh`/account/setup). Concurrent multi-node writes are arbitrated by the precedence/freshness guard (KNOWN-HAZARDS 6.5, node identity in the marker). The per-project branch view exists so any agent can synthesize broader context from *other* agents' contexts in the same project and support 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. On-disk layout: `docs/STORAGE.md`.

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.


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)

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.

-Today's modern SPT backs up its tracked-context **git repo** to a remote GitHub repo (`/spt:psyche-sync-setup`, requires `gh` + account + setup). In spt-core, **only the remote is replaced** — the context stays a local git repo with its `a-<endpoint_id>` (per-agent) and `p-<project_id>` (per-project) branch views; the *data* syncs between a user's nodes over the built-in P2P networking instead of being pushed to GitHub (no `gh`/account/setup). Concurrent multi-node writes are arbitrated by the precedence/freshness guard (KNOWN-HAZARDS 6.5, node identity in the marker). The per-project branch view exists so any agent can synthesize broader context from *other* agents' contexts in the same project and support 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. On-disk layout: `docs/STORAGE.md`.

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

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


Now the ADR-0002 amendment.


## Context

The sister project (`claude_skill_owl`) decided networking should live in a separate `spt-node` daemon,

## Amendment (2026-05-31): GitHub re-admitted as an opt-in context-sync hub

This ADR retired GitHub as the *required/default* cross-node sync. It is now re-admitted as an **opt-in alternative transport for the context repo only** (`tracked/`), enabled by `spt context-github-setup`:

- **Default stays P2P** + no-account — the core promise is unchanged.
- **Hub mode (opt-in):** every node pull→merge→pushes against a private GitHub remote (always-online sync hub + live GUI view), authed by a **shared deploy key** distributed as subnet secret material (only the setup node needs a GitHub account). **Prefer-GitHub, P2P backstop.**
- **Scope:** hub mode carries the **context repo only**. Messaging, registry, remote-drive, pairing, and presence always ride P2P/Iroh; gh-on never removes the P2P requirement. So the original decision (networking baked-in, first-class, P2P) stands in full — the hub is a context-sync convenience layered on top, not a return to GitHub-as-infrastructure.

## Context

The sister project (`claude_skill_owl`) decided networking should live in a separate `spt-node` daemon,


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

## Amendment (2026-05-31): GitHub re-admitted as an opt-in context-sync hub

This ADR retired GitHub as the *required/default* cross-node sync. It is now re-admitted as an **opt-in alternative transport for the context repo only** (`tracked/`), enabled by `spt context-github-setup`:

- **Default stays P2P** + no-account — the core promise is unchanged.
- **Hub mode (opt-in):** every node pull→merge→pushes against a private GitHub remote (always-online sync hub + live GUI view), authed by a **shared deploy key** distributed as subnet secret material (only the setup node needs a GitHub account). **Prefer-GitHub, P2P backstop.**
- **Scope:** hub mode carries the **context repo only**. Messaging, registry, remote-drive, pairing, and presence always ride P2P/Iroh; gh-on never removes the P2P requirement. So the original decision (networking baked-in, first-class, P2P) stands in full — the hub is a context-sync convenience layered on top, not a return to GitHub-as-infrastructure.

## Context

The sister project (`claude_skill_owl`) decided networking should live in a separate `spt-node` daemon,

# Bake networking into core; collapse spt-node into the per-machine daemon

## Status

accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context.

## Context

The sister project (`claude_skill_owl`) decided networking should live in a separate `spt-node` daemon, explicitly to decouple the network stack from SPT's Claude-Code-tied core binary, expose SPT-protocol-as-product, and let networking evolve on its own release cycle. That decision predates spt-core.

spt-core is a ground-up, harness-independent rebuild. The primary reason `spt-node` was separate — decoupling networking from a CC-tied binary — no longer exists, because spt-core is already decoupled from any harness. Meanwhile the product goal for networking sharpened: WAN cross-machine messaging should be **first-class and zero-config — "just works."**

## Decision

Bake the WAN transport stack (Iroh + mDNS LAN discovery + pairing) into spt-core's library crates, behind a compile-time `net` feature flag (default-on in the reference binary, optional for embedded library consumers).

"Just works WAN, reachable even when no session is running" requires a persistent per-machine network presence — the exact job `spt-node` was created to do. spt-core already commits to a single always-on, one-per-machine `spt-daemon` to supervise PTYs (the terminal wrapper). **Collapse the network-host role into that same daemon (option B1).** One process owns both PTYs and the node identity + WAN endpoint.

Rejected alternatives:
- **Seams-only in core, transport in spt-node (A):** inconsistent with first-class/zero-config networking; preserves a separate-deliverable boundary whose original justification is gone.
- **Two daemons — term-daemon + net-daemon (B2):** doubles the always-on process count and the crash/restart/binary-handoff surface for no benefit once both are in core.
- **LAN-in-core, WAN-in-node (C):** puts the repo boundary *inside* the transport layer, splitting shared discovery/identity/framing code across two repos.

**v1 relay stance:** default to n0.computer's free public relays for zero-config onboarding; ship a self-hostable-relay config knob and plain-language disclosure as the principled escape hatch. The dependency is operational, metadata-only (traffic is end-to-end encrypted), and escapable.

## Consequences

- `spt-node` is retired from the project vocabulary as a separate deliverable. WAN transport is a **first-class v1 capability** of spt-core (baked into the core crates, default-on in the reference binary) — *not* a post-v1 milestone and not a separate repo. The `SptNode` endpoint type survives unchanged — it remains how a machine is addressed.
- The "second project atop spt-core" is no longer mandated to be spt-node. The real next milestone is the rebuilt spt plugin reaching parity.
- The reference binary carries Iroh's footprint (~8MB) and the `noq` git-patch coupling when built with `net`. The feature flag keeps minimal library builds clean.
- v1 has a soft, documented runtime dependency on n0-operated relay infrastructure. Acceptable for v1; self-host path is the mitigation.
- The single daemon is a single point of failure for both PTY hosting and networking on a machine; its crash-recovery and binary-handoff story must cover both roles. (Addressed by the broker/brain split in **ADR-0004** — the stable broker holds PTYs + sockets across brain restarts.)

+## Amendment (2026-05-31): GitHub re-admitted as an opt-in context-sync hub

+This ADR retired GitHub as the *required/default* cross-node sync. It is now re-admitted as an **opt-in alternative transport for the context repo only** (`tracked/`), enabled by `spt context-github-setup`:

+- **Default stays P2P** + no-account — the core promise is unchanged.

+- **Hub mode (opt-in):** every node pull→merge→pushes against a private GitHub remote (always-online sync hub + live GUI view), authed by a **shared deploy key** distributed as subnet secret material (only the setup node needs a GitHub account). **Prefer-GitHub, P2P backstop.**

+- **Scope:** hub mode carries the **context repo only**. Messaging, registry, remote-drive, pairing, and presence always ride P2P/Iroh; gh-on never removes the P2P requirement. So the original decision (networking baked-in, first-class, P2P) stands in full — the hub is a context-sync convenience layered on top, not a return to GitHub-as-infrastructure.


accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context.

accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context. · amended (2026-05-31): GitHub re-admitted as an opt-in context-sync hub (see Amendment below).


# Bake networking into core; collapse spt-node into the per-machine daemon

## Status

accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context.

## Amendment (2026-05-31): GitHub re-admitted as an opt-in context-sync hub

This ADR retired GitHub as the *required/default* cross-node sync. It is now re-admitted as an **opt-in alternative transport for the context repo only** (`tracked/`), enabled by `spt context-github-setup`:

- **Default stays P2P** + no-account — the core promise is unchanged.
- **Hub mode (opt-in):** every node pull→merge→pushes against a private GitHub remote (always-online sync hub + live GUI view), authed by a **shared deploy key** distributed as subnet secret material (only the setup node needs a GitHub account). **Prefer-GitHub, P2P backstop.**
- **Scope:** hub mode carries the **context repo only**. Messaging, registry, remote-drive, pairing, and presence always ride P2P/Iroh; gh-on never removes the P2P requirement. So the original decision (networking baked-in, first-class, P2P) stands in full — the hub is a context-sync convenience layered on top, not a return to GitHub-as-infrastructure.

## Context

The sister project (`claude_skill_owl`) decided networking should live in a separate `spt-node` daemon, explicitly to decouple the network stack from SPT's Claude-Code-tied core binary, expose SPT-protocol-as-product, and let networking evolve on its own release cycle. That decision predates spt-core.

spt-core is a ground-up, harness-independent rebuild. The primary reason `spt-node` was separate — decoupling networking from a CC-tied binary — no longer exists, because spt-core is already decoupled from any harness. Meanwhile the product goal for networking sharpened: WAN cross-machine messaging should be **first-class and zero-config — "just works."**

## Decision

Bake the WAN transport stack (Iroh + mDNS LAN discovery + pairing) into spt-core's library crates, behind a compile-time `net` feature flag (default-on in the reference binary, optional for embedded library consumers).

"Just works WAN, reachable even when no session is running" requires a persistent per-machine network presence — the exact job `spt-node` was created to do. spt-core already commits to a single always-on, one-per-machine `spt-daemon` to supervise PTYs (the terminal wrapper). **Collapse the network-host role into that same daemon (option B1).** One process owns both PTYs and the node identity + WAN endpoint.

Rejected alternatives:
- **Seams-only in core, transport in spt-node (A):** inconsistent with first-class/zero-config networking; preserves a separate-deliverable boundary whose original justification is gone.
- **Two daemons — term-daemon + net-daemon (B2):** doubles the always-on process count and the crash/restart/binary-handoff surface for no benefit once both are in core.
- **LAN-in-core, WAN-in-node (C):** puts the repo boundary *inside* the transport layer, splitting shared discovery/identity/framing code across two repos.

**v1 relay stance:** default to n0.computer's free public relays for zero-config onboarding; ship a self-hostable-relay config knob and plain-language disclosure as the principled escape hatch. The dependency is operational, metadata-only (traffic is end-to-end encrypted), and escapable.

## Consequences

- `spt-node` is retired from the project vocabulary as a separate deliverable. WAN transport is a **first-class v1 capability** of spt-core (baked into the core crates, default-on in the reference binary) — *not* a post-v1 milestone and not a separate repo. The `SptNode` endpoint type survives unchanged — it remains how a machine is addressed.
- The "second project atop spt-core" is no longer mandated to be spt-node. The real next milestone is the rebuilt spt plugin reaching parity.
- The reference binary carries Iroh's footprint (~8MB) and the `noq` git-patch coupling when built with `net`. The feature flag keeps minimal library builds clean.
- v1 has a soft, documented runtime dependency on n0-operated relay infrastructure. Acceptable for v1; self-host path is the mitigation.
- The single daemon is a single point of failure for both PTY hosting and networking on a machine; its crash-recovery and binary-handoff story must cover both roles. (Addressed by the broker/brain split in **ADR-0004** — the stable broker holds PTYs + sockets across brain restarts.)

-accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context.

+accepted (2026-05-28) — reverses the sister project's separate-daemon decision (claude_skill_owl CONTEXT.md §Networking) for the spt-core context. · amended (2026-05-31): GitHub re-admitted as an opt-in context-sync hub (see Amendment below).


Cross-node context sync fully captured:

- **Single-writer invariant** (active-instance-authoritative) → conflicts rare by construction; disjoint files union.
- **Merge driver** resolves context files by precedence marker (`spt:node` added), newest-wins — never git line-merge / conflict markers. `--allow-unrelated-histories` for the independently-seeded repos.
- **Two transport modes:** P2P (default, no-account) / Hub (opt-in `spt context-github-setup`, always-online GitHub sync hub + GUI view, **shared deploy key** distributed as subnet secret, P2P backstop). Hub = context-repo only; P2P always required for everything else.
- **Worktree portability:** relative worktree paths (Git ≥ 2.48) + `git worktree repair` fallback, for `$SPT_HOME` relocation / cross-OS / migration.
- **ADR-0002 amended** — GitHub re-admitted as opt-in context-sync hub, default stays P2P.

Captured across STORAGE.md (full mechanics), CONTEXT.md (two-mode summary), ADR-0002 (amendment).

Uncommitted in the working tree: CONTEXT.md, STORAGE.md, ADR-0002. Want me to commit + push this batch now, then move to a remaining open item (17.3 Psyche authoring directives / 17.4 dormancy policy / 17.5 consent-gate UX)?


if the deploy key works via SSH, how do files get pushed from a node to the remote?


Concretely — git pushes to GitHub over SSH using the deploy key, fully programmatically, without touching the user's personal SSH config:

1. **SSH remote URL** — the `tracked/` repo's remote is the SSH form: `git@github.com:<user>/<repo>.git` (not HTTPS).
2. **Repo-scoped key selection** — spt-core sets `core.sshCommand` on *that* repo (or `GIT_SSH_COMMAND` per invocation):
   `ssh -i $SPT_HOME/identity/context_deploy_key -o IdentitiesOnly=yes`
   `IdentitiesOnly=yes` stops the SSH agent from offering the user's other keys; the scoping means spt-core's git ops use *only* the deploy key and never collide with the user's personal git/SSH.
3. **Key storage + perms** — the deploy key private half lives in `$SPT_HOME/identity/` (subnet secret, node-local, never in `tracked/`). SSH requires `0600` perms (Unix); spt-core sets them on write.
4. **Host-key trust** — spt-core pre-seeds GitHub's published host key into a scoped `known_hosts` (safer than blind `accept-new`).
5. **Push flow** — on context update, with that scoped SSH: `git fetch origin` → merge (driver + `--allow-unrelated-histories`) → `git push origin 'a-*' 'p-*'`. The read-write deploy key authenticates; GitHub matches it to the repo and grants the push.

**The one gotcha — port 22.** Some networks block SSH/22. GitHub serves **SSH over 443** at `ssh.github.com:443` — spt-core uses that endpoint when 22 is blocked. If SSH is blocked entirely, it falls to the **P2P backstop**.

**Alternative if you'd rather avoid SSH:** a **fine-grained PAT** scoped to the single repo, distributed the same way (subnet secret), used over **HTTPS** via a git credential helper (`https://x-access-token:<pat>@github.com/...`). HTTPS dodges the port-22 issue but the token is account-tied (deploy keys are repo-only, cleaner trust). I lean **SSH deploy key + 443 fallback + P2P backstop**; the PAT is the escape hatch if SSH is undesirable.

Want SSH-deploy-key as primary (my rec), or HTTPS-PAT? Then I'll fold the push mechanics into STORAGE and commit the batch.


deploy key as primary. record and commit


Recording the push mechanics in STORAGE, then committing the batch.


**Hub auth — shared deploy key.** The gh-authed setup node creates the private repo, generates a **read-write deploy key**, registers its public half on the repo, and distributes the **private half as subnet secret material** (via pairing / P2P, like the TOTP seed). Other nodes need **no GitHub account** — they use the deploy key over SSH. New-node linking never blocks (auto key + P2P backstop). Revocation = rotate the key (remove from repo, regenerate, redistribute) when a node is de-trusted or hub mode is disabled.

**Hub auth — shared deploy key.** The gh-authed setup node creates the private repo, generates a **read-write deploy key**, registers its public half on the repo, and distributes the **private half as subnet secret material** (via pairing / P2P, like the TOTP seed). Other nodes need **no GitHub account** — they use the deploy key over SSH. New-node linking never blocks (auto key + P2P backstop). Revocation = rotate the key (remove from repo, regenerate, redistribute) when a node is de-trusted or hub mode is disabled.

**Push mechanics (deploy key over SSH).** Fully programmatic, never touching the user's personal SSH/git config:
- The `tracked/` remote is the **SSH form** (`git@github.com:<user>/<repo>.git`).
- spt-core scopes key selection to *this* repo via `core.sshCommand` (or per-invocation `GIT_SSH_COMMAND`): `ssh -i $SPT_HOME/identity/context_deploy_key -o IdentitiesOnly=yes` — `IdentitiesOnly=yes` stops the agent offering the user's other keys; the scoping prevents any collision with the user's personal git ops.
- Private key lives in `$SPT_HOME/identity/` (node-local, never in `tracked/`), written `0600`.
- GitHub's published host key is pre-seeded into a scoped `known_hosts` (safer than blind `accept-new`).
- Flow on context update: `git fetch origin` → merge (driver + `--allow-unrelated-histories`) → `git push origin 'a-*' 'p-*'`.
- **Port-22 blocked** → use GitHub's SSH-over-443 endpoint (`ssh.github.com:443`); SSH blocked entirely → fall to the P2P backstop.
- *Alternative (escape hatch only):* a fine-grained repo-scoped PAT over HTTPS via a credential helper — dodges port-22 but is account-tied; SSH deploy key is primary.


**Hub auth — shared deploy key.** The gh-authed setup node creates the private repo, generates a **read-write deploy key**, registers its public half on the repo, and distributes the **private half as subnet secret material** (via pairing / P2P, like the TOTP seed). Other nodes need **no GitHub account** — they use the deploy key over SSH. New-node linking never blocks (auto key + P2P backstop). Revocation = rotate the key (remove from repo, regenerate, redistribute) when a node is de-trusted or hub mode is disabled.

**Push mechanics (deploy key over SSH).** Fully programmatic, never touching the user's personal SSH/git config:
- The `tracked/` remote is the **SSH form** (`git@github.com:<user>/<repo>.git`).
- spt-core scopes key selection to *this* repo via `core.sshCommand` (or per-invocation `GIT_SSH_COMMAND`): `ssh -i $SPT_HOME/identity/context_deploy_key -o IdentitiesOnly=yes` — `IdentitiesOnly=yes` stops the agent offering the user's other keys; the scoping prevents any collision with the user's personal git ops.
- Private key lives in `$SPT_HOME/identity/` (node-local, never in `tracked/`), written `0600`.
- GitHub's published host key is pre-seeded into a scoped `known_hosts` (safer than blind `accept-new`).
- Flow on context update: `git fetch origin` → merge (driver + `--allow-unrelated-histories`) → `git push origin 'a-*' 'p-*'`.
- **Port-22 blocked** → use GitHub's SSH-over-443 endpoint (`ssh.github.com:443`); SSH blocked entirely → fall to the P2P backstop.
- *Alternative (escape hatch only):* a fine-grained repo-scoped PAT over HTTPS via a credential helper — dodges port-22 but is account-tied; SSH deploy key is primary.

+**Push mechanics (deploy key over SSH).** Fully programmatic, never touching the user's personal SSH/git config:

+- The `tracked/` remote is the **SSH form** (`git@github.com:<user>/<repo>.git`).

+- spt-core scopes key selection to *this* repo via `core.sshCommand` (or per-invocation `GIT_SSH_COMMAND`): `ssh -i $SPT_HOME/identity/context_deploy_key -o IdentitiesOnly=yes` — `IdentitiesOnly=yes` stops the agent offering the user's other keys; the scoping prevents any collision with the user's personal git ops.

+- Private key lives in `$SPT_HOME/identity/` (node-local, never in `tracked/`), written `0600`.

+- GitHub's published host key is pre-seeded into a scoped `known_hosts` (safer than blind `accept-new`).

+- Flow on context update: `git fetch origin` → merge (driver + `--allow-unrelated-histories`) → `git push origin 'a-*' 'p-*'`.

+- **Port-22 blocked** → use GitHub's SSH-over-443 endpoint (`ssh.github.com:443`); SSH blocked entirely → fall to the P2P backstop.

+- *Alternative (escape hatch only):* a fine-grained repo-scoped PAT over HTTPS via a credential helper — dodges port-22 but is account-tied; SSH deploy key is primary.

The `a-<id>` / `p-<project_id>` branch views are **linked git worktrees** off the bare seed repo. Linked worktrees store **absolute** back-links by default (`/home/<user>/…`, `C:\Users\<user>\…`) → break on relocation, a different user account, or migration. Use **relative worktree paths** (`git config worktree.useRelativePaths true`, Git ≥ 2.48) so `tracked/` is relocatable. **Fallback** for older git: spt-core runs `git worktree repair` after any relocation to re-point links locally (spt-core detects git version and picks). Cross-node *sync* transfers branch **objects**, not worktree plumbing, so sync is already path-agnostic — relative paths matter for `$SPT_HOME` relocation, backup/restore, and legacy migration.


Bash

cd "C:/Users/decid/Documents/projects/spt-core" && git add -A && git status --short

Stage batch, show status


warning: in the working copy of 'CONTEXT.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs/STORAGE.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs/adr/0002-bake-networking-into-core-collapse-spt-node.md', LF will be replaced by CRLF the next time Git touches it
M  CONTEXT.md
M  docs/STORAGE.md
M  docs/adr/0002-bake-networking-into-core-collapse-spt-node.md


cd "C:/Users/decid/Documents/projects/spt-core" && git commit -q -F - <<'EOF'
docs: cross-node context sync (P2P + opt-in GitHub hub)

- Single-writer invariant + custom precedence-marker merge driver
  (spt:node added); never raw git line-merge; --allow-unrelated-histories
  for independently-seeded repos; disjoint files union
- Two transport modes: P2P (default, no-account) vs Hub (opt-in
  `spt context-github-setup`, always-online GitHub sync hub + GUI view,
  prefer-GitHub / P2P backstop); hub is context-repo only, P2P always
  required for messaging/registry/remote-drive/pairing
- Shared read-write deploy key distributed as subnet secret; only the setup
  node needs a GitHub account; SSH push mechanics (scoped core.sshCommand,
  IdentitiesOnly, key in identity/, host-key pre-seed, 443 fallback)
- Relative worktree paths (Git >= 2.48) + `git worktree repair` fallback for
  $SPT_HOME relocation / cross-OS / migration portability
- ADR-0002 amended: GitHub re-admitted as opt-in context-sync hub; default
  stays P2P + no-account

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

Commit and push cross-node sync batch


pushed
293e337 docs: cross-node context sync (P2P + opt-in GitHub hub)
1d30876 docs: shell model, sleep/wake, and from-shell owner-shutdown
9515e2e docs: initial spt-core planning artifacts


Pushed → `293e337` on `origin/main`. Cross-node context sync fully recorded and committed.

**State:** 3 commits on GitHub. The context-sync design is complete — single-writer invariant, precedence-marker merge driver, P2P-default / opt-in-GitHub-hub dual transport, SSH deploy-key push mechanics, worktree portability.

PRD §17 open items remaining:
- **17.3** — Psyche live/project authoring directives (the live problem that makes two-tier sync actually hold)
- **17.4** — dormancy default policy + suspend command surface
- **17.5** — consent/security-gate UX (now also touched by shell `can_shutdown` + `shell_wake_spawn_anywhere`)

Which next?


before we get into those, a note about developers: spt-core needs great documentation and a straightforward onramp for dev-agents that come to build with it. we should research what popular libraries or services have critically-acclaimed documentation quality (pointed to by claims from devs on social services, blog posts, news articles, papers etc.) -- and research the techniques they use to make their docs so good, then emulate that when building spt-core's docs.


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


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


Strong instinct — and spt-core has a *dual* docs audience: human devs **and** dev-agents building with it (the latter is unusual and underexplored). Launching thorough background research on acclaimed-docs techniques, with an explicit angle on agent-consumable docs.


Agent

Research acclaimed docs techniques

Prompt:
Research what makes developer documentation critically acclaimed, grounded in real developer sentiment, and distill the reusable techniques. The goal is to inform the documentation strategy for `spt-core` — a Rust agent-ecosystem framework (inter-agent messaging, live-agent lifecycle, terminal hosting, P2P networking, a runtime-manifest harness contract) whose primary consumers are BOTH human developers AND AI dev-agents that build integrations with it.

Do real web research (search + fetch). Cite sources (URLs) — especially developer praise on social platforms (HN, Reddit, X/Twitter, dev.to, blog posts), news articles, and any papers/style guides.

Deliver a structured markdown report covering:

1. **Exemplars.** Identify 8-15 libraries/frameworks/APIs/services whose docs are widely praised by developers (e.g. Stripe, Twilio, Tailwind CSS, the Rust Book, Django, FastAPI, Supabase, Prisma, Astro, Clerk, Vue, React, Cloudflare, Bun/Deno, Linear, etc. — verify with actual cited praise; don't assume). For each: a one-line "why devs love it" + a cited source.

2. **Techniques, ranked by how often they recur.** Extract the concrete, reusable techniques behind great docs. Examples to investigate and confirm/expand: time-to-first-success / killer quickstart; the Diátaxis framework (tutorials vs how-to vs reference vs explanation); progressive disclosure; runnable/copy-pasteable examples; interactive playgrounds; "show the whole working thing first"; excellent API reference generation; task-oriented structure; consistent voice/tone (cite Stripe/Google dev style guides); searchability; versioning; visual design; error-messages-link-to-docs; example-driven onboarding; conceptual diagrams. For each technique: what it is, why it works, and which exemplars use it (cited).

3. **Docs FOR AI/agent consumers (critical for spt-core).** Research the emerging practice of making docs consumable by LLMs/agents: `llms.txt` / `llms-full.txt` conventions, structured/machine-readable reference, deterministic copy-paste blocks, schema-first docs, stable anchors, "one canonical way to do X", MCP doc servers, context-window-friendly chunking. Who's doing this well and what works? Cite sources.

4. **Anti-patterns.** The most-cited documentation failures devs complain about (cite).

5. **Synthesis → recommendation for spt-core.** A concrete, prioritized documentation strategy: what doc types to produce, what information architecture, what the killer quickstart should achieve, how to serve the human + agent dual audience, and the top ~10 techniques to adopt. Be specific and actionable.

Be rigorous: prefer claims backed by cited developer sentiment over generic advice. Return the report as your final message; do not write files.


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


Async agent launched successfully.
agentId: abffd48b0b8324c0f (internal ID - do not mention to user. Use SendMessage with to: 'abffd48b0b8324c0f' to continue this agent.)
The agent is working in the background. You will be notified automatically when it completes.
Do not duplicate this agent's work — avoid working with the same files or topics it is using. Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.
output_file: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\abffd48b0b8324c0f.output
Do NOT Read or tail this file via the shell tool — it is the full sub-agent JSONL transcript and reading it will overflow your context. If the user asks for progress, say the agent is still running; you'll get a completion notification.

Research acclaimed docs techniques

Prompt:
Research what makes developer documentation critically acclaimed, grounded in real developer sentiment, and distill the reusable techniques. The goal is to inform the documentation strategy for `spt-core` — a Rust agent-ecosystem framework (inter-agent messaging, live-agent lifecycle, terminal hosting, P2P networking, a runtime-manifest harness contract) whose primary consumers are BOTH human developers AND AI dev-agents that build integrations with it.

Do real web research (search + fetch). Cite sources (URLs) — especially developer praise on social platforms (HN, Reddit, X/Twitter, dev.to, blog posts), news articles, and any papers/style guides.

Deliver a structured markdown report covering:

1. **Exemplars.** Identify 8-15 libraries/frameworks/APIs/services whose docs are widely praised by developers (e.g. Stripe, Twilio, Tailwind CSS, the Rust Book, Django, FastAPI, Supabase, Prisma, Astro, Clerk, Vue, React, Cloudflare, Bun/Deno, Linear, etc. — verify with actual cited praise; don't assume). For each: a one-line "why devs love it" + a cited source.

2. **Techniques, ranked by how often they recur.** Extract the concrete, reusable techniques behind great docs. Examples to investigate and confirm/expand: time-to-first-success / killer quickstart; the Diátaxis framework (tutorials vs how-to vs reference vs explanation); progressive disclosure; runnable/copy-pasteable examples; interactive playgrounds; "show the whole working thing first"; excellent API reference generation; task-oriented structure; consistent voice/tone (cite Stripe/Google dev style guides); searchability; versioning; visual design; error-messages-link-to-docs; example-driven onboarding; conceptual diagrams. For each technique: what it is, why it works, and which exemplars use it (cited).

3. **Docs FOR AI/agent consumers (critical for spt-core).** Research the emerging practice of making docs consumable by LLMs/agents: `llms.txt` / `llms-full.txt` conventions, structured/machine-readable reference, deterministic copy-paste blocks, schema-first docs, stable anchors, "one canonical way to do X", MCP doc servers, context-window-friendly chunking. Who's doing this well and what works? Cite sources.

4. **Anti-patterns.** The most-cited documentation failures devs complain about (cite).

5. **Synthesis → recommendation for spt-core.** A concrete, prioritized documentation strategy: what doc types to produce, what information architecture, what the killer quickstart should achieve, how to serve the human + agent dual audience, and the top ~10 techniques to adopt. Be specific and actionable.

Be rigorous: prefer claims backed by cited developer sentiment over generic advice. Return the report as your final message; do not write files.


Research running in background (launched with `run_in_background: true`, so I stay reachable). It's sweeping for acclaimed-docs exemplars, the recurring techniques behind them, the emerging `llms.txt`/agent-consumable-docs practice, anti-patterns, and a synthesized recommendation.

When it returns I'll distill it into a **docs strategy** for spt-core — likely `docs/DOCS-STRATEGY.md` (information architecture, the killer quickstart target, the human+agent dual-audience approach, and the top techniques to adopt), so docs quality is a designed-in commitment from day one rather than an afterthought.

I'll pick back up the moment it lands.