# Side-Effects Review — compaction-recovery preamble tightening

**Version / slug:** `0.28.66`
**Date:** `2026-04-20`
**Author:** `echo`
**Second-pass reviewer:** `required — compaction / session-lifecycle surface`

## Summary of the change

Rewrites the `COMPACTION_RESUME_PREAMBLE` string in `src/messaging/shared/compactionResumePayload.ts` and the mirror instruction in `prepareInjectionText`'s over-threshold branch. No code-flow change, no new helper, no new decision point — the change is entirely in the text of the instruction a compaction-recovered agent reads on wake-up. Fixes two empirically observed failure modes on topic 6795 (2026-04-20): (a) agents self-narrating compaction as "I lost track / got lost / got confused" instead of a calm "paused for context compaction, now resumed"; (b) agents reconstructing a generic status-summary + re-offering options the user had already delegated back (the "Your call → Your call" ping-pong). Tests added in `tests/unit/compactionResumePayload.test.ts` pin the two invariants on both the inline and file-reference branches.

## Decision-point inventory

- `COMPACTION_RESUME_PREAMBLE` (text fed to recovered agent) — **modify** — tightens instruction content; no change to when or how the preamble is emitted.
- `prepareInjectionText` over-threshold branch (text fed to recovered agent when payload > 500 chars) — **modify** — same tightening, applied to the file-reference variant so both branches give the agent the same guardrails.
- No runtime gate, filter, or authority is added, removed, or modified. The recovered agent remains the only authority; this change is pure input to it.

---

## 1. Over-block

**What legitimate inputs does this change reject that it shouldn't?**

No block/allow surface — over-block not applicable. The change is instruction text. The recovered agent may choose to phrase its compaction acknowledgment in a way that still uses one of the forbidden phrases ("lost track," "got lost," "got confused") — but the preamble can only ask it not to; there is no downstream check that rejects or rewrites its output.

---

## 2. Under-block

**What failure modes does this still miss?**

No block/allow surface — under-block not applicable. Known residual risks:
- Agents may invent new alarming phrasings not covered by the explicit prohibitions (e.g. "I drew a blank," "I lost the thread"). The preamble addresses this by also prescribing the positive phrasing ("paused for context compaction and has now resumed"), but compliance is still LLM-discretion.
- Agents may still reconstruct status summaries when the user's last message is genuinely ambiguous (not a directive, not a question). The preamble cannot solve ambiguity; it only forbids summary-reflex when the user's last message was a delegated decision.

---

## 3. Level-of-abstraction fit

**Is this at the right layer?**

Yes. The preamble is the only layer where this content can live — it is the one piece of text guaranteed to be in the recovered agent's context on wake-up. Alternatives considered and rejected:
- **Detector on the recovered agent's outbound message** (match "lost track" and rewrite): this is exactly the brittle-detector-with-authority anti-pattern the signal-vs-authority doctrine forbids. Also, rewriting the agent's message would distort its voice and break tone-gate compatibility.
- **Coaching via MEMORY.md**: memory is not guaranteed loaded early enough on recovery, and this fix must work for first-run agents too.
- **Post-compaction reflection hook**: fires after the first response, too late to prevent the tone failure the user sees.

The preamble is the correct layer: it's instruction-to-the-authority, not authority-itself.

---

## 4. Signal vs authority compliance

**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)

**Does this change hold blocking authority with brittle logic?**

- [x] **No — this change has no block/allow surface.**
- [ ] No — this change produces a signal consumed by an existing smart gate.
- [ ] Yes — but the logic is a smart gate with full conversational context.
- [ ] ⚠️ Yes, with brittle logic.

This change is text fed to an LLM-backed authority (the recovered agent). It does not filter, reject, or gate any input or output. The authority structure is unchanged. Compliance is trivially satisfied: detectors remain detectors, authorities remain authorities, and the preamble is an instruction INPUT to an existing authority.

---

## 5. Interactions

