Skip to content

[Spec 1313] afx send: mailbox-first delivery (never force-inject) - #1330

Open
mohidmakhdoomi wants to merge 163 commits into
mainfrom
builder/spir-1313
Open

[Spec 1313] afx send: mailbox-first delivery (never force-inject)#1330
mohidmakhdoomi wants to merge 163 commits into
mainfrom
builder/spir-1313

Conversation

@mohidmakhdoomi

@mohidmakhdoomi mohidmakhdoomi commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces afx send's timer-based, in-memory, force-flushing delivery (SendBuffer, Spec 403) with a mailbox-first pipeline. Every message is persisted to global.db before 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)

  1. Mailbox store — durable mailbox table (migration v15, agent-addressed rows); kills silent loss.
  2. Render-empty gaterender-gate.ts classifies a headless-terminal screen (via @xterm/headless); claude/codex profiles. Idle-gate cost well under the ≤ ~50ms budget.
  3. agy profile (blocking) — net-new empirical measurement; agy's normal-intensity gray hint needed a color-keyed placeholderFgPalette rule (dim/bold couldn't separate idle from draft). Trust dialog classifies not-clean (no blind Enter confirms filesystem trust).
  4. Delivery orchestrationhandleSend rewrite: persist → serialize → gate → deliver/hold. Retires SendBuffer and every force path. Per-PTY write serialization (write-queue.ts). Response vocab delivered | held+reason (busy/no-profile/no-live-pty), surfaced client-side (single + --all).
  5. Fast delivery triggers — submit + output-quiescence triggers via a single handleUserInput chokepoint on PtySession; held mail delivers near-immediately once the line clears.
  6. Cron rerouting — the most-unguarded writer joins the one gated path; per-task supersede; honest run-log outcomes.
  7. afx inbox + broadcasts + escalation — CLI (list/show/dismiss, workspace-scoped), overview-changed held-count SSE, mailbox-escalation visibility event (never delivery), liveness telemetry.
  8. Dashboard + VSCode indicators — count-only held indicators + attention state on escalation (read-only; dismissal stays CLI-only).
  9. Docs + skeleton mirror — send vocab, afx inbox, mailbox config; skeleton mirrored; the mailbox-first invariant added to the arch-critical.md hot 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:

  • Silent-loss on a dropped PTY write — a shellper socket that dies during the paced text→…→Enter sequence (Shellper reconnect error is swallowed: terminal becomes a silent zombie (no input/output, 'Message sent' logged for dropped frames) until next Tower restart #1198) was invisible to the t=0 writable precheck, 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.
  • Architect-identity resolutionafx send architect was always held(no-profile) (architect sessions had no persisted command). Fixed with a restart-safe identity SSOT on the session row (migration v16) + a legacy self-heal that honors TOWER_ARCHITECT_CMD precedence.
  • Render-gate correctness — the classifier had only ever been validated against a synthesized claude-idle fixture, so real-output false-busy bugs shipped latent. Resolved by capturing real gzipped terminal rings as fixtures and moving classification onto a persistent bounded SessionScreen mirror per session, fed incrementally at PtySession'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 #1047 OOM residual is closed. A per-session ringToken verdict memo keeps repeated checks cheap.
  • Ghost-cursor exemption — claude paints a dim suggested-command "ghost" into an idle composer whose first cell doubles as the inverse block cursor; the classifier now exempts exactly that cell (inverse + non-dim, at the headless cursor, with a dim non-empty tail). Requires positive ghost evidence, so a real one-char draft can never be false-cleaned.

Testing

  • Automated e2e for the afx send can corrupt in-progress user input: idle-time is a bad proxy for line-occupancy #1265 repro: draft → held(busy) → submit → clean delivery (subprocess harness).
  • Unit: mailbox lifecycle, gate classification vs captured real fixtures (claude/codex/agy: idle/draft/menu/picker/trust/wrapper/ghost), write serialization, dropped-write hold (sync first write + delayed Enter/multiline), inbox routes (incl. 405 method guard), cron supersede, escalation-is-visibility-only, restart-safe identity, and production-path render tests that push real >2 MiB captures through a real RingBuffer and a real SessionScreen (ring path torn/BUSY, mirror path CLEAN).
  • Playwright: dashboard held-count indicator (4/4 real chromium, incl. live update).
  • Last verified green: packages/codev 4551 passing / 48 skipped / 0 failing; VSCode 667; dashboard 328; send-integration.e2e 7/7.
  • Live verification completed (architect ran the built + installed code): idle→delivered; draft→held(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.
  • See the review's Flaky Tests section for an environmental temp-dir/chdir race under concurrent-builder load (not a code defect; green on direct run + retry) and the CI-aware render-gate perf bound.

Notes for reviewers

  • Phase 7 ended on a porch force-advance at the 3-iteration ceiling: the iter-3 Codex fix (a real 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.md are untouched — those two prompt surfaces are owned by the open Spec 1280, so 1313's send-outcomes docs live only in the afx reference (agent-farm.md) + skeleton mirror + arch-critical.md. Both files are byte-identical to origin/main and to each other; 1313 makes no net change to them.
  • Adopt/reconnect torn-seed (#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 in afx 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

  • Spec: codev/specs/1313-afx-send-mailbox-first-delivery.md
  • Plan: codev/plans/1313-afx-send-mailbox-first-delivery.md
  • Review: codev/reviews/1313-afx-send-mailbox-first-delivery.md

🤖 Generated with Claude Code

@mohidmakhdoomi
mohidmakhdoomi marked this pull request as draft August 1, 2026 23:44
mohidmakhdoomi added a commit that referenced this pull request Aug 2, 2026
…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.
mohidmakhdoomi added a commit that referenced this pull request Aug 3, 2026
…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.
mohidmakhdoomi added a commit that referenced this pull request Aug 3, 2026
…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>
mohidmakhdoomi added a commit that referenced this pull request Aug 3, 2026
…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>
@mohidmakhdoomi

This comment was marked as resolved.

mohidmakhdoomi added a commit that referenced this pull request Aug 3, 2026
…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>
mohidmakhdoomi and others added 21 commits August 5, 2026 22:39
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>
mohidmakhdoomi and others added 7 commits August 5, 2026 23:04
…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>
@mohidmakhdoomi

This comment was marked as outdated.

@mohidmakhdoomi

This comment was marked as resolved.

mohidmakhdoomi and others added 3 commits August 6, 2026 02:07
…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>
@mohidmakhdoomi

This comment was marked as resolved.

mohidmakhdoomi and others added 2 commits August 6, 2026 02:19
…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>
@mohidmakhdoomi

This comment was marked as resolved.

mohidmakhdoomi and others added 3 commits August 6, 2026 12:28
…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>
waleedkadous added a commit that referenced this pull request Aug 6, 2026
…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.
@mohidmakhdoomi

Copy link
Copy Markdown
Collaborator Author

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

Model Verdict Confidence
Gemini APPROVE HIGH — no blocking issues
Claude APPROVE HIGH — no blocking issues
Codex REQUEST_CHANGES HIGH — one blocker: the deferred #1361 adopt/reconnect liveness gap

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 source

Codex: an adopted/reconnected shellper session seeds SessionScreen from capRingSeed()'s 1 MiB tail (tower-terminals.ts:777/1036), so a long-lived alt-screen frame >1 MiB can be born torn → classified busy → mail held indefinitely until a repaint/viewer nudge; "would not merge with #1361 outstanding."

Architect verification (pty-session.ts:227–230): attachShellper feeds the gate mirror the identical capped bytes the ring already receives — pushData(replay) (pre-existing) and feedGateScreen(replay) (round-2) use the same replay string. Therefore this gap is:

  • Pre-existing, not a regression — the reconnect cap predates Spec 1313; the old path fed the gate off the same capped ring. Round-2 fixes the live-ring tear (common case) and worsens nothing (strict Pareto improvement).
  • Fail-safe — the row stays durably persisted, visible in afx inbox, and escalates. It is delayed delivery, not loss — the antithesis of the silent-loss/corruption this spec exists to kill.
  • Self-healing — any repaint / viewer attach / output byte clears it (a Tower restart is normally followed by dashboard/VSCode viewers reconnecting).
  • Narrow — requires >1 MiB single newline-free alt-screen frame + Tower reconnect + sustained idle + no viewer + no output, simultaneously.
  • TrackedRender gate: adopt/reconnect mirror seed capped at 1 MiB → idle adopted alt-screen agent can hold mail until next repaint #1361, with two concrete fixes (uncapped ≤8 MiB mirror seed, or repaint-nudge on adopt).

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)

  • Silent-loss fix (the prior integration round's blocker): correct — writeMessagePaced(): Promise<boolean> records any dropped write across the paced text→…→Enter sequence; deliverAgentMail defaults written=false, holds no-live-pty on drop before markDelivered, and invalidates the verdict memo in a rejection-safe finally.
  • Migrations v15/v16: additive + idempotent — CREATE TABLE/INDEX IF NOT EXISTS; v16 ADD COLUMN command is PRAGMA table_info-gated. No destructive changes.
  • Branch state: fully contains origin/main (0 behind, MERGEABLE) — the review's "un-rebased / T16-red / 285-behind" caveat is stale/moot.
  • spec-1280 collision neutralized: CLAUDE.md/AGENTS.md are byte-identical to origin/main (0 net change) and to each other; 1313 touches neither prompt surface.
  • Hot-tier displacement clean: the Spec 987 governance-tier fact and the forge-concept fact displaced from arch-critical.md both survive in cold arch.md (§Governance Docs, §Forge Concept Commands). No doc regression.
  • API changes additive; SSE workspace-scoped; escalation is visibility-only (never a force path).

Decision

Held at the pr gate pending the human's merge call (2-1 split on a HIGH-confidence blocker + the committed→integrated gate is human-only). Architect recommendation: APPROVE for merge with #1361 as a required fast-follow.


Architect integration review

@waleedkadous

Copy link
Copy Markdown
Contributor

Architect Integration Review

This is an impressive piece of systems work — the render-empty gate with empirically-captured per-harness profiles (including the agy color-keyed placeholder rule and the ghost-suggestion fixture), the transactional cron supersede, and the no-force-path principle are all the right architecture. Ran a 2-way CMAP (codex REQUEST_CHANGES / claude APPROVE, both HIGH) with the maintainer's concerns injected; where the lanes contradicted each other I verified against the branch myself. I'd like to see three changes before merge:

  1. --delay's lower bound is lost on the hold path (verified: mailbox-store.ts/mailbox-wiring.ts carry no notBefore concept, and the NOT_FOUND→registry hold at tower-routes.ts:1653 excludes only escape && interrupt, not delayed sends). A --delay message to a target without a live/writable session is persisted delay-less and can drain immediately — breaking the /arch-save clear→re-init sequencing that --delay exists for. Suggested fix: persist not_before on the row and have the drainer respect it — which as a bonus makes --delay durable across Tower restarts, stronger than the [Spec 1307] /arch-save: packaged save→clear→re-init for architect context refresh #1335 in-memory timer it replaces.
  2. Close the shutdown window inside the submission lock. The timer path's delivery-time generation re-check is good (claude's lane verified it), but codex identified the residual: the due callback checks liveness before entering submitToSession without a re-check inside the lock, and marks the row delivered ahead of the queued write — a shutdown during lock-wait can write after the drop decision, and a failed write stays marked delivered. Re-check the generation inside the lock, mark delivered only after the write settles, and cover this seam with a route-level test (the existing test calls the predicate directly).
  3. Give the residue-starvation case a reachable alarm. Both lanes converge here, and it's the maintainer's top concern: one accidental visible character on an autonomous builder's line holds all mail (including cron) indefinitely — busy streaks are excluded from liveness telemetry by design, and escalation reaches only VSCode/dashboard, which headless flows don't watch. Minimum viable: surface heldCount/mailboxEscalated in afx status, and route a non-injecting escalation notice to the spawning architect's mailbox after a held-age threshold. (The remedy afx interrupt exists; the missing piece is the alarm that tells anyone to use it.)

Take-or-file follow-ups: route mailbox-wiring.ts:211's writeMessage through submitToSession so mailbox delivery can't interleave an interrupt (converges the two serializers the docs currently over-claim about); have afx cleanup dismiss a removed agent's held rows (today they pin heldCount/escalation forever); fix the stale "typing-aware send buffer" reference at agent-farm.md:528; and reconsider displacing the git add -A hot-tier line instead of the Spec 987 tier-routing meta-rule.

The cron supersede analysis came back fully clean — bounded to one held row per (workspace, task), verified transactional. And thank you for the honest T16-era course-corrections on this branch; the review record on it is exemplary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants