# releases#276 — the echo brief never transits the agent's drop file (JIT)

Authored by doyle 2026-09-06 ~08:20Z from an OPERATOR DIRECTIVE (same hour, binding):
*"echo communes should have never authored `.claude/<id>-commune.md` in the first
place … echo communes' output should be fed directly to psyche for synthesis into
the agent's live-context and project-context, no file write."* Grounded against
CONTEXT.md before ruling — the grounding is §1; the shape is §2; the gate is §5.
Product change in core → todlando. Source cited at `04e32c8c` (= main `8a21a3b0`
+ one config-only commit; every file cited below is identical on main).

## 0. What is bleeding (measured today, doyle's own funnel)

`spt api io-events doyle --after 0 --json`, block since the 07:48:54Z clear:

- **07:58Z** — Self across-commune authored (arm frame seq 15 carries the
  directive text, clear 17, wake 19). NO authored COMMUNE frame ever appears; the
  next COMMUNE frame is seq 23, `Source: echo-commune`, 07:58:43Z. The Self bytes
  were overwritten unread. The reborn session said so itself (seq 24: *"Wake text
  stale"*).
- **08:09Z** — same shape: arms 64/67, clears 69/72, no authored frame, echo
  frames 81/83 within 25-45s of the clear.
- **08:06:54Z** — the ONE Self commune that survived (seq 43, `<live-context>`
  head), because the "write mid-turn + one more tool call" interim rule got the
  ingest in before the echo.
- Every clear today was followed by an echo frame 25-45s later (frames 1, 23, 57,
  81, 121): the boundary echo lands exactly in the window where an unread Self
  drop sits. Two of three Self communes lost in one hour.
- (07:24Z unrequested clear = an echo FRAME carrying `!!wake!!` in prose; that leg
  was the adapter's and perri shipped the refusal in claude-spt 0.38.3 — both legs
  now refuse any commune with a `Source: ` header. NOT this lane; noted so nobody
  re-fixes it here.)

## 1. Grounding — this is a design gap, not a race to patch

- **CONTEXT.md:208** — the file-drop flow is the AGENT's channel: *"The
  agent/adapter writes `<id>-commune.md` … the daemon watches … ingests, and
  deletes (drop files are daemon-owned single-writer — KNOWN-HAZARDS 6.4)."*
- **KNOWN-HAZARDS §6.4** (:389-393) — the invariant is *the LLM is read-only; only
  the supervisor deletes*. "Single-writer" names ONE writer on the path plus the
  daemon as sole deleter. It never contemplated core itself becoming a SECOND
  writer on the Self's path.
- **`crates/spt-live/src/echo.rs:22-23`** — *"spt-core writes the drop file (it is
  the single writer — 6.4), the mind never does"* — reads "the mind" as the
  Psyche and forgets the Self. **docs/CONTEXT-MEMORY.md:25-33** says the Self
  DOES write that path: boundary communes (`/clear`, `/compact`) are Self-authored
  file drops, and that Self signal is the strong live-context signal (:23). So the
  echo (weak, project-primary, live-conservative) overwrites the strong Self
  capture — the exact inversion of the tiering the asymmetry exists to protect.
- **CONTEXT.md:259** — the echo's purpose is *"to keep the Psyche's durable
  live-context and project-context current."* Nothing in the design requires the
  brief to transit a file; the file was an implementation convenience that
  re-used the ingest.
- **docs-site/src/harness-contract/echo-commune.md:148-159** publishes the
  convenience AS the contract (*"files the summarizer's output as a drop file —
  the exact same channel a hand-written commune uses"*). That page is amended in
  this lane; the adapter-facing half (`[session.echo_commune]` role, keys,
  stdin rule, self-locate) is untouched.

**Ruling:** the drop path `<id>-commune.md` belongs to the Self. The echo's brief
routes straight from the summarizer's stdout into the two-tier context store on
the fire — no file, on any arm.

## 2. Shape (binding)

**A. `crates/spt-live/src/echo.rs`** — `run_echo_commune` loses `commune_dir`;
returns `EchoResult { body }` where `body` is the provenance-stamped brief
(`stamp_provenance(SOURCE_ECHO_COMMUNE, stdout)` — keep the exact header
`Source: echo-commune`; claude-spt 0.38.3 keys its refusal on `Source: `).
Delete `drop_path`, `EchoError::Write`, `retry_access_denied` and its test
(`drop_write_retry_is_bounded_and_denied_only`) — nothing writes. Rewrite the
module doc (:1-25) and `inject.rs:22-23` by replacement (they state the drop).
`record_context_injection(KIND_ECHO_COMMUNE, …)` (:178) stays.

**B. `crates/spt-live/src/ingest.rs`** — factor the body→tiers core out of
`ingest_drops` (:258-365) into ONE shared function, e.g.
`pub fn ingest_body(id, project_id, body, now_ms, window_ms) -> io::Result<BodyIngest>`
returning the `TierWrite`s + the deferred-project fact. `ingest_drops` becomes
read file → `ingest_body` → (preserve pending | delete) exactly as today. Two
callers, one routing — a later tiering change cannot fork.

**C. `crates/spt-daemon/src/lifecycle.rs` `fire_echo` (:1382-1451)**:
1. Drop the `commune_dir` gate (:1383) and the drop-dir resolver arm (:1391-1397).
   The echo no longer needs a dir. It still reads `info.cwd` for the spawn cwd
   (REQ-ECHO-DROP-DIR-RESOLVE rider, keep) and for `project_id`.
2. Extract the cwd→`project_id` computation that `pulse_tick` does at :998-1006
   into one helper both sites call (same owlery-internal exclusion). Do not copy
   the block.
3. After `run_echo_commune` Ok: `ingest_body(id, project_id, &res.body, now,
   window)`. On Ok → `publish_commune_io(id, IO_KIND_COMMUNE, &res.body)`
   (REQ-IO-COMMUNE-EVENT keeps its meaning: the frame is what ENTERED the tier,
   verbatim, echo header first) → `commune_intent::consume_intent(&perch_path)`
   (the releases#96 expectation is "the commune ENTERS THE TIER" — that is now
   this line, not the pulse's file sweep) → `clear_psyche_fault_stamp` as today.
   On Err → `publish_commune_io(id, IO_KIND_COMMUNE_FAIL, reason)` + surface the
   error to the caller so it strikes HARD through the existing `EchoError`
   classification (add an `EchoError::Ingest(io::Error)` arm, `is_timeout() =
   false`; replaces the removed `Write` arm — same "own failure, hard" class).
4. **Deferred-project arm** (empty `project_id`, non-empty `<project-context>`
   slice — psyche-host / engine-room anchors): RULED **loud discard, no file**.
   Emit `ECHO_PROJECT_UNROUTABLE:<id>: project slice discarded (no project
   anchor)` on stderr; the COMMUNE frame published above already carries the
   bytes verbatim, so the funnel is the record. Rationale: an endpoint with no
   project has no project tier to fill; today's "pending forever, bounded no-op
   per tick" file was the smell, and the operator ruled no file write. The
   Self-drop path keeps its F-032 pending behaviour untouched (that IS the
   agent's file). State this arm in the PR body as a behaviour change.
5. Behaviour change to state in PR + docs: an adapter that declares
   `[session.echo_commune]` but no `commune_dir` now echoes (it used to no-op).
   The role-absent once-skip (:1387) stays.

**D. Signoff** (`spt-live/src/signoff.rs:61-95`) — order unchanged (echo → grace
→ recheck → compose). The brief lands in the tiers synchronously inside `echo()`,
so it is durable BEFORE the signoff composes — strictly better than the old
"drop written, ingested on some later tick" for a perch about to go down.

**E. Docs (doc stage, same commit):**
- `docs-site/src/harness-contract/echo-commune.md` — intro :3-18 (*"files it"*,
  *"the drop-file protocol"*, *"spt-core owns … the file-drop"*), §Drop-file
  protocol :148-212, §stdout :214-234, §In one line :236-241. Replace with one
  positive statement: spt-core routes the brief straight into the durable tiers
  on the fire, tagged `Source: echo-commune`, and publishes it as a COMMUNE
  io-event; `commune_dir` is where the AGENT's own commune lands (the
  `spt endpoint list … drop_dir` passage :180-206 stays — it answers "where does
  MY commune go", which is still true), and "ingest deletes" applies to agent
  drops. The bound-kill/intent paragraph :54-71 stays true, reword *"no drop to
  recover"* → *"nothing to route"*. Update the tag comment at :20 to say what the
  page now covers (`stdout routed to the tiers`, not `drop-file protocol`).
- `docs-site/src/harness-contract/manifest.md` — grep `commune_dir` and
  `echo_commune`; any sentence that says the echo writes there is corrected.
- `CONTEXT.md:208` — append one sentence: the echo-commune never writes this
  path (ruled 2026-09-06, releases#276); the drop is the Self's channel and the
  echo's brief routes straight into the tiers. `CONTEXT.md:259` — one sentence
  in the same voice.
- `docs/KNOWN-HAZARDS.md` — new entry **6.12 "The echo brief never transits the
  agent's drop path"** (Failure: core wrote its echo to `<id>-commune.md`, the
  Self's boundary commune was overwritten unread — 2 of 3 lost in one hour,
  releases#276; Invariant: one writer on the Self's drop path, the echo routes
  direct; spt-core mapping: `fire_echo` → `ingest_body`, no write) + the table
  row at :1029.
- `docs/CONTEXT-MEMORY.md` — no rule changes; check :41 (*"how to write the
  drop file"*) still reads right — it does (adapters write the AGENT's drop).

**F. Traceability (`traceable-reqs.toml`, first):**
- Mint `REQ-ECHO-BRIEF-DIRECT-ROUTE` — *"The echo-commune brief routes straight
  from the summarizer into the two-tier store; it never transits the agent's
  `<id>-commune.md` drop path"* — stages `doc`, `impl`, `unit`, `int`.
- Mint `REQ-HAZARD-ECHO-NEVER-WRITES-AGENT-DROP` (6.12) — stages `impl`, `unit`.
- `REQ-ECHO-DROP-DIR-RESOLVE` (:2303-2305): rewrite its comment — the write-retry
  rider is RETIRED with the write; the resolver routing table stays on the ingest
  leg + `psyche_drop_file`; the spawn-cwd rider stays on `fire_echo`. Re-home or
  delete the two lifecycle unit rows that pin the ECHO arm of the resolver
  (:3342 relative-dir-resolved-for-echo, :3396 relative-no-cwd-skips-echo) —
  the ingest leg's equivalents remain the evidence.
- Keep `"` out of titles (registry is TOML basic strings; exit 2 = nothing
  checked).

**G. Tests to write / repin (all in this lane — they are the product's own):**
- `echo.rs` unit `echo_consumes_history_and_writes_commune` → repin: body starts
  with `Source: echo-commune\n`, contains both history records, and the tempdir
  the OLD test wrote into is gone from the signature.
- NEW hazard unit (`[unit->REQ-HAZARD-ECHO-NEVER-WRITES-AGENT-DROP]`), in the
  lifecycle test harness with `live_manifest_with_echo` + the PASSTHROUGH
  summarizer: pre-place an authored `<id>-commune.md` (Self bytes,
  `<live-context>` head) in the drop dir; fire the echo; assert (a) the file is
  BYTE-IDENTICAL after the fire, (b) the live tier now contains the echo brief
  with the `Source: echo-commune` header, (c) the intent marker is consumed.
  **Mutation proof in the PR body:** restore the old write and (a) goes red.
- NEW int (`[int->REQ-ECHO-BRIEF-DIRECT-ROUTE]`): through a real daemon, the
  COMMUNE io-event for the echo appears with the header, and NO file appears in
  `commune_dir` — `io_events_undriven_kinds_e2e.rs` has the rig shape.
- Repins that read the echo's drop: `crates/spt/tests/contract_e2e.rs:441-447`
  (read the live tier via `ContextStore::live_context_path` or the COMMUNE frame
  instead), `lifecycle.rs:3383-3384` (same), the comment at `lifecycle.rs:2219`
  and the `daemon_e2e.rs:132` fixture comment. `lifecycle.rs:2394` (echo role
  FAILS → intent stays) is still true — no change.
- Retraction sweeps STRINGS: grep `docs-site/src`, `docs/`, `CONTEXT.md`,
  `crates/` for `commune drop`, `DROP FILE`, `drop-file`, `writes the brief`,
  `echo wrote`, `files it` — every hit near "echo" is read and either corrected
  or left with a stated reason.

## 3. Out of scope (named so nobody folds it in)

- Anything adapter-side (the 0.38.3 `Source:` refusal, the wake marker, frame
  head-truncation) — perri's, shipped or in flight.
- The boundary-delta-as-message mechanism (CONTEXT.md:261) — unchanged.
- The Self drop's F-032 pending-preserve behaviour — unchanged.
- `!!checkpoint!!` strip points — unchanged (`route_slices` still strips).

## 4. Base + branch

Base off `origin/main` NOW (`8a21a3b0`); branch `fix/276-echo-direct-route`,
worktree `.worktrees/echo-276`, own pool, claim from inside the worktree.
**Land AFTER v0.67.1 is on main** (deployah's land call pending on respin
`34017906638`); rebase onto that main before the PR, thin lane, ff-only. PR body
cites `BigscreenVR/spt-bs-releases#276` (issues live on the releases repo).

## 5. Gate (right-sized)

1. `traceable-reqs check` FIRST after the registry edit (exit 2 = did not parse).
2. Workspace-bins prebuild → `cargo run -p xtask -- check` (docs drift +
   spacerun) → `cargo clippy --workspace --all-targets`.
3. `cargo nextest run -p spt-live -p spt-daemon` + `-p spt` filtered to
   `contract_e2e|psyche_download|commune_two_cwd|io_events|commune_io_events`.
4. `mdbook build docs-site` (the amended page must still build; links resolve).
5. Cross-OS: Windows first (this box), then the same legs on kitsubito — the
   summarizer fixtures are `sort`/`cat` and the tier store is git-backed; both
   OS have bitten this area before.
6. Report: exit FILES read, not the harness notification; pick-audit 1/1 vs the
   branch tip at land.
