Skip to content

fix(bin): acknowledge idle Pi submissions on Herdr - #2042

Closed
zachlandes wants to merge 9 commits into
kunchenguid:mainfrom
zachlandes:fm/fix-afk-pi-herdr-ack
Closed

zachlandes wants to merge 9 commits into
kunchenguid:mainfrom
zachlandes:fm/fix-afk-pi-herdr-ack

Conversation

@zachlandes

@zachlandes zachlandes commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Refresh the EXISTING upstream pull request #2042 ("fix(herdr): acknowledge idle Pi submissions") past its merge conflicts. This is NOT new feature work. The sole goal is to make that already-open pull request mergeable again after upstream main moved underneath it. It had mergeable=false, mergeable_state=dirty.

METHOD IS FIXED AND NOT OPEN TO CHANGE. Merge current origin/main INTO the branch, then push normally. Never rebase, never force-push, never reset --hard, never filter-branch this branch. Two load-bearing reasons: a history rewrite was not authorized, and a merge is trivially revertable while a rewritten branch is not. This is why the branch deliberately carries merge commit 315bb41 with two parents (829f15a, the previous pull request head, and f1a4af4, origin/main) instead of a linear history. Do not linearize it. Do not "clean up" the merge commit.

CONFLICT RESOLUTION RULE. For every conflict: understand what upstream changed and why, understand what this branch intends, and produce the version that preserves BOTH. A conflict resolved by deleting the other side's behaviour is a silent regression and was explicitly ruled unacceptable.