- **Shadowing:** No. The preamble is the only text ever injected in this code path; nothing else injects into the same byte-range.
- **Double-fire:** No. `buildCompactionResumePayload` is called once per compaction recovery event; `prepareInjectionText` is called once per payload. Both changes alter static text, not call-count.
- **Races:** No. The function is a pure string builder. No shared mutable state.
- **Feedback loops:** Indirect only. The recovered agent's response (shaped by the preamble) is seen by the user and becomes part of the next topic context, but this is normal conversational loop and not a self-reinforcing system.
- **Adjacent compaction infrastructure:** `findLastRealMessage` (0.28.51), `isSystemOrProxyMessage` (0.28.51), and `formatContextForSession` (0.28.52) are all unchanged. The preamble text is independent of which messages are selected; this change only affects how the agent is instructed to respond to them.
- **Tone gate interaction:** The preamble is not itself sent through the tone gate (it's injected into the agent's context, not emitted as a user-facing message). The agent's RESPONSE to the preamble is sent through the tone gate, as normal. The forbidden phrases ("lost track," etc.) in the preamble are inside a prohibition instruction ("Do NOT say you..."); the tone gate parses agent output, not its input context, so no collision.

---

## 6. External surfaces

- **Other agents on the same machine:** No effect. Each agent's recovery is independent.
- **Other users of the install base:** Yes — every instar user running 0.28.66+ will see recovered agents produce a different tone on compaction. This is the intended improvement. No breaking change to message format.
- **External systems (Telegram, Slack, GitHub):** No. The output format is still a single text message in the agent's voice.
- **Persistent state:** None. Text change only.
- **Timing / runtime conditions:** No. Purely static text; no new async, no new timeouts.
- **File-reference branch:** when payload > 500 chars, a temp file path appears in the recovered agent's context as before. File format unchanged; only the surrounding instruction text is tightened.

---

## 7. Rollback cost

**If this turns out wrong in production, what's the back-out?**

- **Hot-fix release:** revert the single file (`src/messaging/shared/compactionResumePayload.ts`) + the added tests. Ship as 0.28.67. No code-path change to untangle.
- **Data migration:** None. No persistent state touched.
- **Agent state repair:** None. Live agents pick up the new preamble on next compaction event; if reverted, they pick up the old one on the one after. No stuck-state risk.
- **User visibility during rollback:** A single recovery event in flight might land on mixed wording. Zero-downtime revert.

Total rollback cost: one revert commit, one patch release. Lowest possible risk tier.

---

## Conclusion

The change is low-risk, surgical, and addresses two specific user-observed failures with the minimum possible surface area — instruction text in one file, plus a mirrored update in the same file's over-threshold branch. No decision points are added, no gate logic is modified, no authority is reshaped. Signal-vs-authority compliance is trivially satisfied (no block/allow surface). Rollback is one revert.

One residual risk: LLM compliance with prescribed tone language is not guaranteed, only strongly directed. If telemetry shows the new preamble still produces alarming narrations in practice, the next iteration would be to fold compaction-recovery outputs through a dedicated lightweight tone-checker before emit — but that is a future change, not a reason to block this one.

Clear to ship as 0.28.66.

---

## Second-pass review (if required)

**Reviewer:** general-purpose subagent (independent read, did not see the author's reasoning)
**Independent read of the artifact: concur**

Concerns raised (non-blocking):

- Test strength: initial suite asserted magic-string presence only, not order. Addressed in this pass by adding a test that pins "calm acknowledgment appears before respond-to-most-recent-message" in `COMPACTION_RESUME_PREAMBLE` — the order was itself a core part of the failure mode.
- Prompt-injection surface: the user's last message was already concatenated into the recovered agent's context via `formatInlineHistory` (pre-existing). The new preamble's directive to "act on a delegated decision" slightly raises the stakes of a hostile last message. Residual risk, not a new surface. The recovered agent remains the authority and retains judgment — same trust boundary as any other user-input-in-context scenario. Accepted as pre-existing, not introduced.
- i18n / backward-compat: instar is English-only today; compaction payloads are transient (not persisted across versions), so no in-flight compatibility concern. Noted for completeness.

Reviewer verdict: clear to ship.

---

## Evidence pointers

- Targeted unit tests: `tests/unit/compactionResumePayload.test.ts` — 17 tests, 42 combined with `isSystemOrProxyMessage.test.ts`, all green in worktree.
- Failure-mode source: topic 6795 screenshots 2026-04-20 (Mew / session-robustness at 12:12 PM; Bob / instar-agent-robustness at 12:04 PM).
- Prior art in this line: 0.28.51 (classifier fix), 0.28.52 (rich context payload). This is the third tightening in the compaction-recovery lineage.
