fix(composer): unify safe classification across backends - #2102
Merged
Conversation
…le matrix fixed Consolidate every composer shape - bordered boxes (all families, geometry, titled bottom borders), bare agent-glyph rows and their wrap regions, opencode's left bar, and pi's identity-gated separator pair - into fm_composer_classify_screen in bin/fm-composer-lib.sh. Adapters now contribute only a capture and a declarative capability descriptor (styled/cursor/identity/rows); capability differences change how confidently a shape is judged, never what the shapes are, so a new harness shape is teachable in exactly one place. Correctness fixes landed as part of the consolidation (audit data/fm-composer-consolidation-audit-s1): - locale-safe Unicode-space normalization in the shared owner (closes the fleet-wide half of #1988; cmux's local byte-exact NBSP case deleted; naming converges with PR #1995's normalization primitive) - muse's bare glyph joins the shared set, unbreaking muse on herdr/cmux/orca - orca learns the borderless bare shape, drops its backward-paged composer window, and can no longer classify a stale startup banner as the composer - tmux tolerates a titled bottom border, unbreaking grok steering - the left-bar shape makes opencode readable on every backend - zellij gets a real classifier through dump-screen --ansi, replacing the content-diff submit heuristic that could confirm an undelivered message and close a --resolve-key decision (the fleet's only false positive) - fm-spawn's kimi launch-readiness regex (the fourth shape copy) now routes through the shared classifier The strict blank-row posture applies fleet-wide (captain decision blank-row-injection-posture): no positive container proof = unknown = defer, replacing tmux's permissive blank-cursor-row rule. Away-mode injection was re-validated end to end on real tmux (defer on partial input and unproven rows, clean delivery with swallowed-Enter retry into proven-empty composers). The tmux submit core gains a baseline-idle turn-started conversion so pi steering stays confirmed while its working screen hides the composer; busy conversion without that baseline remains forbidden. Plain-capture backends now degrade a glyph row carrying trailing text to unknown instead of a false pending, per the approved capability rule. Portable regressions pin the full byte-capture matrix from the audit under a UTF-8 locale and LC_ALL=C, the strict-vs-permissive divergence, and deliberate signal separation; the opt-in live guard (tests/fm-composer-matrix-live-e2e.test.sh) verified every installed harness against the real classifier, recorded in docs/verification/runtime-backends.md.
The pipeline's isolated gate worktree is untrusted, so claude, grok, and muse stopped at first-launch trust dialogs there (the guard refuses to confirm them by design). This rerun from the trusted checkout at the final validated head verified all six installed harnesses, the strict blank-row deferral, and the hardened zellij false-positive probe live.
…ed rerun The post-validation rerun from the trusted checkout verified all six installed harnesses at the branch's final head, including Claude 2.1.227 (auto-updated since the audit's captures) and Grok, which the untrusted gate worktree could not verify past their first-launch trust dialogs.
kunchenguid
force-pushed
the
fm/fm-composer-thin-adapter-refactor-r2
branch
from
August 10, 2026 23:54
a2b278e to
bb08968
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Docs-only follow-up on the validated superseding branch (PR #2102, checks green; supersedes PR #2066 after a custody deadlock): refresh docs/verification/runtime-backends.md 'Composer classification matrix' with the genuine trusted-checkout live guard rerun performed at the branch's FINAL validated head - the already-agreed post-green step replaying the recorded test-live-trust decision. All six installed harnesses verified live (claude 2.1.227 - auto-updated since the audit, so the shipped classifier is additionally proven on the newer release - codex 0.146.0, opencode 1.14.46, pi 0.84.0, grok 1.0.0, muse 0.1.0-R708.1), plus the strict blank-row deferral and the hardened zellij false-positive probe; kimi remains honestly absent with no live result claimed. No code changes; everything else on the branch is already validated. The captain's finalized decisions stand and are not to be re-litigated.
What Changed
fm-composer-lib.sh, with tmux, Herdr, cmux, Orca, and Zellij reduced to capture/capability adapters and Kimi readiness using the shared classifier.Risk Assessment
✅ Low: The final follow-up accurately records the requested trusted-checkout rerun, versions, absent Kimi status, blank-row deferral, and Zellij false-positive probe without introducing new source risk.
Testing
Confirmed the final follow-up is docs-only, exercised the referenced portable classifier behavior, corroborated the installed harness/version inventory and Kimi absence, and visually reviewed the rendered matrix. All targeted checks passed; the historical live guard was not replayed because this isolated worktree is not the already-trusted checkout whose final-head run the documentation records.
/var/folders/0k/bf8mwt2n5qddzk24r20gfk0c0000gn/T/no-mistakes-evidence/01KZQ18YH4WK55V52FNE3YHCA6/composer-classification-matrix.html.png)Evidence: Reviewable rendered HTML
Evidence: Installed harness inventory and final commit scope
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
git diff --name-status HEAD^..HEAD && git diff --check HEAD^..HEAD— confirmed the follow-up commit changes onlydocs/verification/runtime-backends.mdand has no whitespace errors.tests/fm-composer-lib.test.sh— exercised the portable composer matrix, strict blank-row behavior, all harness shapes including Kimi, and Zellij ANSI handling.Queried--versionfor the live-guard harness inventory plustmux -V; versions matched the documented matrix and Kimi was absent.Rendered the Composer classification matrix from the target commit to HTML and generated/reviewed a 1400×1400 PNG thumbnail.Validated the rendered artifact includes the final-head wording, Claude 2.1.227, six installed harness results, honest Kimi absence, strict deferral, and Zellij false-positive result.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
Reviewer companion: hazard-by-hazard map and evidence
What this is
One comprehensive, captain-approved refactor of the composer-classification architecture (audit
data/fm-composer-consolidation-audit-s1, decisions corr=a66f17eb1dffbe7f): every composer shape now lives in ONE shared owner (bin/fm-composer-lib.sh,fm_composer_classify_screen), the five backend adapters are capture-only plus a declarative capability descriptor (styled/cursor/identity/rowsas data), fm-spawn's kimi regex (a fourth shape copy) is retired, the whole 30-cell correctness matrix is fixed, and the captain's STRICT blank-row posture applies fleet-wide. A new harness shape is teachable in one place and every backend learns it in the same commit.Matrix before/after (idle-empty composer; correct verdict = empty)
Before = production code at 85e750a, measured by the audit. After = this branch (portable byte-capture regressions under UTF-8 AND
LC_ALL=C; live guard for the tmux column).* the CORRECT reachable verdict for that capability profile (approved design, report §4.3 pt 7): a plain capture cannot distinguish codex's idle hint from typed text (the old orca
pendingcame from a startup banner, a confidently wrong answer), and a backend without an identity probe cannot prove pi's blank separated row (the strict rule). Net: 13/30 correct before (2 locale-dependent, 1 permissive artifact, 1 false pending) → 25/30 proven-empty + 5 principled defers, 0 false pending, 0 false empty.The discrete safety fixes, each its own commit
The base commit plus 18 review-round fix commits each prevent a concrete failure. Review rounds were driven gate-by-gate with captain decisions (yolo off); the review agent's adversarial passes found real hazards in both the original code and its own fixes, and each is separately reviewable:
Base commit
05a8bfb— the consolidation itself plus the audit's six matrix fixes: (1) locale-safe Unicode-space normalization closing #1988 for tmux/herdr (claude's❯+U+00A0 readpendingunderLC_ALL=C, deferring every away-mode escalation in daemon contexts; cmux's adapter-local NBSP case deleted); (2) muse's⟩in the shared glyph set (empty muse composers readunknownforever on herdr/cmux/orca); (3) orca learns the borderless bare shape and drops backward paging (every steer to a borderless harness exited unconfirmed; a codex startup banner classified aspendingcomposer text); (4) tmux tolerates grok's titled bottom border (every grok steer faileddelivery unconfirmed; away mode never injected); (5) opencode's left-bar shape (unreadable on every backend); (6) zellij's content-diff submit heuristic replaced by a realdump-screen --ansiclassifier (the fleet's only FALSE-POSITIVE: any pane change - a clock, a spinner - confirmed delivery and could close a--resolve-keydecision for a message never received). Plus the strict posture, the pi identity probe for tmux, and the tri-state groundwork.Review fix commits (
git log origin/main..HEAD, oldest first):f7cf335— prevent Pi identity-proof bypass: a lone agent-glyph draft between Pi rules reademptyafter a swallowed Enter; identity now reroutes it to the separated shape (pending) where the capability exists. On identity-less backends bare-wins is the captain-decided tradeoff (byte-identical to Claude's real composer; Pi delivery there is never confirmed anyway; the zellij delta independently protects sends). Also extends the matrix tests toward the full capture x profile x locale cross-product.3b1438d— prevent the fix above regressing the fix(cmux): classify borderless Claude composers #2029 cell: absent/non-Pi identity keeps the bare verdict, so Claude's❯+NBSP row staysemptyon tmux; removed the test fixture that masked the regression with an impossible identity.a222dff— harden box structure and titled-border geometry against the widened shape set.a787ed0— prevent false delivery confirmation on an unreadable baseline: the submit core's idle baseline is now tri-state and affirmative (an unreadable pre-typing read disables the unknown→empty turn-started conversion entirely); plus the zellij live-guard fails loudly on setup/source/pane-discovery failures instead of passing vacuously.ebe7dc9— prevent a box's bottom border from ever being read as an input row.12af7a5— prove the zellij probe actually typed (zellij actions exit 0 while doing nothing): observation before Enter,send-failedfails the guard.b11aa2e— preserve Pi identity uncertainty and scan full left-bar drafts (a multi-row opencode draft below the hint row was missed).777254a— verify zellij text landed before submitting (typing proof by observation, not exit codes).337ca19— scope that observation to the classified composer (a screen-wide substring passed on transcript echoes while an unrelated draft sat in the composer, and rejected legitimate wrapped sends).d19458e— prove THIS paste landed by composer-scoped pre/post delta (containment passed when the text was already present, or when furniture like opencode's footer matched a short steer such as "high").0ef8b73— prevent falsesend-failedon wrapped pastes: cursorless extraction includes the bare composer's wrap region.d459dc8— prevent dead-shell injection: a stale❯above an exited agent's live$prompt reademptycursorlessly; a lower shell-prompt row now invalidates the candidate.790d80d— handle bordered shell-glyph placeholders in extraction (a│ > Type a message... │baseline broke the paste proof).ba55d5a— prevent injection over streaming output: a bare glyph row with contiguous output directly below no longer readsemptycursorlessly (blank/rule-separated harness footers stay inert).1ca98e4— the same claimed-region rule for box and left-bar candidates (opencode's╹▀▀▀floor counted as the shape's own boundary).eb0addd— preserve user bytes: prompt-glyph stripping applies only to the shape's actual prompt row, never wrapped continuation rows (steers quoting$ cmdlines were stranded assend-failed).40c3e5f— prevent steer text executing at a live shell: extraction can never absorb a shell-prompt-shaped row as wrap continuation (a paste landing AT the shell could delta-pass, and Enter would have executed it); agent-glyph continuations stay preserved.0d2023a— keep wrapped glyph continuations through submit retries (transcript prompt echoes re-anchor to the live bottom prompt; wrap regions are non-glyph-leading rows only).4a5e860— prevent typed placeholder text confirming delivery: the idle pattern proves emptiness only in a shape's own placeholder position; styled text surviving ghost-stripping elsewhere stays pending; plain-capture regex collisions on glyph rows fail safe.64788c6— restore the two cells the previous fix over-tightened (plain glyph-bearing boxed placeholder stays empty - the grok cell; row-leading agent glyphs re-anchor so normal transcripts select the live bottom prompt instead of blocking away-mode injection indefinitely).fd18c8a— align the zellij paste proof with those final semantics (boxed placeholder baselines; superseded fixture reworked).347211d/a3492d3(docs alignment),a3e93b9(lint),6d4b6e2(CI fixes),73b931b(trusted-checkout live rerun record) complete the branch.Away-mode validation under STRICT (captain decision blank-row-injection-posture)
Path:
inject_msg→pane_input_pending→fm_backend_composer_state→ thin capture →fm_composer_classify_screen(strict rule applied once, in the shared owner).tests/fm-daemon.test.sh,tests/fm-composer-lib.test.sh(test_strict_blank_row_divergence- the posture cannot silently regress),tests/fm-composer-ghost.test.sh, and live.tests/fm-afk-inject-e2e.test.sh) - defers on human partial input then delivers cleanly after idle; retries a swallowed Enter (wrapped digests included) with exactly one submission; delivers exactly one clean digest into a proven-empty composer. Escalation buffers survive every deferral.emptyconfirms; the tmux turn-started conversion requires an affirmatively proven idle baseline; zellij requires a composer-scoped typed-text delta - a changed pane is never again a delivery confirmation.Live verification
FM_COMPOSER_MATRIX_LIVE=1 tests/fm-composer-matrix-live-e2e.test.shfrom the trusted checkout at this branch's final head (2026-08-10, rerun at the final r2 head): claude 2.1.227 (auto-updated since the audit; the shipped classifier is proven on the newer release), codex 0.146.0, opencode 1.14.46, pi 0.84.0, grok 1.0.0, muse 0.1.0-R708.1 all classify empty live; strict blank-row deferral held; the hardened zellij probe (typing observed before Enter) confirmed the false-positive dead. Kimi is not installed here - no live result exists or is claimed; its bordered shape is pinned by the portable regressions. Recorded indocs/verification/runtime-backends.md"Composer classification matrix".Sequencing vs open PRs #1995 / #2047
Both target issue #1988 in the shared owner and were open at intake. This branch implements the locale-safe normalization itself using #1995's exact function names (
fm_composer_normalize_spaces_var/fm_composer_normalize_trim_var) so the implementations converge: if this merges first, #1995's normalization hunks and #2047's fm-composer-lib hunk become redundant (their doc/test additions may still be worth cherry-picking); if either merges first, this branch rebases onto the same names. Captain sequencing call.r2 additions (this superseding PR)
This PR supersedes #2066 identically in content after a validation-custody deadlock (nothing was force-pushed or hand-cleared; the old PR's gate record could not reconcile with its legitimately advanced branch). The rebase onto current main reconciles
.agents/skills/afk/SKILL.mdwith #2065: its durable wake acknowledgement content is untouched and this branch's strict away-mode composer-guard section applies on top. Two further captain-decided fixes landed during r2 revalidation:9a63b77- restore proven-box classification when the cursor sits ON the bottom border (grok parks its cursor there in a pristine composer; fixebe7dc9had over-tightened containment and left grok sends/injectionsunknownin that state).b569442- styled box rows obey the placeholder-position rule faithfully: bright text surviving ghost-stripping stayspendingeven when it matchesType a message.../Ask anything...(the real placeholder is de-emphasized and already stripped); plain-capture box rows keep the grok-cell decision.One reviewer proposal was explicitly DECLINED by the captain as a finalized tradeoff: plain-capture bare-composer text degrades to
unknown(loud refusal, no blind Enter retry on cmux/orca) - deliberately NOT extended with delta-proof machinery.73b931b/bb08968record the trusted-checkout live guard reruns honestly (final r2 head: all six installed harnesses live-green including claude 2.1.227 and grok; kimi absent, no live result claimed).