Summary
The render-gate persistent mirror (SessionScreen, Spec 1313 round-2) is seeded on adopt/reconnect from a 1 MiB-capped replay tail. For a long-lived alt-screen agent whose current frame started > 1 MiB back in the byte stream, the mirror is born torn (missing the alt-screen enter / composer marker / bounding rule) and classifies BUSY. An unattended, idle adopted agent that then emits zero further output has no repaint to heal the frame, so its mailbox can stay held until its next repaint.
This is a pre-existing residual, not a round-2 regression — pre-round-2 the gate classified ringBuffer.getAll().join('\n'), and that ring was seeded from the same capped replay, so the adopt-path tear existed identically before. Round-2 strictly fixes the live-session tear (the common outage) and leaves this adopt edge where main already had it.
It is fail-safe: torn → HOLD, never misdeliver. It self-heals on any post-adopt output or any viewer attach (the WebSocket connect resize nudge triggers a repaint). Mail is delayed, not lost.
Reachability is non-trivial in this workflow because a Tower restart is routine (every pnpm -w run local-install restarts Tower → every active session is adopted via the reconcile path).
Verified code locations (spir-1313 branch / PR #1330)
packages/codev/src/agent-farm/servers/tower-terminals.ts:775 (reconcile) and :1034 (reconnect) — both call capRingSeed(await client.waitForReplay(), …) before attachShellper.
capRingSeed (tower-terminals.ts:40-44) — returns the last RING_SEED_MAX_BYTES (= 1024*1024, 1 MiB) suffix when replay exceeds it.
packages/codev/src/terminal/pty-session.ts:227-231 (attachShellper) — seeds the mirror (feedGateScreen(replay)) with the identical capped bytes the ring gets (ringBuffer.pushData(replay)).
- Shellper retains up to
REPLAY_PAYLOAD_MAX = 8*1024*1024 (8 MiB) of replay (shellper-protocol.ts:40), so the uncapped replay is available.
Fix options
Option B (recommended, low blast radius). Seed the mirror from the uncapped replay (≤ 8 MiB) while the ring stays 1 MiB-capped. Token-safe: the gate change token is ringBuffer.bytesWritten, which stays monotone; the mirror is a constant offset ahead of it at seed time, which does not affect change-detection (both advance by the same live data afterward). No PTY write. Raises the tear threshold from "frame-start > 1 MiB back" to "> 8 MiB back", which covers essentially all real alt-screen frames. Cost: relaxes the current "same bytes to ring and mirror" seed invariant (needs a clear comment); one-time larger parse on adopt. Residual: a frame whose start is > 8 MiB back still tears (negligible for alt-screen apps).
Option C (total guarantee, heavier). Tower-initiated repaint nudge (resize / Ctrl-L) on every adopt/reconnect. Guarantees a coherent mirror, but writes every adopted PTY — blast radius, and a deliberate PTY write on the no-force path.
Acceptance
- Adopt/reconnect of a long-lived alt-screen session yields a coherent gate mirror for any frame whose start is within the retained replay (≤ 8 MiB with Option B).
- Regression test drives a > 1 MiB replay through the real
capRingSeed → attachShellper seed path and asserts the mirror classifies CLEAN (post-fix) where it currently HOLDs.
- The "same bytes to both" seed invariant comment in
pty-session.ts is updated to reflect the intentional mirror/ring seed divergence.
Context
Deferred from PR #1330 (Spec 1313 round-2) per architect decision — the round-2 PR is a verified strict improvement and this residual is pre-existing + fail-safe, so it ships separately rather than expanding the merge-ready, correctness-sensitive delivery PR. Found by Codex round-2 CMAP (REQUEST_CHANGES) and Claude round-2 CMAP (non-blocking obs-c) — same finding, opposite severity calls; architect adjudicated pre-existing/fail-safe after direct code verification.
Summary
The render-gate persistent mirror (
SessionScreen, Spec 1313 round-2) is seeded on adopt/reconnect from a 1 MiB-capped replay tail. For a long-lived alt-screen agent whose current frame started > 1 MiB back in the byte stream, the mirror is born torn (missing the alt-screen enter / composer marker / bounding rule) and classifies BUSY. An unattended, idle adopted agent that then emits zero further output has no repaint to heal the frame, so its mailbox can stay held until its next repaint.This is a pre-existing residual, not a round-2 regression — pre-round-2 the gate classified
ringBuffer.getAll().join('\n'), and that ring was seeded from the same capped replay, so the adopt-path tear existed identically before. Round-2 strictly fixes the live-session tear (the common outage) and leaves this adopt edge wheremainalready had it.It is fail-safe: torn → HOLD, never misdeliver. It self-heals on any post-adopt output or any viewer attach (the WebSocket connect resize nudge triggers a repaint). Mail is delayed, not lost.
Reachability is non-trivial in this workflow because a Tower restart is routine (every
pnpm -w run local-installrestarts Tower → every active session is adopted via the reconcile path).Verified code locations (spir-1313 branch / PR #1330)
packages/codev/src/agent-farm/servers/tower-terminals.ts:775(reconcile) and:1034(reconnect) — both callcapRingSeed(await client.waitForReplay(), …)beforeattachShellper.capRingSeed(tower-terminals.ts:40-44) — returns the lastRING_SEED_MAX_BYTES(= 1024*1024, 1 MiB) suffix when replay exceeds it.packages/codev/src/terminal/pty-session.ts:227-231(attachShellper) — seeds the mirror (feedGateScreen(replay)) with the identical capped bytes the ring gets (ringBuffer.pushData(replay)).REPLAY_PAYLOAD_MAX = 8*1024*1024(8 MiB) of replay (shellper-protocol.ts:40), so the uncapped replay is available.Fix options
Option B (recommended, low blast radius). Seed the mirror from the uncapped replay (≤ 8 MiB) while the ring stays 1 MiB-capped. Token-safe: the gate change token is
ringBuffer.bytesWritten, which stays monotone; the mirror is a constant offset ahead of it at seed time, which does not affect change-detection (both advance by the same livedataafterward). No PTY write. Raises the tear threshold from "frame-start > 1 MiB back" to "> 8 MiB back", which covers essentially all real alt-screen frames. Cost: relaxes the current "same bytes to ring and mirror" seed invariant (needs a clear comment); one-time larger parse on adopt. Residual: a frame whose start is > 8 MiB back still tears (negligible for alt-screen apps).Option C (total guarantee, heavier). Tower-initiated repaint nudge (resize / Ctrl-L) on every adopt/reconnect. Guarantees a coherent mirror, but writes every adopted PTY — blast radius, and a deliberate PTY write on the no-force path.
Acceptance
capRingSeed→attachShellperseed path and asserts the mirror classifies CLEAN (post-fix) where it currently HOLDs.pty-session.tsis updated to reflect the intentional mirror/ring seed divergence.Context
Deferred from PR #1330 (Spec 1313 round-2) per architect decision — the round-2 PR is a verified strict improvement and this residual is pre-existing + fail-safe, so it ships separately rather than expanding the merge-ready, correctness-sensitive delivery PR. Found by Codex round-2 CMAP (REQUEST_CHANGES) and Claude round-2 CMAP (non-blocking obs-c) — same finding, opposite severity calls; architect adjudicated pre-existing/fail-safe after direct code verification.