# RESUME-CONTEXT-PLAN — wire `api psyche-download` into activation

**Status:** in progress (2026-07-27). **Owner:** emphasys. **Target release:** omp-spt 0.3.32.

## Defect

An omp-spt session never reads its durable mind back in. The extension's entire `spt api` verb
set is `endpoint-info`, `bind`, `state`, `session-end`, `listen`, `poll` — `psyche-download`
appears nowhere in `adapter/strings/omp-spt.mjs`, the Rust helpers, or `adapter/omp-spt.toml`.
`api boundary` makes the mind *survive* a reset; `psyche-download` is how the next session reads
it back in, and we never call it. So every resumed OMP session starts blank of its accumulated
role / live / project context and falls back to whatever static instructions the project happens
to carry.

Reported by deployah 2026-07-27 with a same-node cost measurement (an omp-spt-hosted live agent's
tracked mind never exceeded ~440 B across its whole git history, versus 2.4–4 kB for hook-driven
agents on the same node) — blankness is self-reinforcing, because an echo-commune can only
synthesize the blank session it observed. Confirmed independently against this tree before
accepting.

Only prior trace in-repo is `CHECKPOINT-COMMUNE-PLAN.md` T3 ("Parity wiring — `psyche-download`
at SessionStart"), which was specified and never wired. `docs/PARITY.md` currently overclaims:
it describes the startup brief as the whole of activation context.

## Published contract (grounded, `llms-full.txt`)

```
spt api psyche-download <ID> [--json] [--token <TOKEN>] [--session-id <SESSION_ID>]
```

- Emits the durable two-tier mind (self-tagged `<live-role>` / `<live-context>` /
  `<project-context>`) **plus** any written-but-not-yet-synthesized drop as
  `<pending-commune>` / `<pending-signoff>` slices. Project resolved from the perch's recorded cwd.
- Read-only — never writes the mind store.
- `NO-CONTEXT:<id>` on **stderr** with **exit 0** is the fresh-init signal: inject nothing.
- Needs association proof: `--token` (capability token) or `--session-id` (matches the perch's
  `info.json`). A bare call is `AUTH_REFUSED`.
- Callable on shipped spt today; no version floor.

## Design decisions

1. **One insertion point covers every path.** On this adapter promotion *is* activation: `/ready`,
   `/live`, `/live --auto`, and hosted spawn all funnel through `activateEndpoint`
   (`omp-spt.mjs:1415`). Pulling there satisfies the fresh, resume, and go-live obligations at once
   — no separate go-live wiring needed (unlike a hook-shaped harness).
2. **Authenticate with `--token`, not `--session-id`.** The bind token is ours by construction.
   Presenting a session id can, against a dead owner, trigger core's dead-owner rescue and
   **re-pin the perch** (`SESSION_REPIN`) — a write-side effect we have no business causing from a
   read-only context pull. Token-only keeps the pull inert. Stderr is still surfaced (never
   swallowed) so a `SESSION_REPIN` from any other cause is visible.
3. **Fire after bind, consume at the first boundary.** The pull starts as soon as bind returns a
   token; the result is consumed by the existing `before_agent_start` handler that already flushes
   `startupBriefPending`. Resume context is pushed **ahead of** the startup brief — the mind first,
   the mechanics second.
4. **Never fail activation on it.** A failed or timed-out pull logs at error level and the session
   proceeds. Losing the mind is bad; losing the session is worse.
5. **stdout and stderr must not merge.** `runSpt` currently folds stderr into the resolved string.
   That is fine for status verbs, but here stderr carries `NO-CONTEXT:<id>` and any
   `SESSION_REPIN` line, and stdout is injected into the model's context — merging would inject
   diagnostics as if they were mind. Add an opt-in `streams` mode that resolves
   `{ stdout, stderr }`; every existing caller keeps the merged-string contract.

## Tasks

- **T1 — `runSpt` streams mode.** Accumulate stdout/stderr separately (keep the merged accumulator
  for error detail); `overrides.streams` resolves `{ stdout, stderr }`. Thread `options.streams`
  through the default `runSptCommand` wrapper and `runCommand`. Normalize a plain-string result
  from an injected `runSptCommand` so test stubs stay valid.
- **T2 — `pullResumeContext()`.** Post-bind, `api --adapter omp-spt psyche-download <id> --token
  <token>` in streams mode. Classify: `NO-CONTEXT:<id>` on stderr → no injection; other stderr →
  `pi.logger.error`; non-empty stdout → hold as pending resume context.
- **T3 — inject at the first boundary.** In `before_agent_start`, await the pending pull (bounded),
  push its payload into `additions` before `startupBrief(id)`, and clear the pending flag so it is
  injected exactly once.
- **T4 — registry + evidence.** New `REQ-PARITY-RESUME-CONTEXT` in `traceable-reqs.toml`
  (`doc`/`impl`/`unit`), tagged in the same commit.
- **T5 — unit tests** (`tests/omp-extension.mjs`): pull issued after bind with token auth and never
  with a session id; payload injected once, ahead of the startup brief; `NO-CONTEXT` injects
  nothing; failure logs and does not break activation; stderr diagnostics never reach the injected
  text.
- **T6 — docs.** `docs/PARITY.md` gains the resume-context claim (`doc->` tag); `CHANGELOG.md`
  0.3.32; regenerate `docs-site/llms-full.txt` via `python ci/docs/build-docs.py --root . --write`.
- **T7 — release 0.3.32** per `docs/RELEASE-RUNBOOK.md` (8 version-gated files, gates, pack, tag,
  publish).

## Gate

`node tests/omp-extension.mjs` · `sh ci/run-gates.sh` · `traceable-reqs check` all green before the
release commit is frozen.

## Not in scope

The reported second defect (a brief documenting `.claude/<id>-commune.md` against a `.spt`
`commune_dir`) is **withdrawn, not deferred**. This adapter's tree ships no `strings/briefs/` and
no `identity.md`, and its packaged skills already say `.spt/<id>-commune.md`, matching the
manifest. The files deployah read were orphaned residue in the *install* dir
(`adapters/_github/BigscreenVR-omp-spt/strings/briefs/`, mtime Jul 9) — an old sed-adapted brief
set from a generation this tree stopped shipping, alongside a stray foreign `claude-spt.exe` in
the directory `{adapter_dir}` resolves to. Nothing serves them (zero `briefs` references in the
extension). The live issue it exposes is core-side install hygiene — `spt adapter update` replaces
in place and leaves a previous generation's files behind — and is on doyle, not on this adapter.
