[Spec 1313] afx send: mailbox-first delivery (never force-inject) - #1330
[Spec 1313] afx send: mailbox-first delivery (never force-inject)#1330mohidmakhdoomi wants to merge 162 commits into
Conversation
…th conflict) Bring the branch current with main (was 83 behind; PR #1330 was CONFLICTING) before folding in the architect-directed render-gate over-ceiling change. Two conflicts, both in the send path; resolved to preserve BOTH specs: - servers/tower-routes.ts — keep Spec 1313's mailbox-first delivery as the normal path, and preserve Spec 1273's per-terminal submission lock (submitToSession) on BOTH explicit human-bypass paths. The `escape` path auto-merged to it; the `interrupt` path now delivers through it too (origin/main serialized interrupt via the old idle/else-branch, so this is not a regression). The bypass paths do NOT route through the mailbox's per-agent serializer, so they need their own anti-fusion lock. - __tests__/tower-routes.test.ts — keep the gate-path assertion for the un-split-write / separate-Enter property (supersedes 1273's immediate-path variant of the same test); also assert >1 write. Verified: tsc --noEmit clean; tower-routes + spec-1273-submission-lock suites green (104 tests). Full suite runs after the render-gate change lands.
…old; add ringToken verdict memo Architect+user-directed follow-up (folds into PR #1330). Over-ceiling removal (Option 1 — render the whole ring at any size): The Round-4 over-ceiling hold rejected any ring > RENDER_CEILING_UNITS (8M units) UNRENDERED. But `partial` is unbounded (#1047), so a long-lived claude/codex/agy terminal crosses 8M in NORMAL use — a live ~14M-unit empty-composer architect terminal was stuck, its mail undeliverable until relaunch. A size cap that HOLDS is a permanent outage for exactly the busiest agents. Whole-ring render is already correct at any size, so removing the short-circuit just extends correct classification (no slice -> no new false-clean). Removed RENDER_CEILING_UNITS, the 'over-ceiling' GateVerdict.detail member, and the over-ceiling arm of the liveness escalation. An unclassifiable huge ring still holds + escalates via no-region-end / no-composer-marker, so it is never a silent loss. Verdict memo (the Round-4 deferred CPU follow-up, now implemented): The 1.5s backstop re-rendered every held agent's whole ring each tick; for a static ring that is waste. CachedVerdict{token,verdict} is owned + bounded by MailboxDrainer (pruned to the held-agent set each tick), keyed on the same ringToken the gate->write TOCTOU re-validation already trusts. A memo hit does no await, so the post-classify token re-check passes trivially (honors the intended interaction). Confined to the backstop tick — the fast scheduleDrain trigger always re-classifies, since it fires precisely because the ring changed. OOM open question (raised for CMAP): kept NO delivery-blocking cap — a cap that HOLDS just reintroduces the outage under a bigger number. Mitigated by the memo; the root cause (unbounded partial) is #1047, a separate future project. Accepted residual risk documented in the render-gate module header. Tests: render-gate.test.ts — over-ceiling->busy replaced with a >8M-unit ring that renders WHOLE -> CLEAN; perf test decoupled from the removed const. send-delivery .test.ts — +4 memo tests (static -> classify once; re-classify after token change; memo-hit-on-clean delivers; memo pruned when mail clears). Review doc: Round 5 section + Technical Debt updates. Full unit suite: 4259 pass / 48 skip / 0 fail. tsc clean.
…us fields + round-2 record) Fresh 3-way Review consult (round 2, post-rewrite): Gemini APPROVE, Claude APPROVE, Codex COMMENT (non-blocking). Addressed Codex's hygiene comments: - spec Status draft -> specified; plan Status draft -> approved (both already carry approval frontmatter). - Recorded Review round 2 in the review doc's Consultation Feedback + Iteration Summary. PR #1330 body refreshed separately (stale 4162->~4267 test count, agy "deferred"-> live-verified, + the missing post-gate hardening arc). The spec-1280 T16 re-scope stays flagged for its owner; transient consultation artifacts remain deliberately untracked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s) + comment/test cleanups Architect integration review (PR #1330) — Codex REQUEST_CHANGES (HIGH): a dropped PTY write (#1198) was reported `delivered`. WritableSession.write() was typed `void`, so writeMessagePaced resolved on a pure timer and deliverAgentMail called markDelivered unconditionally; the !session.writable precheck is t=0 only, so a socket dying during the paced text->lines->Enter sequence lost the message silently. Fix — thread the boolean end-to-end: - WritableSession.write(): boolean - new drop-aware writeMessagePaced(): Promise<boolean> in message-write.ts (wraps the session, records any dropped write across the whole paced sequence; resolve fires after the Enter, so every write's result is observed) - DeliveryPorts.writeMessage(): boolean | Promise<boolean> - deliverAgentMail holds no-live-pty on a false result instead of markDelivered (memo still invalidated in finally; a genuine reject still propagates) - tests: spec-1313-paced-write-drop.test.ts (BOTH the first write and the delayed Enter/multiline writes) + send-delivery mid-pace-drop hold; the four writeMessage port doubles and the tower-routes gate-session double updated to the boolean contract Cleanups (same review): - delete the vestigial spec-1280 branch-scoped completeness guard (+ its orphaned execFileSync import and PROMPT_BEARING const); 1280 is integrated -> main-resident no-op. Structural manifest validators kept. - rewrite stale SendBuffer/deliverBufferedMessage comments in session-submit.ts to the mailbox-delivery model (also corrected the now-false cron bullet -- Phase 6 removed cron's blind writeMessageToSession -- and the escape/immediate-delivery wording) Deferred (architect-ratified): the gate->write input-echo race stays the tracked Follow-up item; not widened here. Review doc: recorded Review round 3 in Consultation Feedback + Iteration Summary; updated Technical Debt (spec-1280 guard removed; benign partial-write residual) and Follow-up Items. Verify: tsc clean; unit 4275 pass / 48 skip / 0 fail; full build exit 0; delivery e2e 7/7. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This comment was marked as resolved.
This comment was marked as resolved.
…is OPEN, not integrated) New information from the architect: Issue #1280 is OPEN — its status.yaml shows phase_0_instrument in progress, phases 1-10 pending, and phase_1 edits CLAUDE.md/ AGENTS.md. So the T16 manifest-completeness guard in spec-1280-phase-manifest.test.ts is a LIVE guard 1280 pre-positioned in Phase 0, NOT vestigial. Deleting or scoping another active project's guard is wrong. Restore the file to main exactly (git checkout main -- ...), reverting BOTH this session's deletion AND the earlier isProject1280 scoping in one shot. Consequence (intended): T16 now FAILS on this branch — 1313 edits CLAUDE/AGENTS (the Spec 987 hot-tier propagation), which T16 flags for absence from a 1280 manifest. Left failing deliberately to surface the cross-project conflict; escalated to the 1280 owner (waleedkadous) via a PR #1330 comment for guidance. Not scoped/ skipped/deleted; porch check/done deliberately NOT run (it would choke on the failing test). The silent-loss fix (becc6e1) is unaffected. Review doc updated (Deviations, Consultation Feedback round 3, Technical Debt, Follow-up). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Draft spec for afx send mailbox-first delivery (never force-inject), triaged against a 3-way consistency review: cron supersede made unconditionally per-task, --interrupt reframed as deliberate sender bypass reconciled with the no-force-path decision, canonical held-reason tokens, enqueue added to delivery moments, ok:true on held, per-PTY write serialization with per-agent enqueue ordering, two named broadcast events, and area/cross-cutting per label policy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address 3-way spec consultation (Gemini APPROVE, Codex REQUEST_CHANGES, Claude COMMENT — all HIGH confidence): - Add missing ## Expert Consultation section (unanimous ask) - Decision 8: make afx inbox workspace-scope + dismiss authorization explicit; note attention-state visual is plan-level - Decision 6: state supersede keys are cron-only - Test scenarios: add #16 escalation-age-threshold scenario No baked decisions changed. Also seeds the cohort thread file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documents resolution of all 3-way feedback (Codex REQUEST_CHANGES fully addressed; Gemini APPROVE; Claude COMMENT). No baked decisions changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…complete) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9-phase decomposition: mailbox store → rendered-empty gate (claude/codex) → agy profile (blocking) → delivery orchestration + write serialization → fast delivery triggers → cron rerouting → afx inbox + SSE/escalation → dashboard/VSCode indicators → docs + skeleton mirror. Grounded in real architecture (UIs use SSE/overview, not WebSocket; VSCode badge precedent; migration v15). Offers phase-merge knobs for the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address 3-way plan consult (Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE — all HIGH): - Phase 4: client-side send contract (tower-client.ts + commands/send.ts), automated e2e for #1265, pruneTerminal invocation, liveness telemetry - Phase 7: name lib/config.ts loader for escalation-age; liveness surfacing - Phase 1: name lib/config.ts for retention-days - Phase 5: drain-coalescing test - Exec summary WS->SSE; optional Phase 7 split offered No phase scope changed. Includes rebuttal + thread. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address iteration-2 plan consult (Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE — all HIGH; Gemini+Claude verified all iter-1 fixes landed): - Phase 4: dead-session targeting seam (agent-registry fallback in resolveTarget + handleSend restructure to persist no-live-pty instead of 404); --all honest reporting (sendToAll); fix tower-client shape desc - Phase 2: PtySession app-identity seam (command/args private today) for resolveProfile All Codex points verified against code. No scope change. Includes rebuttal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… layer (v15) Phase 1 of mailbox-first delivery: a durable home for every 'afx send' so nothing is lost to a Tower crash/restart/shutdown. Pure, delivery-decoupled data operations that Phase 4 wires against. - schema.ts: additive mailbox table + 3 indexes in GLOBAL_SCHEMA (agent-addressed via to_agent, not PTY-keyed). - index.ts: migration v15 (GLOBAL_CURRENT_VERSION 14->15) creating the table on existing installs; re-export new row types. - types.ts: DbMailbox row + MailboxStatus/MailboxReason unions. - db/mailbox.ts: repository (enqueue/getById/listHeld/findHeldForAgent/markDelivered/dismiss/supersede/pruneTerminal). State machine enforced (transitions touch only held rows); timestamps injectable for tests. - Tests: mailbox.test.ts (lifecycle, per-agent ordering, crash/reopen recovery) and spec-1313-migration.test.ts (v15; fresh vs migrated shapes converge). Refs #1313. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fied, re-parked at pr gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…is OPEN, not integrated) New information from the architect: Issue #1280 is OPEN — its status.yaml shows phase_0_instrument in progress, phases 1-10 pending, and phase_1 edits CLAUDE.md/ AGENTS.md. So the T16 manifest-completeness guard in spec-1280-phase-manifest.test.ts is a LIVE guard 1280 pre-positioned in Phase 0, NOT vestigial. Deleting or scoping another active project's guard is wrong. Restore the file to main exactly (git checkout main -- ...), reverting BOTH this session's deletion AND the earlier isProject1280 scoping in one shot. Consequence (intended): T16 now FAILS on this branch — 1313 edits CLAUDE/AGENTS (the Spec 987 hot-tier propagation), which T16 flags for absence from a 1280 manifest. Left failing deliberately to surface the cross-project conflict; escalated to the 1280 owner (waleedkadous) via a PR #1330 comment for guidance. Not scoped/ skipped/deleted; porch check/done deliberately NOT run (it would choke on the failing test). The silent-loss fix (becc6e1) is unaffected. Review doc updated (Deviations, Consultation Feedback round 3, Technical Debt, Follow-up). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0 open), conflict escalated Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gin/main (1280 owns these prompt surfaces) Architect change round: Spec 1280 Phase 1 owns and is actively rewriting the two byte-identical prompt-surface files (CLAUDE.md, AGENTS.md), so 1313 must not touch them. Reverted 1313's only edit to each — the +9-line "Send outcomes: delivered vs held" section — via `git checkout origin/main -- CLAUDE.md AGENTS.md`. End state (verified): both files byte-match origin/main (zero diff), byte-identical to each other, section absent from both. Net: the branch changes no prompt-bearing file, so Spec 1280's T16 completeness guard passes (its origin/main...HEAD changed-set is empty). This resolves the T16-vs-1313 collision without touching another active project's guard. No information lost: the delivered/held + `afx inbox` docs remain in the kept `afx` reference (agent-farm.md, canonical) + its skeleton twin + the arch-critical.md hot tier. Only a duplicate was removed from CLAUDE/AGENTS. No source code touched. Kept (per architect): arch.md, arch-critical.md, lessons-learned.md, codev/resources/commands/*.md, and the codev-skeleton twins. Review doc updated (SC11, Deviations, round-3 bullet, new Architect Change Round entry, Technical Debt -> RESOLVED, Follow-up -> DONE); thread appended. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e (verified branch-lag caveat) Correct the review doc and thread: after reverting CLAUDE.md/AGENTS.md to origin/main's tip, T16 passes in the rebased/merged state but stays RED on the un-rebased branch. Verified: T16 uses a three-dot origin/main...HEAD diff (merge-base 3f622fe vs HEAD); the branch is 285 behind and main advanced these two files since that merge-base, so HEAD's now-tip version still differs from the merge-base version and T16 keeps listing them (1 of 4 sub-tests fails). Clears with the maintainer-side rebase already needed for the CONFLICTING PR. The prior commit's flat "T16 passes" wording was imprecise; the afx reply + PR comment to the architect already carried this nuance — the committed docs now match. CLAUDE.md/AGENTS.md untouched by this commit (still byte-identical to origin/main and to each other). Docs-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reconciliation plan Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…the mailbox
Rebase reconciliation: main's send-path advance (Spec 1307 `afx send --delay`)
is a competing rewrite of the same path 1313 rewrote. Re-home --delay onto the
mailbox so both survive, preserving 1307's five load-bearing contract points.
- handleSend: validate deliverAfter (also at the public route, not just the CLI),
refuse escape+delay, and on the delay branch schedule a due-time callback that
ENQUEUES to the durable mailbox + triggers the render-gated drain. The delayed
row is created only WHEN the timer fires, so (1) it is younger than anything
already queued -> a delayed send never overtakes an already-held message (the
drain delivers oldest-first, created_at ASC), and (5) a pre-due send stays
in-memory only, dropped on restart per 1307. Delayed --interrupt keeps the
explicit gate-bypass. Restore the Spec 1273 escape-wrap (submitToSession +
writeEscapeToSession), flattened away by the rebase.
- delayed-send.ts: survivor file; rewrite doc-comments that referenced deleted
code (SendBuffer, main's deliverOrBuffer) to the mailbox delivery path.
- Tests: drop the dead SendBuffer `deferred`/queued bucket (send.test.ts -> held
model); de-SendBuffer the 1307 test + fix its deliverOrBuffer / non-existent
interleave-test references; add the ordering regression guard in
send-delivery.test.ts ('delayed sends never overtake already-queued mail') that
drives the real timer -> fire-time enqueue -> drain chain and asserts /clear
drains before a delayed /arch-init.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…87 two-tier fact to cold The rebase UNION of main's #1189 server/client-isolation fact and 1313's mailbox-first fact pushed arch-critical.md to 11 critical facts (Spec 987 cap is 10), failing hot-tier.test.ts. Both new facts are behavior-changers and stay hot; demote the weakest existing entry — 'Governance docs are two-tier (Spec 987)' — to cold. Its full treatment already lives in arch.md's Spec 987 section and the hot file's own header states the cap/displacement discipline, so no information is lost. Now 10 facts / 32 lines. CLAUDE.md/AGENTS.md untouched (Spec 1280 owns them). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ied, 2 failures fixed, suite green Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f3600c9 to
46232e1
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
…ed-command ghost cursor cell Live PR-testing found afx sends to an IDLE agent stranding held(busy) while the composer was empty. Root cause (architect byte-level verified): claude 2.1.220 paints a suggested-command ghost into the idle composer when its own last reply mentioned a runnable command; the ghost's first char doubles as the software block cursor, rendered SGR-7 INVERSE at normal intensity over a SGR-2-dim tail. The dim rule skipped the ghost body but counted the lone inverse cursor cell -> user-text/busy forever on an idle terminal (fail-safe becomes fail-forever for the exact unattended agent afx send exists to wake). classifyScreen now exempts exactly that cell via isGhostCursorCell: inverse + non-dim + at the headless cursor + a dim/empty tail on its row (the measured ghost signature). Deliberately NOT a blanket inverse skip -- an inverse selection over a real draft fails the dim-tail test and keeps every other cell counted, and a real draft never trips it because claude never inverse-renders typed text (the block cursor rests on trailing whitespace, skipped as whitespace). Verified cross-app against live terminals: codex renders its own ghost wholly dim (already clean via the dim rule, never affected); the exemption is profile-agnostic so it would handle a codex inverse-ghost identically. Regression coverage: claude-ghost-suggestion-empty.replay.bin.gz (139x63) wired as a fixture -> CLEAN post-fix (busy/user-text(1) pre-fix), plus four synthetic branch tests (ghost->clean; inverse-cursor-over-real-text->busy; real-draft- inverse-trailing->busy; codex-signature->clean). All 17 existing fixtures classify unchanged; render-gate suite 39/39; full unit suite 4540 pass / 48 skip / 0 fail; send-integration e2e 7/7. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd + arch.md §7 Review doc: new Consultation Feedback subsection (Architect Live-Test Round 2026-08-06) documenting the finding, the Option-C ghost-signature fix, the live cross-app check (codex already clean / real claude draft holds), and regression coverage; a Consultation Iteration Summary row; a Technical Debt entry for the accepted 1-char-draft residual + the deferred liveness-net diagnostic; an Architecture Updates note; refreshed test-count (4540) and fixture-count (5) metrics. arch.md §7: one-sentence pointer that the classifier exempts claude's ghost cursor cell (isGhostCursorCell), so an idle ghost classifies CLEAN and delivers. No hot-tier change (the mailbox-first invariant is unchanged); CLAUDE/AGENTS untouched (Spec 1280 owns those two prompt surfaces). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iteration Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This comment was marked as resolved.
This comment was marked as resolved.
…dim tail (Codex CMAP) CMAP round on the ghost-cursor fix: Gemini APPROVE, Claude APPROVE, Codex REQUEST_CHANGES (architect-verified + agreed). isGhostCursorCell granted the exemption on a dim-OR-EMPTY tail, so a 1-char draft with the cursor parked on its only char (an inverse cell with an empty tail) false-CLEANed -- a real no-new-corruption-vector / fail-toward-hold violation, not the acceptable residual it had been documented as. Tighten to require POSITIVE ghost evidence: at least one dim, non-whitespace, non-chrome cell must follow on the cursor's row (the SGR-2 suggestion body). An empty or whitespace-only tail now returns false -> the cell is counted -> busy. The real ghost is unaffected (its dim command body is 23 cells). Added the empty-tail regression test (inverse non-dim cursor, empty tail -> busy/user-text); render-gate suite 40/40, all fixtures unchanged, real ghost fixture still CLEAN; full unit suite 4541 pass / 48 skip / 0 fail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…odex CMAP round) Review doc: Technical Debt entry reframed from "accepted residual" to CLOSED (the 1-char-draft false-clean was a spec violation, now fixed via positive-dim-evidence); new CMAP-round bullet (Gemini/Claude APPROVE, Codex RC -> tightening); metrics 4540->4541. arch.md §7: "non-empty dim tail" as positive ghost evidence; empty-tail 1-char draft stays busy (fail-toward-hold). Thread: CMAP round + tightening. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This comment was marked as resolved.
This comment was marked as resolved.
…ed ring via persistent bounded mirror (round 2) Round-1's whole-ring rewrite reintroduced the delivery outage one layer down: #1205 caps the RingBuffer newline-free partial at 2 MiB and an alt-screen frame is one giant partial, so a busy long-lived agent's frame crossing the cap handed the gate a torn front -> permanent false-busy hold. Replace whole-ring re-rendering with a persistent bounded @xterm/headless mirror per session (SessionScreen), fed incrementally at PtySession's output chokepoint; the gate reads its bounded viewport. Cap irrelevant, live-ring tear gone, classify O(viewport), #1047 whole-render OOM residual closed. Monotone RingBuffer.bytesWritten replaces the trim-aliasing (currentSeq, partialBytes) change token; bigRing/backoff machinery retired. Production-path tests feed both real >2 MiB captures through a real RingBuffer AND a real SessionScreen (ring -> busy/torn, mirror -> clean). Option-A hygiene (architect direction; adopt-path residual deferred to #1361): - adopt-path regression (pty-session-attach): real capRingSeed(>1MiB) -> attachShellper seeds a torn mirror -> asserts fail-safe HOLD (busy) for both real captures - SessionScreen.dispose() settles pending + read() early-returns when disposed - qualify the "from birth"/"tear is gone" code comments (live path vs adopt seed; #1361) - export capRingSeed / RING_SEED_MAX_BYTES for the adopt-path test 3-way CMAP: Gemini APPROVE/HIGH, Claude APPROVE/HIGH, Codex REQUEST_CHANGES/HIGH (adopt-path torn seed -- verified pre-existing, not a round-2 regression; #1361). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ review; adopt-path caveat (#1361) arch.md §7: replace the whole-ring / over-ceiling / backoff / OOM description with the persistent bounded SessionScreen mirror; qualify "from birth" / "tear is gone" for the live path and note the adopt/reconnect seed is capRingSeed-capped (1 MiB) -> can be born torn -> fail-safe HOLD, self-heals on repaint/viewer (deferred #1361). Review doc: round-2 section (blocker, fix, CMAP verdicts, adjudication, Option-A hygiene); #1047 debt CLOSED; #1361 follow-up; metrics (4551 pass, e2e 7/7). Thread: round-2 CMAP + Option-A execution. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…at pr gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n (T16) Waleed's ruling 2026-08-06. The completeness scan lived in the shared suite and ran a repo diff + git status on every PR; even scoped by [Spec 1280] provenance its uncommitted-file check caught Mohid's #1330 (which had to strip CLAUDE/AGENTS edits to pass CI) — the second cross-project misfire (Spec 1307 was first). What survives: the manifests, the M11 human inspection contract, and the manifest FORMAT checks (four-fields, batch-cap) which read only 1280's manifest dir. Only the CI tripwire is gone. Also cleaned the audit-report manifest row so its path parses. R5 writeup in 1280-retirements.md.
Architect Integration Review — PR #1330 (Spec 1313, mailbox-first delivery)Risk tier: HIGH (~13.3k additions / ~2.2k deletions, 100 files, core Tower/agent-farm/DB-migration/terminal surfaces) → full 3-way CMAP + independent architect verification against source. 3-way CMAP verdicts
The split is entirely on one point: the adopt/reconnect "born-torn" gate-mirror gap (#1361). Everything else is unanimous praise for a correctness-by-construction refactor. The contested point (#1361), adjudicated against sourceCodex: an adopted/reconnected shellper session seeds Architect verification (
Architect adjudication: non-blocking for this PR. Claude concurs ("fail-safe, pre-existing, track don't block"); Codex's concern is legitimate and #1361 should be a required fast-follow, but a pre-existing, fail-safe, self-healing, tracked edge should not block a large corruption-eliminating refactor. Independent architect verification (all confirmed against source)
DecisionHeld at the Architect integration review |
Summary
Replaces
afx send's timer-based, in-memory, force-flushing delivery (SendBuffer, Spec 403) with a mailbox-first pipeline. Every message is persisted toglobal.dbbefore the send returns, and its body is only ever written to a prompt a headless-terminal render-gate proves is empty. Corruption is eliminated by construction — a message can never fuse with a draft, menu, dialog, or wrapper screen — and there is no force path: no timeout, valve, or fallback ever writes onto a non-clean screen. A busy line means a human is present; the response is to escalate visibility (indicator +afx inbox), never to fight for the keyboard.Closes #1313
Changes (9 phases, single PR)
mailboxtable (migration v15, agent-addressed rows); kills silent loss.render-gate.tsclassifies a headless-terminal screen (via@xterm/headless); claude/codex profiles. Idle-gate cost well under the ≤ ~50ms budget.placeholderFgPaletterule (dim/bold couldn't separate idle from draft). Trust dialog classifies not-clean (no blind Enter confirms filesystem trust).handleSendrewrite: persist → serialize → gate → deliver/hold. RetiresSendBufferand every force path. Per-PTY write serialization (write-queue.ts). Response vocabdelivered | held+reason (busy/no-profile/no-live-pty), surfaced client-side (single +--all).handleUserInputchokepoint onPtySession; held mail delivers near-immediately once the line clears.afx inbox+ broadcasts + escalation — CLI (list/show/dismiss, workspace-scoped),overview-changedheld-count SSE,mailbox-escalationvisibility event (never delivery), liveness telemetry.afx inbox, mailbox config; skeleton mirrored; the mailbox-first invariant added to thearch-critical.mdhot tier.Post-gate hardening (folded into this PR)
After the first pr-gate approval, live testing on installed code surfaced real defects; the architect authorized a verify→implement rollback to fold the fixes into this PR across several additional CMAP rounds:
writableprecheck, so the row was marked delivered off the paced-write timer. The dropped-write result is now threaded end-to-end as a boolean; a dropped write holds the row (no-live-pty) — never a false delivery.afx send architectwas alwaysheld(no-profile)(architect sessions had no persistedcommand). Fixed with a restart-safe identity SSOT on the session row (migration v16) + a legacy self-heal that honorsTOWER_ARCHITECT_CMDprecedence.claude-idlefixture, so real-output false-busybugs shipped latent. Resolved by capturing real gzipped terminal rings as fixtures and moving classification onto a persistent boundedSessionScreenmirror per session, fed incrementally atPtySession's output chokepoint: each classify is O(viewport) rather than O(ring), the[PIR #1205]ring cap can no longer tear the classifier's input, and the whole-render#1047OOM residual is closed. A per-sessionringTokenverdict memo keeps repeated checks cheap.Testing
busy) → submit → clean delivery (subprocess harness).RingBufferand a realSessionScreen(ring path torn/BUSY, mirror path CLEAN).packages/codev4551 passing / 48 skipped / 0 failing; VSCode 667; dashboard 328;send-integration.e2e7/7.busy)→delivers on clear; bg-task/monitor→delivered (no false-busy); real >1 MiB rings classify clean; idle-with-ghost→delivered; agy trust-dialog held. No held-message regressions.chdirrace under concurrent-builder load (not a code defect; green on direct run + retry) and the CI-aware render-gate perf bound.Notes for reviewers
POST-only method guard on/api/inbox/:id/dismiss+ regression test) landed and Claude approved, but there was no iter-4 re-consult. Flagged honestly in the review; the pr gate is the backstop.CLAUDE.md/AGENTS.mdare untouched — those two prompt surfaces are owned by the open Spec 1280, so 1313's send-outcomes docs live only in theafxreference (agent-farm.md) + skeleton mirror +arch-critical.md. Both files are byte-identical toorigin/mainand to each other; 1313 makes no net change to them.#1361, fast-follow) — a long-lived alt-screen session whose replay exceeds the 1 MiB reconnect cap can seed its gate mirror born-torn on a Tower restart and hold mail until the next repaint/viewer. This is pre-existing (the reconnect cap fed the old delivery path the same capped bytes) and fail-safe (the row stays persisted, visible inafx inbox, and escalates — delayed delivery, never loss or misdelivery), and self-heals on any repaint. Tracked separately so this PR stays scoped.Spec / Plan / Review
codev/specs/1313-afx-send-mailbox-first-delivery.mdcodev/plans/1313-afx-send-mailbox-first-delivery.mdcodev/reviews/1313-afx-send-mailbox-first-delivery.md🤖 Generated with Claude Code