# Phase 24: Tracked-Dir Forked-Repo Layout - Context

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

<domain>
## Phase Boundary

`psyches/tracked/` becomes a forked-git-repo system. A single bare `seed/` repo
holds the canonical object database; each agent and project gets its own git
**worktree** linked to `seed/` whose branch matches the folder name. Every
commune/signoff commits to the local branch and pushes to `seed/` (the local
"remote"); Phase 35 later swaps the remote to a GitHub `spt-agent-storage` repo.

Per-generation activity is recorded in `agents/{id}/sessions.log` — single file,
truncated on every new generation, with prior-gen content recoverable via
`git log -p sessions.log`.

Pure-restructure phase. No new commune/signoff commands. New write-path
plumbing only; existing skill commands stay identical.

</domain>

<decisions>
## Implementation Decisions

### Git Plumbing

- **D-01:** All git ops shell out to the **system `git` CLI** — matches Phase 23's
  `src/common/git.rs` Stamp helpers. No `git2-rs`. No bundled git. Operator can
  reproduce every internal command at the shell. Phase 23's 500ms soft-timeout +
  rate-limited stderr warning posture extends to all Phase 24 git invocations.

- **D-02:** **Missing-git fallback = degrade silently.** When `git --version`
  fails on first boot: skip all version-control plumbing for the session;
  commune/signoff writes still land in `agents/{id}/` + `projects/{name}/`
  directories (raw files, not versioned). Single-line stderr warning,
  rate-limited per Phase 23 D-13. `$LIVE doctor` surfaces
  `git missing — tracked-dir not versioned`. Commune/signoff delivery never
  blocks.

### Working-Tree Topology

- **D-03:** **Bare seed + worktrees.** `psyches/tracked/seed/` is initialized
  via `git init --bare`. Each `agents/{id}/` + `projects/{name}/` directory is
  a `git worktree add` linked working tree sharing seed's object db. Disk cost
  ~1× (blobs de-duped across all branches). Atomic multi-branch push via
  `git push --all origin`. Phase 35 remote swap is one-line (`git remote
  set-url origin ...`) — propagates to every worktree.

- **D-04:** **Branch naming uses prefix-namespaces to prevent collisions.**
  Agent branches: `a-{agent_id}` (e.g. `a-doyle`). Project branches:
  `p-{project_name}` (e.g. `p-claude_skill_owl`). Folder names stay
  prefix-less: `agents/doyle/`, `projects/claude_skill_owl/`. The folder→branch
  mapping is mechanical: `agents/{x}` → branch `a-{x}`; `projects/{x}` →
  branch `p-{x}`. Ensures an agent and a project cannot share a branch
  namespace even when they share a literal name string.

- **D-05:** **Worktree maintenance config locked.**
  `git -C seed config gc.worktreePruneExpire never` set at init time to
  prevent `git gc` deleting objects still referenced by linked worktrees.
  Cleanup paths use `git worktree remove` (never `rm -rf` of the folder).
  `$LIVE doctor` runs `git -C seed worktree prune --dry-run` to surface
  orphaned worktree metadata.

- **D-06:** **Bootstrap seed with an empty initial commit** so each worktree
  has a valid HEAD. Done once on first `seed/` init. Commit subject:
  `init: tracked seed`. No content, just the ref anchor.

### Commit Message Format (folds SEED-004)

- **D-07:** **Commit subjects** follow shape
  `{kind}: {self_id} {payload-type} — {short}` where `{kind}` ∈
  `{commune, signoff, echo, context-save, amend-signoff, sessions, migrate}`
  and `{short}` is a 50-char truncated context excerpt. Example:
  `commune: doyle echo — phase 24 wave 2 done`.

