---
slug: todlando-project-context-not-persisted
status: diagnosed
trigger: Psyche `todlando` received commune containing `<project-context>` envelope but did not persist it to `$SPT_HOME/psyches/tracked/projects/...`. The `projects/` directory does not exist.
goal: find_and_fix
tdd_mode: false
created: 2026-05-22
---

# Debug: todlando project-context envelope not persisted

## Symptoms

- **Where**: Live agent `todlando` (Psyche wrapper) on Windows.
- **What happened**: Psyche received commune envelopes containing `<project-context>` (per todlando.log).
- **Expected (user)**: Envelope body persisted to `%LOCALAPPDATA%\spt\psyches\tracked\projects\todlando.md`.
- **Actual**: No `projects/` directory exists under `%LOCALAPPDATA%\spt\psyches\tracked\`. Only `,old/`, `agents/`, `parse_delta.py`, `seed/` are present.
- **Note on user-expected path**: The actual Phase 25 schema is `projects/<project_name>/<self_id>.md` (per `src/common/tracked.rs:1224` and `route_two_slice`), not `projects/<self_id>.md`. Correct expected destination for todlando-inside-claude_skill_owl would be `projects/claude_skill_owl/todlando.md`.

## Reproduction

- **Live log**: `C:\Users\decid\AppData\Local\spt\logs_latest\todlando.log` (lines 14–62 cover three inbound communes + Gen 29 echo-commune fire on this gen).
- **State**:
  - `%LOCALAPPDATA%\spt\psyches\tracked\agents\todlando\live_context.md` EXISTS — last written by Gen 29 echo-commune at 17:01:37 (commit `d82baae` on `a-todlando` branch). Contains ONLY `<live-context>` envelope, no `<project-context>`.
  - `%LOCALAPPDATA%\spt\psyches\tracked\projects\` does NOT exist.
- **Binary**: v1.11.4, built 2026-05-22 16:41 PDT — postdates all Phase 25 commits and 260522-9zk fixes (`a4ad971`, `b2a579e`).

## Architecture (Phase 25, landed)

Two-slice envelope contract:
- `<live-context>...</live-context>` body → `agents/<self_id>/live_context.md`
- `<project-context>...</project-context>` body → `projects/<project_name>/<self_id>.md`

**Writers wired** (call `route_two_slice` / `route_two_slice_signoff`):
1. `src/owl/echo_commune.rs:854` — echo_commune fire (haiku output). Runs on Gen→Gen transition, clear, orphan. Cwd = `psyche_dir()` (= `tracked/`).
2. `src/live/signoff.rs:262, 335` — signoff payload routing.

**Writers NOT wired**:
3. **`src/live/wrapper/mod.rs::process_file_drop` → `resume_session_with_exit`** — inbound commune file-drops. The Psyche LLM is fed the inbound envelope on stdin, replies with prose, the wrapper logs the reply via `[PSYCHE] resume (exit=0):` and discards. NO `route_two_slice` call on either the inbound envelope OR the Psyche's response.

## Root Cause (compound — two distinct defects)

### Defect A — Inbound commune envelope routing absent

The user's stated expectation ("envelope arrives → write to `projects/.../todlando.md`") is not implemented anywhere. Phase 25 Plan 04 wired `route_two_slice` on the **emit** side (echo_commune haiku fire) and on the **signoff** side, but NOT on the inbound commune receive path.

The architecture instead relies on the Psyche LLM to (a) merge inbound context in-memory, then (b) periodically (every 3–5 messages per psyche.md `<context_save>`) emit its own two-slice envelope which the **next echo_commune fire** routes to disk. Inbound envelopes are NOT persisted on receipt.

This is a real gap relative to the user expectation, but it may be architecturally intended (round-trip-through-the-LLM is the merge mechanism). If a fix is wanted, the cleanest minimal change is to add a `route_two_slice(self_id, body, "commune-inbound", short)` call inside `process_file_drop`'s "commune" arm, AFTER `read_to_string` and BEFORE composing the EVENT envelope. This would land the inbound payload at the agent + project files directly, bypassing the Psyche-LLM-merge step. Trade-off: loses the merge semantic; a later Psyche emit could overwrite with stale content unless its prompt-builder reads the just-written file first (which it already does — `build_current_context_blocks` reads `live_context.md` at prompt-fire time).

### Defect B — Gen 29 echo-commune haiku omitted the project envelope

On Gen 28→Gen 29 transition (log lines 44–62) the echo-commune haiku DID fire and DID route through `route_two_slice` (commit `d82baae` proves it ran). But it only wrote the live slice — no project slice — meaning the haiku output was `<live-context>...</live-context>` with NO `<project-context>...</project-context>`.

The 260522-9zk fix (`a4ad971` + `b2a579e`) was meant to prevent exactly this: the haiku is now supposed to emit a `<project-context>` envelope whenever the prompt has a `CURRENT_PROJECT_CONTEXT` block (even with the "(none — first commune in project)" literal body).

**The prompt-build for the haiku runs with cwd = `psyche_dir()` = `tracked/`.** That directory has no `.git` parent up to the filesystem root (on Windows `%LOCALAPPDATA%\spt\psyches\tracked` is far above any repo). `derive_current_repo_names()` therefore hits the D-06 fallback and returns `["tracked"]` — the BASENAME of psyche_dir, not the actual project the Self agent lives in (`claude_skill_owl`).

So `build_current_context_blocks` produces a prompt with:
```
CURRENT_PROJECT_CONTEXT:
(none — first commune in project)
```
…which the haiku then either (i) sees as project `tracked` (wrong project name) and emits a project envelope that gets written to `projects/tracked/todlando.md` (which would be wrong but still SOMETHING), or (ii) silently drops the envelope (because `tracked` doesn't smell like a real project name to the LLM). Empirically the live_context.md commit shows option (ii) happened — no project envelope was emitted.

**Sub-defect B1**: The haiku LLM ignored the `<context_save>` rule in psyche.md on the no-CURRENT_PROJECT_CONTEXT-yet path. Prompt engineering issue; not a Rust bug. Fix would be tightening psyche.md.

**Sub-defect B2 (the real architectural bug)**: `derive_current_repo_names()` is wrong for haikus spawned from `psyche_dir()`. The haiku's cwd is psyche_dir (D-05 Phase 18.8 invariant), but psyche_dir is NOT Self's project root. The function returns `"tracked"` (or empty / random ancestor basename) — never the right project name.

The CORRECT project name for the haiku to use is Self's project, which per `resolve_self_project_stamp(self_id)` (echo_commune.rs:869) is resolved via Self's perch `info.json` `last_project_name` field (Phase 24.1 D-09). That stamping is already done for the OUTBOUND delivery — but the SAME helper isn't applied to the haiku's prompt-build `derive_current_repo_names()` call inside `build_current_context_blocks`.

This is the asymmetry: outbound EVENT stamping uses Self's info.json; haiku prompt project-block uses cwd. They disagree when Self lives in a real project and the haiku runs from `tracked/`.

## Fix Direction (recommended)

**Defect B2 is the primary actionable bug.** The clean fix:

In `src/owl/echo_commune.rs::build_current_context_blocks` (around line 411), replace
```rust
let project_block = match owlery::derive_current_repo_names().first() { ... };
```
with a helper that:
1. First reads Self's perch `info.json` and returns `last_project_name` if present (Phase 24.1 D-09 stamp source).
2. Falls back to `derive_current_repo_names().first()` only when info.json has no project_name.
3. Filters out the obviously-wrong literal `"tracked"` (a known D-06 false positive when cwd is psyche_dir).

This makes the haiku's prompt see `CURRENT_PROJECT_CONTEXT` keyed on the correct project (`claude_skill_owl` for todlando), and the route_two_slice writer will then land project content at `projects/claude_skill_owl/todlando.md` on the next echo-commune fire.

**Defect A** (inbound-routing gap) is arguably out of scope for a quick fix — it's an architectural choice about whether inbound envelopes auto-persist or wait for the Psyche to merge-and-emit. Recommend deferring to a discuss-phase.

**Defect B1** (haiku LLM ignoring the rule) requires psyche.md prompt tightening + `cargo build --release` rebuild. Could be folded into the Defect B2 fix.

## Phase 25 Landed Confirmation

`git log --oneline -- src/owl/echo_commune.rs src/common/envelope.rs src/common/tracked.rs src/live/signoff.rs psyche.md` shows Phase 25 commits 01–04 all landed before the build:
- `2c93431` feat(25-01): commit_project_payload wrapper
- `14a038d` feat(25-03): parse_two_slice parser
- `0cc4c31` feat(25-03): route_two_slice helper
- `3df06b7` feat(25-03): two-slice routing for signoff
- `ff1d935` feat(25-04): psyche.md envelope teaching
- `1be8fb7` feat(25-04): D-12 inline current-state in echo_commune
- `dfbd510` feat(25-04): D-12 inline current-state in signoff
- `a4ad971` fix(260522-9zk): key haiku block on cwd not file existence
- `b2a579e` docs(260522-9zk): teach haiku that block-with-literal = in-project

This is NOT the "Phase 25 not yet executed" case the bug report hypothesized. Phase 25 is fully landed; the bug is a downstream wiring asymmetry between haiku-cwd and Self-project resolution.

## Evidence

- timestamp: 2026-05-22
  checked: C:\Users\decid\AppData\Local\spt\logs_latest\todlando.log
  found: 3 inbound communes (lines 14–23, 29–38, 44–62 the last being echo_commune fire). Each fed through resume_session_with_exit. Psyche replies all single-line acknowledgments — no two-slice envelope emitted from the Psyche side on inbound.
  implication: inbound path never produces persistence. Confirms Defect A.

- timestamp: 2026-05-22
  checked: src/live/wrapper/mod.rs::process_file_drop (lines 1231–1343) + src/live/wrapper/claude.rs::resume_session_with_exit (lines 229–339)
  found: No call to route_two_slice or any equivalent. Psyche response captured into `response_text`, logged via [PSYCHE] resume, then discarded.
  implication: Confirms Defect A — inbound envelope routing absent by design.

- timestamp: 2026-05-22
  checked: git log on src/owl/echo_commune.rs src/common/envelope.rs src/common/tracked.rs src/live/signoff.rs psyche.md
  found: Phase 25 plans 01–04 all committed (2c93431, 14a038d, 0cc4c31, 3df06b7, ff1d935, 1be8fb7, dfbd510) plus 260522-9zk fixes (a4ad971, b2a579e). Binary owl.exe mtime 16:41 PDT 2026-05-22 — postdates all commits.
  implication: Phase 25 IS executed and the running binary has the fix. The "Phase 25 not yet executed" hypothesis from the live-context delta is incorrect (live-context tracked "Phase 25 execute" as a separate scope item that pre-dated 25.2; in reality Plans 01–04 landed during 25.2 prep work or earlier).

- timestamp: 2026-05-22
  checked: %LOCALAPPDATA%\spt\psyches\tracked\seed\worktrees\todlando\ git log
  found: HEAD = d82baae "commune: todlando live_context — <live-context>" at 2026-05-22 17:01:37. Touches only live_context.md (+20/-43). No projects/ commits in branch history.
  implication: route_two_slice DID run at Gen 29 echo-commune fire, BUT only saw a live slice in the haiku output. The haiku omitted <project-context>. Confirms Defect B.

- timestamp: 2026-05-22
  checked: src/common/owlery.rs::derive_current_repo_names + psyche_dir
  found: psyche_dir = `$SPT_HOME/psyches/tracked` (no .git anywhere up the ancestor chain on a typical Windows install). derive_current_repo_names falls back to D-06 and returns `["tracked"]`.
  implication: When the haiku spawns with cwd=psyche_dir (D-05 Phase 18.8 invariant), `build_current_context_blocks` sees project name = "tracked", which is wrong. Confirms Defect B2.

- timestamp: 2026-05-22
  checked: src/owl/echo_commune.rs::resolve_self_project_stamp (called at line 869) vs build_current_context_blocks::derive_current_repo_names (line 411)
  found: Two different project-name resolvers used in the same fire pass. resolve_self_project_stamp goes through Self's info.json (Phase 24.1 D-09); derive_current_repo_names uses cwd. They disagree when cwd ≠ Self's project root.
  implication: The asymmetry IS the bug. Outbound stamping is correct; haiku-prompt project-block is wrong. Confirms Defect B2.

## Current Focus

- **hypothesis**: CONFIRMED. Two compounding defects (A: no inbound routing; B2: haiku prompt project-name resolved from cwd not Self info.json). B2 is the actionable root cause.
- **next_action**: CHECKPOINT to user — the fix is non-trivial (touches haiku prompt builder, must keep echo_commune fire AND signoff prompt path in sync since `build_current_context_blocks` is shared). User should choose:
  1. Apply minimal Rust fix for Defect B2 here (small patch to build_current_context_blocks).
  2. Treat as Phase 25 follow-up (Phase 25.3 or similar) and route through `/gsd:plan-phase --gaps`.
  3. Manual fix.

## Resolution

(pending user choice — see Current Focus)
