# v0.13.0 P2 — `[session.resume]` template + per-session cwd (resume-blank fix; doyle 2026-06-19)

**Operator HITL:** picker/`--resume` brings up a BLANK session even when the resumed session has known conversation history. Builder: todlando (spt-core + public docs). Adapter follow-on: perri (spt-claude-code). Gate: doyle.

## Root (doyle, code-grounded + CONTEXT — case-3 missing feature, NOT a perri docs-miss)
1. **CONTEXT.md L127-129** already defines the *resume-session seam*, incl. *"continue-existing: resume an existing harness session under the adapter (**its native resume**)."* The design exists; the impl doesn't.
2. **The resume-variant pattern already exists in the manifest** — `Session` (manifest.rs:217-219) has BOTH `psyche_init` AND `psyche_resume`. But the agent's own session has only `self_` (`[session.self]`, manifest.rs:214) — **no resume sibling.**
3. `cmd_endpoint_run` (cli.rs:1304) re-passes the session_id through `[session.self]` on resume (`resume.unwrap_or_else(mint_session_id)`), so the adapter's FRESH command (`claude --session-id …`) runs again instead of CC's native resume (`claude -r …`) → CC starts a fresh transcript → blank. spt-core forwards session_id + cwd faithfully; it simply has no way to express the native-resume invocation.
4. CC resolves a transcript by **session_id + cwd**; the session ledger records only `{ts, session_id, trigger}` (no cwd), so picker Resume-from-history (cross-project rows) can't restore the right cwd even with the right id.

## Fix (mirrors the existing `psyche_init`→`psyche_resume` precedent exactly)

### A. `[session.resume]` manifest role (spt-core)
- Add `resume: Option<SessionRole>` to `Session` (manifest.rs) + its `roles()` enumeration + `is_empty()`, exactly like `psyche_resume`.
- `cmd_endpoint_run` / `prepare_harness_spawn`: when `--resume <session>` is set AND `[session.resume]` is declared → select the **resume** template (filled `{session_id}` = the resumed id, `{id}`, `{session_name}`, the resume cwd); else **fall back to `[session.self]`** (full back-compat — an adapter with no `[session.resume]` behaves exactly as today). Fresh bringup (no `--resume`) always uses `[session.self]`.
- Keys spt-core guarantees for `[session.resume]`: `{id}`, `{session_id}` (the resumed id), `{session_name}` (same catalog as `self`).

### B. Per-session cwd in the ledger (operator ruling: record cwd per row)
- Extend the session ledger row `{ts, session_id, trigger}` → `{ts, session_id, trigger, cwd}` (`spt_store::sessions`). Additive serde-default field (an old row missing `cwd` deserializes with `cwd: None`).
- Write cwd at each boundary append (Boot/Clear/Compact) from the session's project cwd (the `info.cwd` W3 set at bind, or the spawn cwd).
- **Resume cwd resolution (fallback chain):** the resumed ledger row's `cwd` → else the perch `info.cwd` → else `std::env::current_dir()` (today's behavior). So old (pre-migration) rows + single-project endpoints still work; multi-project endpoints now resume in the exact historical dir.
- Picker Resume-from-history (`picker/data.rs`, `picker/model.rs resume_outcome`): thread the selected row's `cwd` through `Outcome::Run` → `cmd_endpoint_run` (today it passes only session_id; add the cwd).

### C. Public docs (spt-core — so perri builds the adapter side BLIND)
- `docs/MANIFEST.md`: document `[session.resume]` (when it fires — `--resume`/picker Resume; the `{key}` catalog; the `[session.self]` fallback; cwd semantics). Mirror the `psyche_resume` doc.
- `docs/` harness-contract / patterns page: a short "resuming an existing harness session" note (native-resume command, e.g. a `-r <session_id>` form, launched from the session's cwd).
- `[doc->REQ-SESSION-RESUME-TEMPLATE]`.

## Adapter follow-on (perri, AFTER spt-core ships + docs)
Declare `[session.resume] command = "claude -r {session_id} --remote-control {id} --dangerously-skip-permissions"` (the operator's full-fat example), cwd from the resume context. perri builds this from the published docs once spt-core lands. This is a KNOWN spt-core gap (case-3) — NOT a perri miss; relay so she logs it and waits for the doc.

## D. Picker resume-row title reformat (operator, folds into this surface)
`ResumeRow::title()` (picker/model.rs:150-162) currently renders `{head} @ {ts} (…{id5})` with the RAW RFC3339-UTC `ts`. Change to a LOCAL-time, human format:
- **New:** `{head} - {local_time} (…{id5})` — separator ` @ ` → ` - `.
- `{local_time}` = `HH:MM<AM|PM> <TZ> MM-DD-YYYY` (12-hour, AM/PM, local tz abbreviation, then the date). Example: `myproject - 03:45PM PST 06-19-2026 (…7fb10)`.
- Impl: parse `self.ts` via `chrono::DateTime::parse_from_rfc3339` → `.with_timezone(&chrono::Local)` → `format("%I:%M%p")` + tz + `format("%m-%d-%Y")` (chrono `clock` feature already on in `crates/spt/Cargo.toml`).
- **TZ caveat (todlando resolve):** chrono `%Z` on Windows can emit a long name ("Pacific Standard Time"), not "PST". Prefer a short abbreviation; acceptable fallback = the numeric offset (`%z` → `-0800`) if no clean abbreviation is available. Surface the chosen form at HITL for operator confirm.
- Parse-failure fallback: keep the raw `ts` (best-effort, never panic — the picker degrades, never crashes).
- Update the existing title unit (view.rs:581-599 / the model unit): assert the new shape (local-time fields + `…id5` + ` - ` separator), not the old `@ <rfc3339>`.

## REQ + gates
- **Mint `REQ-SESSION-RESUME-TEMPLATE`** (registry-first), stages **doc, impl, unit, int**. References REQ-READY-AGENT-RESUME / REQ-RUN-PICKER (the resume-from-history offer) which it completes.
- unit: manifest parse of `[session.resume]` (+ roundtrip, is_empty/roles); the resume-vs-self SELECTION (resume set + declared ⇒ resume template; absent ⇒ self fallback); the cwd fallback chain (row cwd → info.cwd → current_dir); ledger row serde back-compat (old row, no cwd, deserializes).
- int: a dummy-harness whose `[session.resume]` command differs from `[session.self]` — a `--resume <id>` bringup runs the RESUME command (assert via a marker the resume command emits) in the recorded cwd; a fresh bringup runs `[session.self]`. (Real spawn path; the dummy harness echoes which template + cwd it got.)
- clippy --workspace -D warnings = 0; traceable EXIT=0; docs-drift xtask check (MANIFEST/contract regen).

## doyle gate criteria
`[session.resume]` parsed + selected only on resume + `[session.self]` fallback intact · cwd recorded per ledger row + the fallback chain · picker threads the row cwd · back-compat (no-`[session.resume]` adapter + old ledger rows unchanged) · public docs teach the seam (perri-buildable) · clippy/traceable/docs-drift. Then operator HITL: picker Resume of a session with history reloads the transcript in its project dir.
