feat: Hermes Communication Officer local Discord bridge (FM-3) - #116
Closed
dnth wants to merge 11 commits into
Closed
feat: Hermes Communication Officer local Discord bridge (FM-3)#116dnth wants to merge 11 commits into
dnth wants to merge 11 commits into
Conversation
Discord /fm requests enter this home through a dedicated Hermes Gateway plugin and local inbox/outbox files. Opt-in is config/ext-bridge or FM_EXT_BRIDGE=1 plus a mode-0600 secret, not the hosted X-mode relay. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Abort the outbox posting marker on a definite send failure before a successful response so that generation can retry, and keep mid-delivery only for an ambiguous crash after the post started. If intake cannot append the wake after claiming the offer, drop the offer marker so a later intake or poll can retry. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Keep the posting marker on ambiguous transport errors so a timeout or URLError cannot double-post. Record a terminal failed marker for permanent 4xx so pending stops retrying. Abort only for transient definite failures (HTTP 429 and 5xx). Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Reuse X mode's Discord numbered-thread split (default 1900, cap 25) behind FM_EXT_DISCORD_REPLY_MAX_CHARS so the sibling bridge does not need a pairing token or hosted relay. Post chunks in order with durable per-chunk progress so a later failure cannot double-post earlier messages, and so an oversized reply is split instead of becoming a terminal HTTP 400. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Resume no longer treats JSON progress with a null inflight field as a shared claim. begin CAS-claims a per-generation inflight marker before any send, so two posters cannot both post the same remaining chunk. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
The concurrent inflight-claim case added about three seconds of wall time. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Split failure after posted chunks and a stored/split chunk-count mismatch now drop the send claim before returning, so resume is not stuck. Abort releases inflight before posting so a mid-abort crash cannot leave a stale claim that blocks the next begin. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Case 22 added about two seconds of wall time on this runner. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
begin records owner pid and claim time. A leftover inflight is stolen only when it is older than FM_EXT_INFLIGHT_TTL_SECS (default 30) and the owner pid is dead. Live posters, including past TTL, still refuse so two concurrent senders cannot both post the same chunk. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Case 23 added about two seconds of wall time on this runner. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
Claim expiry may still be zero for tests, but steal serialization must not treat a live lock as immediately reclaimable. Two concurrent stealers therefore cannot both take the send right. Co-authored-by: Dickson Neoh <dickson.neoh@gmail.com>
dnth
added a commit
that referenced
this pull request
Sep 7, 2026
* feat: Hermes Communication Officer local Discord bridge (FM-3) Squashed rebase of PR #116 onto current main, unmodified, as the base for the review fixes that follow. Original work by dnth on branch cursor/fm-ext-hermes-comms-mvp-e478 (head 43140b8). Claude-Session: https://claude.ai/code/session_01MmS2mAcduG9zy5HD3pUGuV * fix(ext): recover wedged replies, bound poll cost, and tighten bridge auth Addresses the scout review of PR #116. The core-infra edits it found clean are unchanged in behavior; every fix below is in the ext-bridge seam. Recoverable replies. An ambiguous mid-chunk send (URLError, TimeoutError, OSError, HTTP 408, unparseable 200) deliberately leaves the chunk recorded in-flight so it cannot double-post, but nothing cleared it: one network timeout wedged that reply forever, and neither restart, wait, nor release could recover it. fm_ext_outbox_begin now consults a bounded recovery first. Past FM_EXT_MIDDELIVERY_RECOVERY_SECS (300, far above the 15s send timeout) it reopens exactly that chunk for another attempt; after FM_EXT_MIDDELIVERY_RECOVERY_MAX (3) attempts it records the terminal failure, exits 5, and wakes firstmate. A timeout now costs a repeated chunk or a surfaced failure, never a silent truncation. Bounded poll and retention. A delivered payload is retired as soon as its generation is terminal, so `pending` scans only pending work instead of every reply ever sent, and marker names are matched without a basename process each. Terminal markers stay in place so a duplicate emit is still idempotent, which is why begin now answers a terminal outcome before requiring the payload. ext-context records and retired outbox markers expire on the same seven-day window X mode uses, swept from the poll the way fm-x-poll.sh sweeps its own. Least-privilege auth. Admission and authority are now separate: only a <guild>:<channel>:<author> rule grants standing authority to act on project work, while a guild-only or channel-only rule admits a request that needs the captain's confirmation before any project change. Malformed rules (an empty component, a fourth component) are ignored rather than guessed at. The plugin's parallel Python allowlist is deleted; bin/fm-ext-lib.sh is the single owner and the plugin resolves through fm-ext-intake.sh, which exits 3 on refusal. This settles the three rule shapes the two copies disagreed on, all in the deny direction. One opt-in authority. config/ext-bridge is the only way to activate the bridge; FM_EXT_BRIDGE can now only disable one. The plugin no longer forces that variable on, so installing it can no longer switch the intake half of a home on while its bootstrap and watcher halves believe the bridge is off. Durable wake before consumption. fm-ext-poll.sh appends its own wake record per claimed offer and releases the claim when that append fails, mirroring fm-ext-intake.sh. A watcher that dies between claim and append no longer drops the request silently, and the watcher does not append a second record. deliver_one treats a retired payload as an outcome rather than raising, so a sibling poster retiring it mid-pass cannot abort a whole drain. Tests: ten new cases (24-33) covering wedge recovery and its surfaced exhaustion, pending flatness and retention, shell/plugin allowlist agreement over ten rule shapes, the authority levels and every fail-closed allowlist, the secret gate on mismatched/0644/symlinked/empty/missing secrets, opt-in consistency, poll unclaim on wake failure, and inertness without opt-in. The fixture now models the recommended author-scoped rule. 33/33 pass; bin/fm-lint.sh and bin/fm-doc-audience-check.sh clean. Claude-Session: https://claude.ai/code/session_01MmS2mAcduG9zy5HD3pUGuV * no-mistakes(review): Replace source grep and add case-variant behavioral probes * no-mistakes(review): Serialize recovery progress update before inflight release * no-mistakes(review): Recover pre-send wedges without progress artifacts * no-mistakes(review): Validate inbox artifacts before retaining bridge context * no-mistakes(review): Honor state overrides and remove source-only spawn assertions * no-mistakes(review): Recover only inactive in-flight deliveries using heartbeats * no-mistakes(test): Fixed bridge test harness defects; targeted suite passes * no-mistakes(document): Update heartbeat-based recovery documentation
Owner
Author
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.
Summary
fm-ext-*inbox/wake/outbox), not hosted relay / FMX / pending-reply./fmplugin template undercontrib/hermes-gateway-firstmate-comms/(dedicated gateway profile; crewmate TUI unchanged).FM_EXT_DISCORD_REPLY_MAX_CHARS), exclusive inflight claim, TTL/steal (dead pid + age) with steallock floormax(ttl,1).tests/fm-ext-bridge.test.sh.Captain decisions applied
.inflight(30s + dead pid)Test plan
tests/fm-ext-bridge.test.sh(26/26 at tip)Do not merge until captain word via Firstmate.