# Phase 23: Commune & Signoff Project-Root + HEAD SHA Stamping - Context

**Gathered:** 2026-05-19
**Status:** Ready for planning

<domain>
## Phase Boundary

Every commune-shaped payload (plain commune, init_signoff, echo_commune) and every persisted context-save (initial save + amend-signoff) records a uniform 5-field stamp — `machine`, `project`, `branch`, `head_sha`, `head_subject` — so resumed agents can compute repo/branch/cross-machine drift since last activity. `psyche-download` surfaces both the stored stamp and a fresh `<current/>` block (with commits-since and unpulled-commits counts), and instructs Self to confirm catch-up behaviour via AskUserQuestion when drift is detected against the same project.

Pure-metadata phase. No new commands, no protocol additions beyond stamping existing payloads. Plain `COMMUNE (ts): body` prose form is promoted to `<EVENT type="commune" ...>body</EVENT>` to carry attrs uniformly with the other typed envelopes.

</domain>

<decisions>
## Implementation Decisions

### Stamped Field Set

- **D-01:** Five fields stamped on every commune/signoff/echo payload: `machine`, `project`, `branch`, `head_sha`, `head_subject`. Expanded from the roadmap's nominal "project_root + HEAD SHA" pair after discussion (2026-05-19) — user requested machine + project + branch as full set.
- **D-02:** `project` is derived as `basename(git rev-parse --show-toplevel)` when the `git` CLI is available and the cwd is inside a repo; otherwise the basename of the current working directory. Subdir invocations within a repo stamp the same project. Same repo cloned to different paths still matches.
- **D-03:** `machine` is the OS hostname (`gethostname()` on Unix, `COMPUTERNAME` on Windows). No user-configurable override (`$SPT_MACHINE` not introduced this phase).
- **D-04:** `head_subject` is the first line of the HEAD commit message, capped at 72 characters with `…` ellipsis on overflow. Predictable EVENT-attr size; matches git subject-line convention.

### Carrier Shape per Payload Type

- **D-05:** All EVENT-wrapped payloads carry the fields as **inline attributes** on the EVENT tag — siblings to `timestamp`. Applies to `init_signoff` (`src/live/signoff.rs::compose_init_signoff_payload`) and `echo_commune` (`src/owl/echo_commune.rs::compose_echo_commune_payload`).
- **D-06:** Plain commune is **promoted** to a typed EVENT envelope: `<EVENT type="commune" timestamp="..." machine="..." project="..." branch="..." head_sha="..." head_subject="...">body</EVENT>`. Replaces the `COMMUNE (ts): body` prose form across `src/live/commune.rs::run` and `commune_result`. Clean cutover (parallel to Phase 29 echo_commune envelope cutover) — no legacy-prose fallback retained.
- **D-07:** Context-save markdown files (`{ctx_dir}/{self_id}.md`) and amend-signoff appended sections both gain a **YAML front-matter block** stamping the same five fields. Initial save prepends front-matter at top of file; amend-signoff section prepends front-matter inside its `## Post-Signoff Amendment (ts)` block.

### psyche-download Surface

