Skip to content

tower: HOTFIX — echo-verify re-write loop re-injects a delivered message unboundedly (3.3.2 regression from #1573; zero re-writes after a completed write) #1584

Description

@waleedkadous

Problem (3.3.2 regression — hotfix)

#1583 (shannon, 2026-09-02): one afx send --file message was re-injected into a builder's prompt dozens of times, byte-identical; a separate architect→architect message was delivered twice. Both on 3.3.2. Root cause confirmed at current main:

  • mailbox-delivery.ts:694: after the gated write has completed (every byte + Enter accepted by the PTY), a failed echo-verification returns hold('busy').
  • The next clean-prompt pass re-runs the row with a full re-write. No per-row attempt cap exists anywhere in the module.
  • busy holds are deliberately excluded from escalation streaks (isClassifierStuck), so the loop is silent to the owner.
  • Field trigger is the common case, not the agy corner tower: bound redelivery when echo verification never confirms (#1573 residual) #1578 predicted: a recipient that starts responding immediately scrolls the header out of the sampled screen (or a long write evicts the pre-write copy from the mirror), so the occurrence count does not increase, verification fails, the message is re-injected, the recipient responds again — self-sustaining. afx interrupt worsens it (each interrupt = fresh clean prompt = another pass).

Verify-then-rewrite was the dangerous half of #1573. The honest half (settle-before-write, 48KB cap, "tell the sender when we could not verify") stays.

Fix (prescribed — owner-approved design 2026-09-02: ZERO re-writes)

A row whose write completed is at-least-once delivered and must never be written again.

  1. At the verify site (mailbox-delivery.ts ~694): on verification failure after a completed write, do not hold. Allow one bounded additional verify window (no bytes written — e.g. one more watch-style sample after a short delay, ≤ ~1.5s total) to accommodate slow renderers; if still unconfirmed, mark the row delivered and:
    • log WARN (delivered-unverified, row id, to_agent, terminal id, header needle length);
    • set the row's escalated flag (existing column) so the dashboard/SSE surface it;
    • return a result that carries verified: false.
  2. Thread verified (boolean) additively onto the immediate-delivery send response (tower-routes.ts handleSend, sdk/tower-client.ts typing, commands/send.ts prints e.g. [ok] Message delivered (unverified — header not seen on the terminal)). Additive field; older clients unaffected.
  3. Remove/neutralize any path by which a completed-write row can be re-written: audit every hold(...) reachable after submitMessagePaced resolves successfully; only pre-write prechecks may hold.
  4. isClassifierStuck / recordStreak: no change needed once (1) lands (no busy-hold after a completed write exists anymore) — confirm with a test rather than assume.

Out of scope: smarter verification (scrolled-header tolerance), attempt columns/migrations, #1567 truncation class, agy measurement. Those remain under #1578 (retitle it to "smarter verification" follow-up, keep open).

Testing

Release

Hotfix release 3.3.3 to @latest immediately on merge. Note in the PR that this supersedes the residual recorded in #1578.

Refs: #1583 (field report), #1578 (residual this replaces), #1573 / PR #1577 (regression source), #1564/#1521 (the verification's original purpose).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/towerArea: Tower server / agent farm CLIbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions