Skip to content

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

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

[Spec 1313] afx send: mailbox-first delivery (never force-inject)#1330
mohidmakhdoomi wants to merge 162 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 9 commits August 5, 2026 23:04
…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>
@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

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

Labels

None yet

Projects

None yet

1 participant