- **D-08:** `psyche-download` emits two structured blocks alongside existing memformat + context body:
  - `<psyche-stamp machine=... project=... branch=... head_sha=... head_subject=.../>` — the most recent stored stamp (from the context file's front-matter).
  - `<current machine=... project=... branch=... head_sha=... head_subject=... commits_since="N" commits_unpulled="N"/>` — live values computed at download time. `commits_since` = `git rev-list --count {stored_sha}..HEAD`. `commits_unpulled` = `git rev-list --count HEAD..@{upstream}` (or `0` when no upstream / no network). Both omit gracefully when not in a repo.
- **D-09:** When `current.project == stored.project` AND any of `branch`/`head_sha`/`machine` differ, `psyche-download` instructs Self to ask via **AskUserQuestion**:
  > "This project has advanced since my involvement. Should I catch up?"
  Options (multiSelect): `Observe new commits` / `Peek at peer contexts` / `Skip for now` / `Don't ask again`.
  - The `Peek at peer contexts` option is **only rendered** when (a) other live-agent contexts for this project are discoverable AND (b) the Phase 24/25 `tracked/` restructure has landed (until then it stays no-op / hidden).
  - `Don't ask again` writes a per-(self_id, project) suppression marker that future psyche-downloads honour.
- **D-10:** When `current.project != stored.project` (cross-project resume), `psyche-download` is silent — fields surface in `<current/>` but no banner, no AskUserQuestion. Trust that Self will notice on read.

### Not-a-Repo Fallback

- **D-11:** When the current cwd is not inside a git repo (or `git` is absent / fails / times out), `head_sha`, `branch`, and `head_subject` attrs are **omitted entirely** from the EVENT tag. `machine` and `project` (cwd basename) always present. Attr absence is the signal; no sentinel string. Same rule applies to YAML front-matter — those keys omitted, not set to empty.

### Git Invocation on Hot Path

- **D-12:** Direct subprocess call per fire — `git rev-parse HEAD`, `git rev-parse --abbrev-ref HEAD`, `git log -1 --pretty=%s` (or single combined `git log -1 --pretty=format:%H%n%s` plus `rev-parse --abbrev-ref HEAD`). No process-wide cache, no mtime-based invalidation. Trade marginal CPU for correctness + simplicity.
- **D-13:** Each git subprocess gets a **500ms soft timeout**. On timeout / nonzero exit / git-not-found, treat as "not a repo" per D-11 and emit a single-line stderr warning (rate-limited; not per-fire). Never blocks commune/signoff delivery.
- **D-14:** echo_commune fires from the wrapper inside `psyche_dir` (not Self's cwd). It must still stamp **Self's project**, not the psyche dir's name. Wrapper resolves Self's project by reading the matching live perch's metadata (today via `$OWL list` / perch `info.json`; Phase 24 will formalise the lookup). If lookup fails, fall back to psyche_dir basename (degraded but never crashes the echo path).

### Claude's Discretion

- Exact shape of EVENT-attr escaping for `head_subject` strings containing `"`, `<`, `>`, `&` — use the existing `event_attr_escape` in `src/owl/poll.rs`. No new escaper.
- File layout for the per-(self_id, project) "don't ask again" suppression marker — planner decides (likely under `$SPT_HOME/suppressions/`).
- Whether the stamp helper (`fn stamp() -> Stamp`) lives in `src/common/git.rs` (new) or extends `src/common/owlery.rs` — planner's call.
- Test coverage breakdown across unit/golden/integration — researcher + planner triangulate, but golden fixtures will need refresh on Linux (CI excludes Windows from golden runs).

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Source Surfaces (Touchpoints)
- `src/live/commune.rs` — `run` + `commune_result`. Currently formats `COMMUNE (ts): body`; needs envelope promotion + 5-field stamp.
- `src/live/signoff.rs` §`compose_init_signoff_payload` — EVENT envelope; needs 5 attrs added.
- `src/owl/echo_commune.rs` §`compose_echo_commune_payload` + §`dispatch_commune_markers` — EVENT envelope; needs 5 attrs added; wrapper-resolved Self project (D-14).
- `src/live/context.rs` §`run_save` / `context_save_result` / `run_amend_signoff` / `amend_signoff_result` — markdown writers; need YAML front-matter prepend.
- `src/live/context.rs` §`download_payload` / `run_download` — emit `<psyche-stamp/>` + `<current/>` blocks + same-project AskUserQuestion directive (D-08/D-09).
- `src/owl/poll.rs` §`event_attr_escape` / `event_body_escape` — reuse for attr/body escaping; no new escaper.

### Project Context
- `.planning/PROJECT.md` — v1.7.1 sealed; v1.8 entry; Phase 23 listed as pure-metadata addition with no deps.
- `.planning/ROADMAP.md` §Phase 23 (lines 389-403) — Success Criteria 1-4.
- `.planning/todos/done/2026-04-18-track-project-root-and-latest-commit-in-commune-and-amend-si.md` — original problem statement + touch points list.

### Codebase Maps
- `.planning/codebase/ARCHITECTURE.md` — owl/live module split, IPC topology.
- `.planning/codebase/CONVENTIONS.md` — module/output/status-tag conventions.
- `.planning/codebase/STRUCTURE.md` — src/ layout.

### Forward-Compat (Phase 24/25 awareness)
- ROADMAP Phase 24 (lines 405+) — `tracked/` forked-repo layout + per-agent dirs. The `Peek at peer contexts` AskUserQuestion option (D-09) stays no-op until this lands.
- ROADMAP Phase 25 — Owlery nesting; psyche-download wire-up. Final landing place for cross-agent peek.

### Skill / Plugin Docs to Update
- `plugin/spt/skills/spt-commune/SKILL.md` — commune instructions reference EVENT envelope shape.
- `plugin/spt/skills/spt-signoff/SKILL.md` — signoff payload shape.
- `plugin/spt/skills/spt-live/SKILL.md` — LIVE skill commune callout.

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `crate::common::time::format_timestamp()` — used by all 3 EVENT composers today; no change needed.
- `crate::owl::poll::event_attr_escape` / `event_body_escape` — handle `<`, `>`, `&`, `"` escaping; reuse for new attrs.
- `crate::common::owlery::write_last_commune_epoch_now(self_id)` — cursor bump helper; unaffected by this phase.
- `std::panic::catch_unwind` wrapper pattern (WR-02) around `send::deliver_body_anonymous` — preserve when adding stamp call.

### Established Patterns
- **Typed EVENT envelope cutover** (Phase 29 echo_commune, quick-260513-v8f init_signoff) — clean cutover with no legacy-prose fallback. Plain-commune promotion (D-06) follows the same pattern.
- **`compose_*_payload` pure functions** — every EVENT composer is a pure formatter testable without subprocess/filesystem. New stamp function should follow this shape: `fn stamp() -> Stamp { ... }` returns plain struct; composers accept `&Stamp` parameter.
- **Structured `_result` variants** (`commune_result`, `signoff_result`, `context_save_result`, `amend_signoff_result`) — D-07-introduced parallel API surface; must be updated in lockstep with `run` variants.
- **Soft-fail on subprocess failure** — git_commit_context in `src/live/context.rs` already swallows `git` errors and warns Self via owl message. Same posture for stamp git calls (D-13).

### Integration Points
- Wrapper-side parser for stamped EVENTs — `src/live/wrapper/*` reads EVENT envelopes to dispatch INIT_SIGNOFF etc. New attrs are additive; existing predicates (case-insensitive `type=` match) are unaffected.
- Binary handoff (Phase 18.4/18.5) — during handoff a v1.7.1 wrapper may briefly receive a v1.8-stamped EVENT (or vice versa). Additive attrs are forward/backward compatible; no schema break. New `<EVENT type="commune">` envelope (D-06) is the only schema addition — older wrappers won't recognise it but won't break (they ignore unknown-type EVENTs as plain body text).
- SessionStart injection — `psyche-download` output is consumed by SessionStart hook + skill-instructed Self reads. The new AskUserQuestion directive (D-09) must be emitted as **instruction text** ("ask the user via AskUserQuestion: ..."), not invoked by owl.exe directly — psyche-download is not the calling agent.
- Per-(self_id, project) "don't ask again" suppression file — new state; lives under `$SPT_HOME/` somewhere. Coordinate location with Phase 24 tracked/ layout to avoid double migration.

</code_context>

<specifics>
## Specific Ideas

- User's mental model for the AskUserQuestion option set (D-09): "Observe new commits / Peek at peer contexts / Skip for now / Don't ask again". The exact phrasing is locked; planner should not reword.
- "Peek at peer contexts" is a forward-compat hook. Render it only when Phase 24/25's `tracked/` directory restructure has landed AND other live-agent contexts for the same project are discoverable. Until then, hidden / removed from the option set — not stubbed as "Coming soon".
- "This project has advanced since my involvement. Should I catch up?" — exact AskUserQuestion question text, locked.
- `<current/>` block must include `commits_since` (stored→HEAD) AND `commits_unpulled` (HEAD→upstream). Two separate counts. `commits_unpulled` is allowed to be `0` / omitted when no upstream is configured or fetch is offline.

</specifics>

<deferred>
## Deferred Ideas

- `$SPT_MACHINE` env override for `machine` field — useful for privacy / cross-device aliasing; not in scope this phase. (Surfaced in D-03 alternatives.)
- Computed `<drift>` block with files-changed / shortstat — heavier alternative to the raw `<current/>` + counts. Deferred; the count pair is sufficient for v1.8.
- `project_path` (full absolute path alongside basename) — forensics-only; not in scope. May be folded into Phase 24 when tracked/ stores per-agent project metadata.
- Cross-project AskUserQuestion prompt — D-10 keeps cross-project silent. Future phase could add an opt-in "I'm resuming in a different project; show me catch-up options" surface.
- Per-process / mtime-based caching of git rev-parse output — D-12 chose direct call. Revisit only if hot-path measurements show real impact.

### Reviewed Todos (not folded)
- `2026-04-18-add-psyche-peek-slash-command-powershell-resume-branch.md` (score 0.9, area: psyche) — `/psyche-peek` slash command; orthogonal to stamping. Belongs in v1.8 Phase 25 (psyche-download wire-up) or its own phase.
- `2026-04-13-capsule-psmux-use-winname-flag-for-repo-branch.md` (score 0.6, area: capsule) — Capsule (v1.9). Out of scope.
- `2026-04-21-sync-ccs-instance-settings-json-owl-live-paths.md` (score 0.6, area: tooling) — `$OWL`/`$LIVE` path sync; tooling-side, unrelated to stamping.
- `2026-04-13-capsule-reattach-existing-psmux-session.md` (score 0.4, area: capsule) — Capsule (v1.9). Out of scope.

</deferred>

---

*Phase: 23-commune-signoff-project-root-head-sha-stamping*
*Context gathered: 2026-05-19*
