You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
#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.
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:
set the row's escalated flag (existing column) so the dashboard/SSE surface it;
return a result that carries verified: false.
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.
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.
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
Control test (must fail on current main, pass after): fake session whose mirror never shows the header → the message is written exactly once; the row ends delivered + escalated, never held; a second drainer tick writes nothing.
Immediate-responder simulation: mirror shows the header, then scrolls it out before verification samples → written once, delivered (verified or unverified, but never re-written).
Field verification before close (architect-driven, post-release): long --file send to a builder that responds immediately → exactly one copy in its transcript.
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).
Problem (3.3.2 regression — hotfix)
#1583 (shannon, 2026-09-02): one
afx send --filemessage 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 returnshold('busy').busyholds are deliberately excluded from escalation streaks (isClassifierStuck), so the loop is silent to the owner.afx interruptworsens 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.
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 morewatch-style sample after a short delay, ≤ ~1.5s total) to accommodate slow renderers; if still unconfirmed, mark the rowdeliveredand:WARN(delivered-unverified, row id, to_agent, terminal id, header needle length);escalatedflag (existing column) so the dashboard/SSE surface it;verified: false.verified(boolean) additively onto the immediate-delivery send response (tower-routes.tshandleSend,sdk/tower-client.tstyping,commands/send.tsprints e.g.[ok] Message delivered (unverified — header not seen on the terminal)). Additive field; older clients unaffected.hold(...)reachable aftersubmitMessagePacedresolves successfully; only pre-write prechecks may hold.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
delivered+escalated, neverheld; a second drainer tick writes nothing.delivered,verified: true, not escalated.verified:falsesurfaces as the unverified wording;verified:true/absent prints today's line.--filesend to a builder that responds immediately → exactly one copy in its transcript.Release
Hotfix release 3.3.3 to
@latestimmediately 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).