Skip to content

Render gate: adopt/reconnect mirror seed capped at 1 MiB → idle adopted alt-screen agent can hold mail until next repaint #1361

Description

@mohidmakhdoomi

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 capRingSeedattachShellper 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerArea: Tower server / agent farm CLI

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions