Skip to content

feat(bin): confirm tmux submits from semantic lifecycle records - #1391

Open
JaredHuynhning wants to merge 8 commits into
kunchenguid:mainfrom
JaredHuynhning:fm/fm-semantic-submit
Open

feat(bin): confirm tmux submits from semantic lifecycle records#1391
JaredHuynhning wants to merge 8 commits into
kunchenguid:mainfrom
JaredHuynhning:fm/fm-semantic-submit

Conversation

@JaredHuynhning

Copy link
Copy Markdown

Finishes the semantic-lifecycle migration on the tmux submit path (system-review item 1): an instruction's delivery is confirmed from the harness's own lifecycle signal, with rendered-composer scraping demoted to an explicitly labelled fallback.

What changed

  • Confirmation requires positive evidence. Only a record-proven idle baseline enters the semantic path, and a send is confirmed (empty turn-opened) only when a trusted busy record with an advanced sequence is observed after an Enter keystroke the send actually delivered. An unchanged record never confirms anything: busy, unknown, malformed, and stale baselines - including the spawn-time seed and a stuck record from a crashed turn - route to the rendered-composer core, whose verdict carries an explicit composer-fallback label.
  • A rejected keystroke is a failure. If every Enter keystroke fails, the verdict is send-failed, never a confirmation.
  • The rendered signal can veto, never grant. With FM_SUBMIT_SEMANTIC_COMPARE=1 (the bake-in default) the composer is read exactly once, after the verdict; a confirmation contradicted by a still-pending composer is downgraded to unknown semantic-contradicted, and every disagreement is recorded durably in state/<id>.submit-disagreement (cleaned by teardown, documented in AGENTS.md). fm-send accepts only exact empty, so a pending-reply delivery is never committed on a contradiction - closing the suppression class behind the 9.5-hour incident recorded in bin/fm-tmux-lib.sh's header.
  • Coverage: claude, opencode, pi, and pi-signed confirm semantically through the existing feat(bin): replace busy heuristics with semantic lifecycle state #1327 hook, plugin, and extension wiring. Codex, kimi, grok, unarmed tasks, and context-free sends (the away-mode daemon) keep the scraper; the daemon's call shape and verdicts are byte-identical. Eligibility reads bin/fm-busy-lib.sh's trust table, so codex and kimi flip automatically when their verification gates open.
  • The confirm-window floor (FM_SUBMIT_SEMANTIC_MIN_BUDGET) defaults to a conservative 2 seconds per attempt, sized to cover the writer chain (harness event dispatch, process spawn, writer lock, atomic write) on a loaded machine.

Known gaps, deliberately not addressed here

  • No send-to-turn correlation. Confirmation is an observed idle-to-busy transition, not a correlated acknowledgement, so an unrelated turn that opens in the confirmation window can be credited to a send when the composer reads empty or unknown. The bake-in comparison only vetoes the case where the composer still shows the text.
  • No concurrent-send serialisation. Two concurrent sends to one task can read the same idle baseline and both claim the same turn. A per-task lock built in an earlier round was removed by explicit decision: it half-covered the problem (busy and ineligible sends bypassed it) while adding failure modes of its own.
  • Both gaps are owned by the queued fm-submit-correlation follow-up, which starts from the premise that this needs a per-submit token the harness echoes back, not more shell-side inference.
  • The live hook-write latency measurement is deliberately not taken here. The 2-second floor is sized to cover the unmeasured writer chain conservatively rather than tuned to a measurement; a too-short budget reports a delivered send as unconfirmed (loud), never the reverse.

Validation

Validated through the no-mistakes pipeline: two review rounds (three findings fixed in-pipeline: the test suite registered in the runner's family map, a stale lock comment removed, and the confirm-budget rationale corrected with the raised floor), full test suite, documentation, and lint all green. tests/fm-tmux-submit-semantic.test.sh proves transition-confirmed submits with zero pane reads at the post-bake-in setting and exactly one post-verdict read at the default, the contradiction downgrade with its durable record, the rejected-Enter refusal, and that busy baselines never confirm from the record.

🤖 Generated with Claude Code

Submit confirmation on the tmux path now reads the harness's own
generation-stamped busy record (bin/fm-busy-lib.sh) instead of rendered
composer characters: a trusted busy record with an advanced sequence after
Enter confirms the submit, and a record-proven mid-turn pane accepts the
Enter as queued. Claude, OpenCode, Pi, and pi-signed are covered through
the existing kunchenguid#1327 hook wiring; codex, kimi, grok, unarmed tasks, and
context-free sends keep the composer scraper as an explicitly labelled
composer-fallback verdict, and the away-mode daemon's context-free call
keeps its exact bare verdict. During bake-in
(FM_SUBMIT_SEMANTIC_COMPARE=1) both signals are evaluated in parallel and
a loud SUBMIT-CONFIRM DISAGREEMENT warning fires when they differ, so a
mis-wired hook reporting submits that did not happen is caught instead of
trusted. fm-send passes the task's semantic context through the backend
dispatcher and surfaces how confirmation was attempted in error output.
The old-bin conformance shim registers fm-busy-lib.sh as a copied sibling
because fm-tmux-lib.sh now sources it.
…tures

fm-tmux-lib.sh now sources fm-busy-lib.sh for semantic submit
confirmation, so every synthetic root that symlinks fm-tmux-lib.sh needs
the new sibling reachable or teardown dies at source time.
…nfirm

Independent review rejected the first cut for confirming sends from an
unchanged busy record: the spawn-time seed or a stuck record from a
crashed turn confirmed every send forever (B1), a rejected Enter still
confirmed (B2), a turn from a previously queued message could credit a
swallowed Enter (B3), concurrent sends could claim one turn (B4), a
contradicted confirmation still marked pending replies delivered (B5),
the shipped default contradicted the no-pane-read claim (B6), and one
test encoded the defect as intended behaviour (B7).

Redesign: only a record-proven idle baseline enters the semantic path,
and confirmation requires a trusted busy record with an advanced
sequence observed after an Enter this send actually delivered, under a
per-task submit lock that fails closed when contended and breaks dead
holders' stale locks. Busy, unknown, and unreadable baselines route to
the labelled composer fallback, so an unchanged record can never
confirm. A send whose every Enter was rejected is send-failed. During
bake-in the rendered composer is read once after the verdict; a
contradicted confirmation is downgraded to unknown
semantic-contradicted and every disagreement is appended durably to
state/<id>.submit-disagreement, which teardown now removes along with
the lock. The B7 test now proves busy baselines never confirm from the
record, and new tests pin the rejected-Enter, contended-lock,
stale-lock, downgrade, and default-environment single-read behaviours.
…orrelation

Captain re-scope after the independent recheck: the per-task submit lock
half-covered the concurrency problem (busy and ineligible sends bypassed
it and ran the fallback concurrently) while adding failure modes of its
own: a legitimate send could block up to 60 seconds on an unstamped
lock, PID reuse could lock a task out repeatedly, and an interruption
between the stale-lock rename and removal leaked a stale directory. A
half-covering lock that can block real sends is worse than the
unserialised status quo, so it is removed along with its two tests, and
teardown no longer cleans a lock artifact.

The five recheck-closed fixes stay: unchanged and non-idle records never
confirm and route to the labelled fallback, a rejected Enter is
send-failed, a contradicted confirmation downgrades so a pending-reply
delivery is never committed on a contradiction, the default pane read
stays post-verdict and veto-only, and the busy-baseline test proves the
record alone never confirms.

Known gaps stated in code and docs and owned by the queued
fm-submit-correlation follow-up: no send-to-turn correlation (an
unrelated turn can be credited to a send when the composer reads empty
or unknown) and no concurrent-send serialisation; correlation needs a
per-submit token the harness echoes back.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant