fix: herdr 0.7.5 compatibility — send-target resolution + event-stream model#13
Conversation
…efuses (herdr 0.7.5 compat) (#25) * fix: resolve terminal-id send targets via agent.list when agent.get refuses Herdr 0.7.5 (auto-update 2026-07-21) stopped resolving terminal-id targets through agent.get; agent.list still publishes terminal_id -> pane_id. On a definite HerdrErrorResponse for a terminal_id binding, fall back to the listing before terminalizing. Live outage: every Telegram->pane command rejected 'could not resolve the private send target' for ~3h; verified fixed E2E (31s round trip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: regression for agent.list fallback on terminal-id resolution Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…elopes) onto upstream main Phase-2 threading stripped; 4 upstream subscription-shape tests still assert the pre-0.7.5 shape and need updating to the mixed model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…; port compat regressions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
plotarmordev
left a comment
There was a problem hiding this comment.
Thank you for the production report and for keeping the compatibility port separate from the Phase 2 turn-model work. I verified the core report against the official Herdr v0.7.5 tag: agent.get no longer accepts terminal IDs, pane.updated exists, and the two parameterized subscription variants require their additional fields. The proposed direction is correct, and routing pane.updated only to turn refresh is important for stable-owner continuity.
I am requesting changes before merge for the following compatibility and fail-closed gaps.
1. Standalone pane-turn ingestion remains broken on Herdr 0.7.5
Config.herdr_bin still defaults to herdr, and _read_private_turn() still executes:
herdr pane turn <pane> --last --format json
That command is absent from Herdr v0.7.5. A Herdres-managed installation avoids this only because Herdres installs and configures herdr_turn_adapter.py; standalone Tendwire users retain the default bare Herdr path and will lose pane-backed turn and pending-decision observation.
Please either:
- provide a Tendwire-owned compatible adapter/path for standalone installs; or
- narrow this PR's stated compatibility scope and add an explicit, release-blocking follow-up with capability detection and a fixed degraded outcome.
This should not silently remain a successful-looking default configuration.
2. The 0.7.4 fallback fails when there are no current pane IDs
In _subscribe_event_stream(), rejection of the v0.7.5 mixed request does this:
if not self._subscription_pane_ids:
raiseAn older Herdr with an empty workspace therefore rejects pane.updated, Tendwire declines fallback, marks the event backend as a protocol error, and repeatedly reconciles/reconnects. I reproduced this directly with an empty pane set and a 0.7.4-style rejection.
Please add a bounded global-only legacy subscription for the zero-pane case. It must continue receiving lifecycle events such as pane.created, allowing a new pane to become visible without a degraded reconnect loop. Add an exact regression for old-version rejection with zero panes.
3. agent.list fallback must require one unique exact mapping
_private_pane_id_for_binding() currently returns the first row whose terminal_id matches. With two conflicting rows it silently selects the first pane. I reproduced a two-row response returning p1 and ignoring p2.
Even though valid Herdr state should keep terminal IDs unique, this is an authoritative protocol boundary and must fail closed on malformed or ambiguous data. Please:
- require an actual list of mapping objects;
- collect all exact terminal-ID matches;
- accept only one unique non-empty pane ID;
- reject conflicting or malformed matches without attempting a send;
- cover zero, one, duplicate-identical, conflicting, and malformed results.
Please preserve the current pre-send disposition contract: transport/protocol uncertainty must remain retryable, while a valid authoritative list proving no matching target may remain terminal.
4. Empty-ID error tolerance is global rather than subscription-scoped
validate_response() now accepts every {"id":"", "error":...} response, and _read_response() assigns it to whichever synchronous request is active. This weakens request correlation for list, command, and other socket calls even though the compatibility exception is needed only while negotiating the v0.7.5 subscription schema.
Please keep ordinary response validation strict and scope the exception to the subscription negotiation path and expected schema-rejection envelope. The compatibility path should use a fresh/known connection and close it before reconnecting for the fallback, so an uncorrelated or delayed error cannot be attributed to another operation. Add regressions proving empty-ID errors remain rejected for ordinary requests.
Additional issue noted in #12
The edge C0/C1 observation finding is real but is not addressed here. Current command input rejects these bytes, while current turn-origin matching does not remove terminal framing bytes at the outer edge, so an observed "hello\\u0001" can fail to match submitted "hello". The fork's referenced fix targets its Phase 2 normalizer and cannot be copied directly onto current main. Please keep this as a separate small compatibility correction (or add it here without importing Phase 2): strip only edge C0/C1 framing bytes in the current matching normalizer, preserve interior controls as significant, and add collision/fail-closed regressions.
Verification
The current GitHub check is red with one unrelated, known suite-order FD snapshot assertion (2881 passed, 1 skipped, 1 failed). After the changes, please rerun the focused command/event/protocol/socket suites and obtain a green required check. No Phase 2 submission-ledger code should be added to this PR.
Once the four blocking items above are resolved, this compatibility patch should be valuable and appropriate to merge before Tendwire officially supports or deploys against Herdr v0.7.5.
|
Excellent review — thank you, especially for reproducing (2) and (3). All five points accepted: fix round in progress on this branch (empty-installation global-only fallback, fail-closed ambiguous terminal lookup, negotiation-scoped id-less tolerance, standalone pane-turn coverage, and the framing-byte adaptation to _turn_merge_match_text). Points 2–4 were latent in our production fork as well — we're hardening there in parallel, so the review paid off twice. Will re-request review when the branch is green. |
…on fallback, fail-closed terminal lookup, negotiation-scoped id tolerance, framing-byte match adaptation Addresses all five points of plotarmordev's review on plotarmordev#13. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review round pushed — all five points addressed: (1) standalone pane-turn socket fallback when the CLI subcommand is missing; (2) empty-installation rejection now falls back to a healthy global-only 0.7.4 subscription (your repro added as a regression); (3) terminal-id lookup fails closed on duplicate matches; (4) id-less error tolerance scoped to subscription negotiation only — ordinary requests keep strict correlation (regression included); (5) edge-C0/C1 strip adapted to _turn_merge_match_text with ported tests. 414 targeted green; full suite rerun in progress. Ready for re-review. |
|
Merged in The merged result preserves the contributor's Herdr 0.7.5 work:
Three integration changes were made before merging:
Verification:
Thank you for the production report and the compatibility port. The event-model and terminal-target changes are now active in the main deployment. |
As offered in #12 — the herdr 0.7.5 compat fixes, adapted to your main (no fork-specific code):
agent.getno longer resolves terminal-id targets (authoritative error → every submit terminally rejected pre-send). On that specific failure, fall back toagent.list'sterminal_id → pane_idmapping before terminalizing; other binding kinds and transport errors keep existing semantics. Regression included.pane.updated. This subscribes with the 0.7.5 mixed model (global lifecycle entries + one status entry per pane), falls back to the 0.7.4 shape on rejection, and routes id-less error envelopes to the compat path instead of dying. Upstream's subscription-shape tests updated to the new semantics; compat regressions ported. One caveat encoded in the tests from production experience:pane.updatedfeeds turn refresh ONLY — letting it re-project worker meta changes stable-key derivation downstream (cost us an outage).Full suite green on this branch (2069 passed; one pre-existing order-dependent flake in test_local_state_permissions passes in isolation, and the sidecar benchmark needs its env root as usual). Production-verified on the jerryfane fork against live herdr 0.7.5.
🤖 Generated with Claude Code