- **D-08:** **Commit-message trailers carry the Phase 23 Stamp.** Implemented
  via a new renderer `Stamp::commit_trailers()` in `src/common/git.rs`. Per
  scope:
  - **Agent worktree** (`agents/{id}/`) commits: trailers
    `Machine`, `Project`, `Branch`, `Head-SHA`, `Head-Subject`.
  - **Project worktree** (`projects/{name}/`) commits: trailers
    `Machine`, `Branch`, `Head-SHA`, `Head-Subject` (project implied by the
    worktree's path; omitted to avoid redundancy).
  - Optional fields (branch / head_sha / head_subject) follow Phase 23 D-11:
    if cwd is not in a repo, those trailers are omitted entirely, not
    set to empty.

  Folds [SEED-004](.planning/seeds/SEED-004-stamp-params-in-psyche-commit-messages.md).
  Mark seed `status: folded` on this CONTEXT.md commit.

### Sessions Audit Log

- **D-09:** **Path = `agents/{id}/sessions.log` (singular, no `{gen}` suffix).**
  Prior generations are recoverable via `git log -p sessions.log`. The seal
  IS a git commit; truncation is the rollover mechanism.

- **D-10:** **JSONL format with dedup-by-combo.** One JSON object per line:
  ```json
  {"ts":"2026-05-20T08:00:00Z","session_uuid":"e0616fa0-...","trigger":"pulse"}
  ```
  Three fields only. **No `gen`, `machine`, or `project`** — those are
  reconstructible from git history + Phase 23 / D-08 commit trailers. Field
  order locked: `ts` → `session_uuid` → `trigger`.

  **Dedup contract (D-18, locked):** Rows are dedupe-keyed by the
  `(session_uuid, trigger)` pair. Each combo appears **at most once** per
  generation. Write algorithm:
  1. Read existing `sessions.log` into memory (small file — bounded by
     `4 triggers × N sessions in current gen`).
  2. If a row with matching `(session_uuid, trigger)` exists → update that
     row's `ts` to the new timestamp in place; rewrite the file.
  3. Else → append a new line.

  Effect: a single session that pulses 50 times produces ONE `pulse` row
  whose `ts` advances. `boot`/`commune`/`signoff`/`pulse` for the same UUID
  produce up to four distinct rows. The audit shows "what happened to this
  session" rather than "every wake".

- **D-11:** **Trigger enum (4 values, locked):**
  - `boot` — initial `/spt:live` start OR `/spt:revive` (fresh `claude -p`
    with no `--resume`). Both treated as new-generation events.
  - `pulse` — periodic wrapper resume cycle (the recurring `claude -p
    --resume` spawn that keeps the psyche alive). Emitted from
    `src/live/wrapper/claude.rs::resume_session` (and the wrapper main
    loop in `src/live/wrapper/mod.rs`), NOT `src/live/touch_loop.rs`
    (which is the Touch monitor, not the pulse emitter).
  - `commune` — echo_commune cycle.
  - `signoff` — init_signoff write.

- **D-12:** **Seal semantics = truncate-on-roll.** On generation change
  (`boot` trigger): (a) commit current `sessions.log` to the agent branch
  with subject `sessions: {self_id} seal gen {N}`, (b) truncate file to
  empty, (c) the first entry of the new gen is appended fresh. File always
  holds current-gen entries only.

### Write-Path Integration

- **D-13:** **Commune/signoff write path** = (1) write payload into the
  appropriate worktree (`agents/{id}/` for agent-scoped context,
  `projects/{name}/` for project-scoped), (2) `git -C {worktree} add ...`,
  (3) `git -C {worktree} commit -m {D-07 subject + D-08 trailers}`. **No
  push step in Phase 24.** Worktrees share `seed/.git/objects/` and write
  branch refs directly into `seed/refs/heads/{branch}` — the bare `seed/`
  sees commits the instant a worktree commits, no push needed. Phase 35
  adds (4) `git push origin {branch}` against the GitHub remote, plus the
  one-time `git remote add origin <gh>` per worktree.

  Failures at any step soft-fail per D-02; payload landing on disk is the
  guaranteed contract.

  **Rationale (research finding):** Pushing a worktree to its own bare host
  is a no-op (refs already updated by the commit; git returns "Everything
  up-to-date"). Skipping push avoids ~30ms of redundant subprocess work per
  write and removes the codepath until it carries real meaning. Trade-off:
  Phase 35 adds the push call site **and** the origin config; Phase 24
  D-17 doctor "unpushed" column reads as `local-only` until Phase 35.

- **D-14:** **Cross-machine divergent pushes defer to Phase 35.** Until
  Phase 35 enables the GitHub remote, `seed/` is local-only and there is
  no cross-machine push contention. Within a single machine, concurrent
  agent worktrees write to distinct branches — no contention. Conflict
  policy is **Phase 35's problem statement**, not Phase 24's.

### Migration

- **D-15:** **Auto-migrate on first boot.** Detection: `psyches/tracked/seed/`
  absent AND any of `tracked/{id}.log` / `tracked/{id}.md` /
  `tracked/{id}-memformat.xml` present. One-shot migrate path:
  1. `git init --bare psyches/tracked/seed`
  2. Apply D-05 config + D-06 bootstrap commit
  3. For each detected agent `{id}`:
     a. `git -C seed worktree add ../agents/{id} a-{id}`
     b. Move legacy files in with renames per ROADMAP SC4:
        - `tracked/{id}.log` → `agents/{id}/daemon.log`
        - `tracked/{id}.md` → `agents/{id}/live_context.md`
        - `tracked/{id}-memformat.xml` → `agents/{id}/memformat.xml`
     c. Single commit per agent:
        `migrate: {id} — import legacy flat layout`
     d. Push to seed.
  4. Stderr emits one summary line: `migrated N agents to forked layout`.
  5. Legacy flat files removed on success.

  No project worktrees created during migration (no legacy project-scoped
  files exist). Projects created lazily on first project-scoped write.

### Lazy Worktree Creation

- **D-16:** **Worktrees created on first relevant write, not eagerly.**
  Agent worktree: first commune/signoff/echo/pulse for that `{id}`.
  Project worktree: first project-scoped write for that `{name}`.
  Creation flow: `git -C seed worktree add ../{scope}/{name} {prefix}-{name}`
  (where `{prefix}` is `a` or `p` per D-04). Branch is created from the
  seed's empty bootstrap commit.

### Doctor Surface (SC10)

- **D-17:** **`$LIVE doctor` reports per-worktree status.** Output table per
  worktree: `{folder} → {branch} → {clean|dirty|N unpushed}`. Uses
  `git -C seed worktree list --porcelain` for the roster +
  `git -C {worktree} status --porcelain` + `git -C {worktree} rev-list
  --count @{upstream}..HEAD` (or `HEAD` count if no upstream) for state.

### Claude's Discretion

- Exact subject-line truncation algorithm (D-07 50-char `{short}`) —
  reuse Phase 23 `head_subject` 72-char + ellipsis pattern, scaled down.
- Where the migration detection check fires within `src/live/start.rs` /
  `src/common/owlery.rs` — planner's call.
- Whether `Stamp::commit_trailers()` returns `String` (joined) or
  `Vec<(&str, String)>` (structured) — researcher/planner decide based on
  caller ergonomics.
- Empty initial commit author identity — likely a synthetic
  `spt-bootstrap <noreply@spt>` to differentiate from real agent commits.
- Test coverage breakdown (unit / golden / integration) — researcher +
  planner triangulate; worktree fixtures need new harness work.
- Exact `$LIVE doctor` table rendering — visual polish; planner picks.

</decisions>

<canonical_refs>
## Canonical References

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

### Source Surfaces (Touchpoints)
- `src/common/owlery.rs` — state-root + perch helpers; add tracked-repo
  helpers (`tracked_root()`, `seed_path()`, `worktree_path()`).
- `src/common/git.rs` — Phase 23 `Stamp` lives here; add
  `Stamp::commit_trailers()` (D-08) + worktree-aware helpers
  (`git_worktree_add`, `git_push_branch`, etc.).
- `src/live/start.rs` — boot path; first-boot migration trigger (D-15) lives
  here.
- `src/live/wrapper/claude.rs` (`resume_session`) + `src/live/wrapper/mod.rs`
  — emits `pulse` trigger entries (D-11). `src/live/touch_loop.rs` is the
  Touch monitor; it does NOT emit pulse triggers (research correction).
- `src/live/echo_commune.rs` — emits `commune` trigger entries.
- `src/live/signoff.rs` — `compose_init_signoff_payload`; emits `signoff`
  trigger entries.
- `src/live/context.rs` — `run_save` / `run_amend_signoff`; write path now
  routes through worktree + commit + push (D-13).
- `src/owl/doctor.rs` — extend with D-17 per-worktree status.

### Phase 23 Continuity
- `.planning/phases/23-commune-signoff-project-root-head-sha-stamping/23-CONTEXT.md` —
  Phase 23 decisions (Stamp shape, soft-fail posture, suppression marker
  location). D-08 builds on Phase 23's `Stamp` + git.rs helpers.
- `.planning/seeds/SEED-004-stamp-params-in-psyche-commit-messages.md` —
  folded into D-08; mark `status: folded` post-write.

### Project Context
- `.planning/PROJECT.md` — v1.8 entry; Phase 24 is the structural restructure
  of v1.8.
- `.planning/ROADMAP.md` §Phase 24 — 11 Success Criteria.
- `.planning/todos/done/2026-04-18-organize-psyches-tracked-by-agent-and-add-psyche-sessions-fi.md` —
  original organize-tracked todo (promoted).

### Codebase Maps
- `.planning/codebase/ARCHITECTURE.md`
- `.planning/codebase/CONVENTIONS.md`
- `.planning/codebase/STRUCTURE.md`

### Forward-Compat
- ROADMAP Phase 24.1 — `tracked/agents/{id}/info.json` lives inside the
  agent worktree created here.
- ROADMAP Phase 25 — Owlery nesting + psyche-download wire-up; consumes
  the worktree layout for cross-agent peek (Phase 23 D-09 unlocked).
- ROADMAP Phase 35 — gh remote sync. Phase 24's `seed/` "local remote"
  becomes the GitHub remote (D-14).

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets (from Phase 23)
- `crate::common::git::Stamp` — five-field stamp struct already in tree;
  D-08 adds a `commit_trailers()` renderer beside existing
  `event_attrs()` + `yaml_frontmatter()`.
- Soft-fail subprocess posture (Phase 23 D-13) — 500ms timeout +
  rate-limited stderr warning. Extend to all Phase 24 git calls (D-02).
- `crate::common::owlery::state_root()` / `psyches_dir()` — extend with
  `tracked_root()`, `seed_path()`, `agent_worktree(id)`,
  `project_worktree(name)` helpers.

### Established Patterns
- **Pure composer functions** (`compose_*_payload`) — Phase 23 pattern.
  Sessions-log entries should compose pure JSON-line strings testable
  without filesystem.
- **Subprocess soft-fail with stderr warn** — `git_commit_context` in
  `src/live/context.rs` swallows git errors and warns via owl message.
  All Phase 24 git ops follow this posture.
- **Path constants in `common/owlery.rs`** — single source of truth for
  on-disk locations.

### Integration Points
- Binary handoff (Phase 18.4/18.5) — during handoff a v1.7.1 wrapper may
  briefly write to the legacy flat layout while a v1.8 sibling has already
  migrated. Coexistence: migration is idempotent (skips if `seed/` exists);
  legacy-write detection on next boot triggers a follow-up migration pass
  for any newly-orphaned flat files.
- Phase 24.1's `info.json` — lives at `agents/{id}/info.json` inside the
  agent worktree. Same write path (D-13) covers it.
- Suppression markers (Phase 23 D-09) — already locked at
  `$SPT_HOME/suppressions/`, OUTSIDE the tracked tree. No double-migration
  concern.

</code_context>

<specifics>
## Specific Ideas

- Branch prefix scheme `a-` / `p-` is **mechanical**: agents/X → branch
  a-X, projects/Y → branch p-Y. Planner should not negotiate the mapping.
- Worktree-aware cleanup: any path that removes an agent or project
  directory MUST `git worktree remove` first, not `rm -rf`. Doctor catches
  orphans via `git worktree prune --dry-run`.
- `sessions.log` JSONL field order is locked: `ts`, `session_uuid`,
  `trigger`. New fields would be additive; existing readers tolerate.
- Generation count is NOT in the sessions.log file — only in the git
  commit subject (`sessions: doyle seal gen N`).
- The seed/ "local remote" is the push target until Phase 35. Phase 35
  swaps `git remote set-url origin ...`; no Phase 24 code change needed
  to support that swap.

</specifics>

<deferred>
## Deferred Ideas

- Per-machine UUID for `machine` field (alternative to OS hostname) —
  Phase 35 territory. Hostname is the locked source for Phase 23 D-03 and
  reused unchanged in Phase 24.
- `seed/` history squash / shallow clone for new agents — disk concern
  only matters if seed grows large; revisit only when measured.
- `$LIVE migrate` opt-in subcommand — auto-migrate (D-15) is the default.
  Manual subcommand can be added later if auto-migrate ever proves
  surprising in practice.
- Sessions log retention / pruning policy — git history is unbounded for
  now. Future phase can add `git log --since=` based pruning if growth
  becomes a concern.
- Cross-machine concurrent push conflict policy — explicitly Phase 35.
- Project worktree pre-creation during migration — migration creates
  agent worktrees only; project worktrees stay lazy.

</deferred>

---

*Phase: 24-tracked-dir-forked-repo-layout-agents-projects-branches-sess*
*Context gathered: 2026-05-20*