Two files conflicted. Both were resolved by keeping both sides.

  1. bin/backends/herdr.sh. Upstream (PR feat: add Cursor CLI crew harness #2238, Cursor CLI crew harness) and this branch each inserted a DIFFERENT new function at the same insertion point just after fm_backend_herdr_composer_state. That adjacency was the entire reason they collided; the two functions are unrelated. Both were kept: upstream's fm_backend_herdr_rendered_busy_state (rendered busy-footer delivery confirmation for a harness whose native agent-state never reports idle) and this branch's fm_backend_herdr_pi_idle_composer_state (Pi identity-corroborated idle composer acknowledgement). The two paths provably cannot shadow each other: they sit on opposite arms of the submit core's idle-baseline test, and fm_backend_herdr_classify_submit_agent_status maps blocked to busy, so a Cursor pane (which Herdr reports as blocked in every state) always takes the footer arm and can never reach the Pi fallback, while Pi reports idle and always takes the Pi arm. A non-Pi agent that does reach the Pi arm still resolves to pending, exactly as before the change.

  2. docs/architecture.md. A single sentence that both sides had rewritten. Merged so it names upstream's pre-Enter rendered-footer fallback AND this branch's Pi-only idle-delivery acknowledgement, rather than taking either side whole.

TWO DELIBERATE EDITS BEYOND THE RAW CONFLICT HUNKS. Upstream's new Cursor harness falsified two claims this branch had written as complete enumerations, so each was extended to stay true after the merge. First, the closing summary comment on fm_backend_herdr_send_text_submit had been narrowed into an exhaustive two-way claim and now also names the non-idle rendered-footer fallback. Second, the per-harness "retain their existing submit paths" list in docs/verification/runtime-backends.md now includes Cursor. These are intentional accuracy repairs that keep the merged tree from asserting something false. They are not scope creep and should not be reverted as unrelated.

SCOPE CONSTRAINT. Conflict resolution only. Do not improve unrelated code, do not refactor, do not extend the underlying Pi fix, and do not make behavioural choices this pull request never made. The branch's contribution over origin/main is deliberately exactly its original 14 files: no more, no fewer.

REPO CONTRIBUTOR RULES that apply to this change: one full sentence per line in tracked Markdown, plain dash rather than em dash, shellcheck-clean bin scripts, tests colocated in tests/ following the existing pattern, and never an agent name as a commit co-author.

DELIVERY CONSTRAINT. This pull request ALREADY EXISTS and must be UPDATED, never duplicated. Do not open a second pull request for this branch. Merge authority belongs to the upstream maintainer and is explicitly NOT ours; do not merge pull request 2042 and do not ask anyone to merge it. The job ends at green checks on the refreshed head.

KNOWN PRE-EXISTING FAILURES, ALREADY INVESTIGATED, OUT OF SCOPE, DO NOT FIX HERE. Four tests fail on this branch: tests/fm-bearings-snapshot.test.sh, tests/fm-wake-queue.test.sh, tests/fm-pi-watch-extension.test.sh, and tests/fm-calm-pi-extension.test.sh. All four were verified to fail byte-identically on a clean origin/main checkout with no merge involved, so they are pre-existing upstream failures in this environment and are not caused by this change. Three of them sit in areas upstream heavily rewrote (calm mode, wake queue, bearings) that this branch never touches. Fixing them would expand this conflict refresh well past its authorized scope.

EVIDENCE THAT THE MERGED AREA IS SOUND. tests/fm-backend-herdr.test.sh passes 178 of 178 with zero failures, with BOTH sides' cases green in the same run: this branch's three Pi tests and upstream's four Cursor and rendered-footer tests. bin/fm-lint.sh is clean, bin/fm-doc-audience-check.sh reports ok, and the test-runner coverage guard passes.

What Changed

  • Confirm idle-native Pi submissions only when Herdr reports the Pi identity and the post-Enter composer is structurally empty, while preserving existing non-Pi and rendered-footer paths.
  • Add focused adapter tests and an opt-in Pi/Herdr end-to-end regression covering delivered, swallowed, and unsubmitted input.
  • Document the updated Herdr confirmation behavior and include recorded verification evidence.

Risk Assessment

✅ Low: The merge preserves both conflicted behaviors, retains the durable Pi acknowledgement invariant, and limits branch changes over main to the intended 14 files.

Testing

Targeted adapter tests and a real Pi-on-Herdr away-mode run confirmed exactly-once idle delivery, retained pending input, and preserved Cursor's rendered-footer path; commit topology and 14-file scope also match intent. This is a CLI/backend change, so the live transcript is the reviewer-visible evidence rather than a screenshot.

Evidence: Real Pi/Herdr end-to-end acknowledgement transcript

ok - real Pi/Herdr idle-native delivery clears the buffer after one typed digest ok - real Pi/Herdr unsubmitted input preserves the pending buffer evidence: pi=0.84.2 herdr=0.8.0 protocol=19 successful_send_texts=1

fm-afk-launch: daemon launched in non-visible herdr workspace w2 (pane fm-lab-fix-afk-pi-herdr-98357-30232:w2:p1), supervising fm-lab-fix-afk-pi-herdr-98357-30232:w1:p1
ok - real Pi/Herdr idle-native delivery clears the buffer after one typed digest
ok - real Pi/Herdr unsubmitted input preserves the pending buffer
fm-afk-launch: terminal close command failed, but exact absence was confirmed
fm-afk-launch: away mode stopped; daemon terminal torn down and .afk cleared
evidence: pi=0.84.2 herdr=0.8.0 protocol=19 successful_send_texts=1
Evidence: Focused Herdr adapter test transcript covering both merged behaviors
ok - fm_backend_herdr_version_check: accepts the current protocol (14)
ok - fm_backend_herdr_version_check: refuses an old protocol loudly
ok - fm_backend_herdr_version_check: refuses loudly when herdr is not installed
ok - fm_backend_herdr_workspace_label: a primary home (no marker) resolves to 'firstmate'
ok - fm_backend_herdr_workspace_label: a secondmate home (.fm-secondmate-home) resolves to '2ndmate-<id>'
ok - fm_backend_herdr_workspace_label: trims whitespace around the marker's secondmate id
ok - fm_backend_herdr_workspace_label: an empty marker file falls back to the primary label 'firstmate'
ok - fm_backend_herdr_workspace_label: two different secondmate homes get two different, non-colliding labels
ok - fm_backend_herdr_cli: sets HERDR_SESSION AND appends a trailing --session flag on every call
ok - fm_backend_herdr_launcher_identity: a firstmate not running inside herdr has no launcher workspace to inherit
ok - fm_backend_herdr_launcher_identity: HERDR_ENV=1 without a pane id selects the backend but binds no parent
ok - fm_backend_herdr_launcher_identity: resolves the launcher's exact workspace even when a same-labeled workspace sorts first
ok - fm_backend_herdr_launcher_identity: refuses a launcher pane that names a different herdr session
ok - fm_backend_herdr_launcher_identity: refuses a claimed pane without exact server identity
ok - fm_backend_herdr_launcher_identity: refuses a launcher pane whose injected socket belongs to another herdr server
ok - fm_backend_herdr_launcher_identity: refuses when the launcher's own pane no longer resolves
ok - fm_backend_herdr_launcher_identity: refuses when the launcher's pane and tab disagree about their workspace
ok - fm_backend_herdr_launcher_identity: refuses when the launcher's workspace is gone from its own session
ok - fm_backend_herdr_workspace_ensure: places a worker in the launcher's exact workspace, not the first same-labeled one
ok - fm_backend_herdr_workspace_ensure: refuses to guess between two same-labeled home workspaces
ok - fm_backend_herdr_workspace_ensure: a --secondmate container resolves that home's own workspace, not the launcher's
ok - fm_backend_herdr_container_ensure: surfaces the exact ambiguous-placement refusal instead of a generic failure
ok - fm_backend_herdr_container_ensure: version-gates, starts the server, ensures the firstmate workspace, echoes session:workspace_id + the seeded default tab id
ok - fm_backend_herdr_container_ensure: reuses an existing firstmate workspace without recreating it, and reports no seeded default tab (adopted, not created)
ok - fm_backend_herdr_container_ensure: workspace create passes --no-focus
ok - fm_backend_herdr_container_ensure: creates the workspace under the SECONDMATE home's own label, not 'firstmate'
ok - fm_backend_herdr_create_task: prunes exactly the seeded default tab container_ensure identified, once the first real task tab exists
ok - herdr repeated spawn/teardown: one persistent firstmate workspace reused, zero orphans, default tab pruned, create ran once
ok - fm_backend_herdr_create_task: an ADOPTED workspace's pre-existing tab is never pruned (the created-vs-adopted gate)
ok - fm_backend_herdr_create_task: the label-collision startup-workspace scenario (2026-07-02 incident) leaves the captain's live tab untouched
ok - fm_backend_herdr_workspace_prune_seeded_default_tab: refuses to close the seeded default tab when its pane reports a working agent (defense in depth)
ok - fm_backend_herdr_create_task: refuses a duplicate tab label (herdr's own tab create has no uniqueness check)
ok - fm_backend_herdr_create_task: a same-labeled tab with a live (even idle) registered agent still refuses exactly as before
ok - fm_backend_herdr_create_task: scans every same-labeled tab and refuses if any duplicate is live
ok - fm_backend_herdr_create_task: closes and replaces a same-labeled tab whose pane is dead (pane_not_found)
ok - fm_backend_herdr_create_task: closes and replaces a same-labeled tab whose pane is alive but hosts no registered agent (a restored plain shell)
ok - fm_backend_herdr_create_task: closes every confirmed same-labeled husk only after creating the replacement
ok - fm_backend_herdr_create_task: refuses success when a preexisting husk tab remains after replacement
ok - fm_backend_herdr_create_task: refuses (fail-safe) rather than guessing when the duplicate's agent state cannot be classified confidently
ok - fm_backend_herdr_create_task: creates the replacement tab BEFORE closing the husk tab, never the reverse
ok - fm_backend_herdr_create_task: creates a tab and parses tab_id/pane_id from the JSON response, prunes nothing when no seeded tab id is given
ok - fm_backend_herdr_create_task: tab create passes --no-focus
ok - herdr presentation: a home that set nothing gets the projection by default at or above the floor
ok - herdr presentation: an unconfigured home below the floor falls back flat with one naming warning
ok - herdr presentation: an unreadable client release falls back flat instead of guessing
ok - herdr presentation: a deliberate opt-in is never silently downgraded below the floor
ok - herdr presentation: an explicit off opts the home out
ok - herdr presentation: an unrecognized value warns and follows the default instead of failing a spawn
ok - herdr presentation: the below-floor warning is one per home per release, not one per spawn
ok - herdr presentation: warning marker publication is atomic, symlink-safe, and fails visible
ok - herdr presentation: client and selected server floors compose conservatively without overriding explicit opt-in
ok - herdr presentation floor: every measured release, and each signal alone, classifies correctly
ok - herdr presentation floor: either signal alone can carry an above verdict, and each divergence is real
ok - herdr presentation: config parsing separates a deliberate choice from an unconfigured default
ok - herdr presentation journal: atomically publishes one non-authoritative 128-bit correlator and refuses overwrite
ok - herdr presentation journal: version 2 binds exact home/endpoint/parent identities and advances atomically
ok - herdr presentation create: exact response IDs yield one normal task pane with no workspace-close authority
ok - herdr presentation create: concurrent same-label tabs are never prune targets
ok - herdr presentation focus: snapshot requires one exact active workspace and tab
ok - herdr presentation focus: exact pane close restores the exact prior workspace and tab
ok - herdr presentation focus: cleanup refuses rather than close the captain's active tab
ok - herdr presentation focus: pane close fails when exact focus restoration fails
ok - herdr presentation reclaim: live agent state at the close boundary refuses mutation
ok - herdr presentation cleanup: emptying close behind focus ends the exact shell without a move or focus change
ok - herdr presentation cleanup: emptying close before focus moves the doomed workspace to the end and ends its exact shell
ok - herdr presentation cleanup: emptying close with the focused workspace last skips the move
ok - herdr presentation cleanup: emptying close of the last workspace skips the move
ok - herdr presentation cleanup: a non-emptying close stays plain with no proof, move, or signal
ok - herdr presentation cleanup: no-move plain close requires structured pane removal
ok - herdr presentation cleanup: an ambiguous workspace layout falls back to the plain close
ok - herdr presentation cleanup: a failed repositioning move falls back to the plain close with a warning
ok - herdr presentation cleanup: a pane with a live foreground process falls back to the plain close
ok - herdr presentation cleanup: a transient prompt helper settles into the pane-death path instead of the plain close
ok - herdr presentation cleanup: a SIGHUP-surviving shell is escalated to SIGKILL before giving up
ok - herdr presentation cleanup: a failed pane-death close falls back to the plain close
ok - herdr presentation cleanup: the exact-tab restore remains the backstop behind the pane-death close
ok - herdr presentation cleanup: SIGKILL never reaches a pid the exact pane no longer owns
ok - herdr presentation cleanu

... [2634 bytes truncated] ...

nter/escape/ctrl+c
ok - fm_backend_herdr_capture: calls 'pane read <pane> --source recent --lines N' with the session set
ok - fm_backend_herdr_capture: works around the verified small-N '--lines' bug by over-fetching and trimming locally
ok - fm_backend_herdr_capture: ensures the session and preserves pane read failure
ok - fm_backend_herdr_send_key: normalizes the key and targets the right pane
ok - fm_backend_herdr_kill: calls pane close and stays best-effort on failure
ok - fm_backend_herdr_current_path: reads pane foreground_cwd (the live running process), not the frozen creation-time cwd
ok - fm_backend_herdr_busy_state: working -> busy
ok - fm_backend_herdr_busy_state: done -> idle, blocked -> idle (surfaced like a stale pane, not suppressed as busy)
ok - fm_backend_herdr_busy_state: unparseable/absent agent state reports unknown, the regex-fallback cue
ok - fm_backend_herdr_composer_state: a bare '❯' composer row reads empty
ok - fm_backend_herdr_composer_state: bright placeholder-like text stays pending rather than being mistaken for an idle ghost
ok - fm_backend_herdr_composer_state: real composer text reads pending
ok - fm_backend_herdr_composer_state: a slash-command popup's argument-hint placeholder still reads pending (the incident fix)
ok - fm_backend_herdr_composer_state: reports unknown when the pane cannot be captured
ok - fm_backend_herdr_composer_state: reports unknown for bare shell prompts with no composer row
ok - fm_backend_herdr_composer_state: a native idle Pi separator composer reads empty
ok - fm_backend_herdr_composer_state: real Pi composer text remains pending
ok - fm_backend_herdr_composer_state: an incomplete lower Pi separator cannot inherit a stale empty row
ok - fm_backend_herdr_composer_state: Pi separators never authorize working, non-Pi, unreadable, or over-tall targets
ok - fm_backend_herdr_composer_state: a real-claude unbordered '❯' prompt row (no border box in view) reads empty
ok - fm_backend_herdr_composer_state: a real-claude unbordered '❯ <text>' prompt row reads pending
ok - fm_backend_herdr_composer_state: a live unbordered prompt row below a stale bordered decorative box still wins (not misread as the box's own row)
ok - fm_backend_herdr_composer_state: claude's dim prompt-suggestion ghost (the overnight wedge shape) reads empty
ok - fm_backend_herdr_composer_state: real typed text on the same claude prompt row still reads pending
ok - fm_backend_herdr_composer_state: grok's dark-truecolor placeholder (the TRUECOLOR gap) reads empty
ok - fm_backend_herdr_composer_state: grok's real bright typed input still reads pending
ok - fm_backend_herdr_composer_state: a real-codex unbordered '›' prompt row reads empty
ok - fm_backend_herdr_composer_state: a faint real-codex ghost suggestion reads empty
ok - fm_backend_herdr_composer_state: non-faint codex prompt text still reads pending
ok - fm_backend_herdr_wait_for_working: reports 'busy' immediately on the first poll, without spending the rest of the budget
ok - fm_backend_herdr_wait_for_working: a slow transition landing on a later sample within one window is still caught (robust against the 'slow transition' failure direction)
ok - fm_backend_herdr_wait_for_working: spreads six samples across the full budget endpoint without a final trailing sleep
ok - fm_backend_herdr_send_text_submit: applies the herdr minimum confirmation budget before polling agent-state
ok - fm_backend_herdr_wait_for_working: reports 'idle' (readable, genuinely not yet working) when 'busy' never appears
ok - fm_backend_herdr_wait_for_working: reports 'unknown' (a hard read failure, not a timing race) only when EVERY poll in the window fails
ok - fm_backend_herdr_wait_for_working: treats blocked as submit-active for confirmation without changing watcher busy-state semantics
ok - fm_backend_herdr_send_text_submit: reports 'empty' once agent_status reports working after one Enter, without ever reading the composer
ok - fm_backend_herdr_send_text_submit: confirms one consumed Pi message when native Pi state stays idle
ok - fm_backend_herdr_send_text_submit: preserves the buffer for a swallowed Pi Enter
ok - fm_backend_herdr_send_text_submit: native Herdr confirmation remains unchanged
ok - fm_backend_herdr_send_text_submit: reports 'pending' when agent_status never reports working after retried Enters (swallowed)
ok - fm_backend_herdr_send_text_submit: a slash-command popup's placeholder fill on Enter #1 never flips agent_status to working, so it does not short-circuit as submitted; Enter #2 is retried and lands it
ok - fm_backend_herdr_send_text_submit: a post-Enter blocked state confirms delivery without retrying into the prompt
ok - fm_backend_herdr_send_text_submit: preexisting working is not accepted as submit proof when the composer still holds the message
ok - fm_backend_herdr_composer_state: cursor's mid-turn placeholder-plus-busy-token row reads pending (why delivery needs a separate signal)
ok - fm_backend_herdr_rendered_busy_state: busy/idle/unknown from the rendered footer, with an unreadable pane never reading idle
ok - fm_backend_herdr_send_text_submit: a rendered-footer idle-to-busy transition confirms delivery when native agent-state never reports idle
ok - fm_backend_herdr_send_text_submit: an already-busy footer baseline is never accepted as proof that this Enter landed
ok - fm_backend_herdr_send_text_submit: confirms submission via native agent-state alone, immune to a codex-style dynamic idle-tip composer that would have misread as 'pending' under the old composer-based confirmation
ok - fm_backend_herdr_composer_state: a faint real-codex dynamic idle-tip composer row reads empty
ok - fm_backend_composer_state (herdr): the pre-injection empty-box guard still refuses a genuinely non-empty composer, unaffected by the submit-confirmation change
ok - fm_backend_herdr_send_text_submit: a slow transition landing on a later sample within one Enter's budget is confirmed WITHOUT sending a needless extra Enter
ok - fm_backend_herdr_send_text_submit: reports 'send-failed' when the literal send-text call itself errors
ok - fm_backend_herdr_send_text_submit: reports 'unknown' when the post-Enter agent-get read fails (never retries past an unreadable target)
ok - fm_backend_validate: herdr is a known backend (P2)
ok - fm_backend_busy_state: tmux (no native primitive) always reports unknown, preserving the P1 regex-only path
ok - fm_backend_composer_state dispatches every backend to its named thin classifier, unknown for unrecognized backends
ok - fm-peek/fm-send: explicit stale targets matching metadata use the recorded backend
ok - fm_backend_herdr_normalize_event routes through the shared record with an empty from_status
ok - fm_backend_herdr_escalation_marker keys the dedupe marker exactly like the watcher's .stale-<key>
ok - fm_backend_herdr_apply_transition: blocked dedupe starts only after explicit commit
ok - fm_backend_herdr_apply_transition: a working edge clears the marker so the next ->blocked re-escalates
ok - fm_backend_herdr_clear_transition removes task-owned dedupe state
ok - fm_backend_herdr_apply_transition: idle/done (defer) and unknown/empty (fallback) take no fast action
ok - fm_backend_herdr_wait_transition: a home with no herdr panes falls back to polling (rc 2)
ok - fm_backend_herdr_wait_transition: below-capability protocol/schema falls back to polling (rc 2)
ok - fm_backend_herdr_wait_transition: reconnect level-reconcile returns an uncommitted blocked pane
ok - fm_backend_herdr_wait_transition: subscribes before reconnect level-reconcile
ok - fm_backend_herdr_wait_transition: a still-blocked, already-escalated pane is not re-delivered on reconnect
ok - fm_backend_herdr_wait_transition: a streamed ->blocked edge returns the record sub-poll
ok - fm_backend_herdr_wait_transition: streamed working clears the marker, idle/done are deferred (clean timeout)
ok - fm_backend_herdr_wait_transition: a reader/subscribe failure falls back to polling (rc 2)
ok - fm_backend_herdr_wait_transition: Bash 3.2-safe bad-ack path closes fd 9 and removes its FIFO
ok - fm_backend_herdr_wait_transition: stock macOS Bash clean timeout closes fd 9 and returns 1

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped

Step was skipped.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Inspected git show -s --format=&#39;%H %P&#39; HEAD and the base-to-head file list to confirm merge commit 315bb41 retains both required parents and exactly 14 changed files.
  • Ran tests/fm-backend-herdr.test.sh, exercising the Pi fallback, swallowed-Enter handling, unchanged native confirmation, and Cursor rendered-footer fallback together.
  • Ran FM_AFK_PI_HERDR_ACK_E2E=1 tests/fm-afk-pi-herdr-ack-e2e.test.sh against real Pi 0.84.2 and Herdr 0.8.0 protocol 19.
  • Inspected the saved end-to-end transcript and confirmed the worktree remained clean after test teardown.
⚠️ **Document** - 1 warning
  • ⚠️ bin/fm-composer-lib.sh:1304 - The comment still says Herdr confirms only through native agent-state; updating it would expand the explicitly fixed 14-file change scope.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

* Kept native agent-state confirmation unchanged for non-Pi agents.\n* Required a matching idle Pi identity and empty composer before clearing a digest buffer.\n* Added unit, real-lab, and showboat evidence for delivered and pending input.
@zachlandes
zachlandes force-pushed the fm/fix-afk-pi-herdr-ack branch from 89cf63d to 829f15a Compare August 11, 2026 01:58
Sways1024 added a commit to Sways1024/firstmate that referenced this pull request Aug 11, 2026
…rmed

Root cause (kunchenguid#1859's residual after the composer-classifier fix): inject_msg
types a digest once and requires backend confirmation; a submit that actually
LANDED but returned `unknown` (pane unreadable at confirm time) preserved the
buffer, and the next flush retyped the identical digest into the now-empty
composer as a duplicate turn. The upstream 16h incident measured 94 no-op
messages - 59 repeat deliveries of unchanged payloads - costing $6.90.

Fix: an unknown verdict records the typed digest's hash and time in
state/.subsuper-last-unconfirmed-inject. When the exact same digest next
reaches an affirmatively EMPTY composer within FM_INJECT_DEDUP_SECS (default
3600), the earlier Enter is treated as accepted - a swallowed Enter would
still show the text as pending and defer on the composer guard - and the
buffer clears without a retype. A different digest always types normally,
and a confirmed submit clears the marker. Suppressing the rare genuinely
lost identical digest costs nothing: the payload is by definition unchanged.

Regression: tests/fm-daemon.test.sh gains the unknown-then-identical case
(marker armed, no retype, marker cleared, different digest unaffected),
verified to fail against the previous retype behavior. All 100 daemon
assertions plus the live herdr away-mode e2e tests pass.

Upstream: kunchenguid#1859 (open; PR kunchenguid#2042 attacks the Pi
acknowledgement half, unmerged).
* Refreshed the branch past upstream's Cursor harness work, which had
  moved the same Herdr submit core this fix touches.
* Kept both new helpers rather than either side whole. Upstream added
  a rendered busy-footer read for a harness whose native agent-state
  never reports idle; this branch added the Pi identity-corroborated
  composer acknowledgement. Both sides had inserted a new function at
  the same point after fm_backend_herdr_composer_state, which is the
  only reason they collided.
* The two paths cannot shadow each other: they sit on opposite arms of
  the submit core's idle-baseline test. A Cursor pane reports native
  `blocked`, which classifies busy, so it always takes the footer arm;
  Pi reports idle and always takes the Pi arm. A non-Pi agent reaching
  the Pi arm still resolves to `pending` exactly as before.
* Merged the architecture sentence both sides rewrote so it names the
  footer fallback and the Pi acknowledgement instead of only one.
* Added Cursor to the verification record's list of harnesses whose
  submit path this fix leaves alone; that list was written before the
  Cursor harness existed and read as complete.
@zachlandes zachlandes changed the title fix(herdr): acknowledge idle Pi submissions fix(bin): acknowledge idle Pi submissions on Herdr Aug 15, 2026
# Conflicts:
#	.agents/skills/afk/SKILL.md
#	bin/backends/herdr.sh
#	docs/architecture.md
#	docs/herdr-backend.md
#	docs/verification/runtime-backends.md
#	tests/fm-backend-herdr.test.sh
@zachlandes

Copy link
Copy Markdown
Contributor Author

Closing this PR. The behavior it adds — transport-layer acknowledgement of idle Pi submissions on Herdr — already exists on main as of commit 1bb72cc (via #2647): fm_backend_herdr_composer_state is already consulted whenever native state stays idle, for every harness including Pi. The function added here introduces only an extra agent get probe per idle Pi submit and re-introduces a previously-declined narrowing, with no acknowledgement the base lacks; its new unit tests pass on base unchanged. Issue #1859's behavior is therefore already addressed on main, so this is redundant.

@zachlandes zachlandes closed this Sep 19, 2026
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