Skip to content

feat(bin): add trusted carbon peer relay to the primary firstmate - #8

Closed
morris2spears wants to merge 5 commits into
mainfrom
fm/firstmate-peer-relay-carbon
Closed

feat(bin): add trusted carbon peer relay to the primary firstmate#8
morris2spears wants to merge 5 commits into
mainfrom
fm/firstmate-peer-relay-carbon

Conversation

@morris2spears

Copy link
Copy Markdown
Owner

Intent

The captain wants trusted inbound relay from the concrete carbon Claude Code orchestrator session to the live primary Firstmate on mirage, using the existing SSH fleet-key trust boundary and the established durable external-channel pattern. A carbon-side client must capture its exact originating tmux pane, stable window and session identity, hostname, and timestamp; SSH the raw request into the primary home; durably record private request metadata, message, and pending/resolved delivery state; surface ids only through an authenticated check wake and durable wake queue; let a new agent-only fmpeer-respond skill act with the same authority as direct captain input; and return a concise response over ssh carbon to that exact pane via one literal tmux send-keys followed by a separate Enter, resolving only on confirmed delivery and refusing blind retries after uncertainty. Keep this pass carbon-focused without a general peer registry or new pairing-token authentication, do not alter X or Telegram mode behavior, document activation and mechanics proportionately, add real mocked regression coverage for receive, wake integration, and reply command shape, and leave actual carbon deployment and Telegram completion notification to Firstmate after the green PR.

What Changed

  • Added a carbon-side client (bin/fm-peer-relay-tell.sh) that captures the originating tmux pane, window/session identity, hostname and timestamp and SSHes the raw request into the primary home, where bin/fm-peer-relay-receive.sh durably records private request metadata, message and pending/resolved state (bounded ingest read and message cap) and wakes the watcher.
  • Added bin/fm-peer-relay-poll.sh to surface ids only via authenticated peer-relay-request check wakes (plus a peer-relay-error wake for stranded offers) with bounded re-offer and retention pruning, bin/fm-peer-relay-reply.sh to inspect a request and send a reply back over ssh carbon to that exact pane as one literal tmux send-keys followed by a separate Enter - resolving only on confirmed delivery and refusing retries after uncertainty - and shared helpers in bin/fm-peer-relay-lib.sh, wired into bootstrap, session start and supervision.
  • Added the agent-only fmpeer-respond skill, docs/peer-relay.md plus activation/tunable notes in AGENTS.md and the architecture, configuration, scripts and turnend-guard docs, and 13 mocked regression cases in tests/fm-peer-relay.test.sh covering receive, poll, wake integration, reply command shape and delivery uncertainty. X and Telegram mode behavior is untouched.

Note: the one failing case in tests/fm-session-start.test.sh (lock concurrency) reproduces on the base commit and is pre-existing, not a regression from this branch.

Risk Assessment

✅ Low: This round changes only AGENTS.md, the fmpeer-respond skill, and two docs to route the already-implemented peer-relay-error wake and document the retention tunable, matching the established X/Telegram routing pattern with no executable code touched and every earlier accepted fix verified in source.

Testing

Ran the new peer-relay regression suite (all 13 cases green) plus the touched adjacent suites, then proved the intent end-to-end outside of unit tests: a real tmux pane acting as the carbon orchestrator sent a captain message through the client, the mirage receiver stored a private 0600 durable record, the authenticated watcher surfaced only the request id into the durable wake queue with no body leakage, and the reply came back over the ssh-carbon leg as one literal send-keys plus a separate Enter into that exact pane, flipping the record from pending to resolved and refusing a second blind send. Evidence is a CLI/terminal transcript and a tmux pane capture rather than a screenshot because the changed surface is shell tooling and a terminal pane, with no rendered UI.

Evidence: End-to-end carbon peer relay transcript (client send → durable record → wake queue → reply into exact pane → resolved → retry refused)

=== carbon orchestrator pane is %0 (session orchestrator, window @0) ===

=== captain types in the carbon pane: fm-peer-relay-tell.sh 'ship the relay ...' ===
request id returned to carbon: peer-1785607622-gQMPl4

=== mirage: durable private request record ===
total 24
-rw------- 1 morris staff 50 Aug 1 14:07 message
-rw------- 1 morris staff 163 Aug 1 14:07 meta
-rw------- 1 morris staff 39 Aug 1 14:07 status

=== mirage: FIRSTMATE inspects the request (fm-peer-relay-reply.sh inspect) ===
version=1
request_id=peer-1785607622-gQMPl4
origin_host=carbon
pane_id=%0
session_name=orchestrator
window_id=@0
client_epoch=1785607622
received_epoch=1785607622
state=pending
updated_epoch=1785607622
--- message ---
firstmate: what is the status of the carbon relay?
=== mirage: authenticated watcher surfaces ids only and queues a durable check wake ===
check: /var/folders/z5/m852gsm10ts0cd8hmbk2jjcw0000gn/T/no-mistakes-evidence/01KYZ6CTRXV13947WSTWE862R1/e2e/mirage-home/state/peer-relay-watch.check.sh: peer-relay-request peer-1785607622-gQMPl4

=== durable wake queue on mirage ===
1785607626	1	check	/var/folders/z5/m852gsm10ts0cd8hmbk2jjcw0000gn/T/no-mistakes-evidence/01KYZ6CTRXV13947WSTWE862R1/e2e/mirage-home/state/peer-relay-watch.check.sh	check: /var/folders/z5/m852gsm10ts0cd8hmbk2jjcw0000gn/T/no-mistakes-evidence/01KYZ6CTRXV13947WSTWE862R1/e2e/mirage-home/state/peer-relay-watch.check.sh: peer-relay-request peer-1785607622-gQMPl4

=== mirage: firstmate answers per the fmpeer-respond skill and delivers over ssh carbon ===
resolved peer-1785607622-gQMPl4

=== mirage: request status after confirmed delivery ===
state=resolved
updated_epoch=1785607626

=== carbon pane as the captain sees it (tmux capture-pane) ===
2R1/bin/fm-peer-relay-tell.sh 'firstmate: what is the status of the carbon relay?' > /var/folders/z5
/m852gsm10ts0cd8hmbk2jjcw0000gn/T/no-mistakes-evidence/01KYZ6CTRXV13947WSTWE862R1/e2e/client.out 2>&
1; echo client-exit=$?
client-exit=0
┌──(morris㉿mirage)-[~/.no-mistakes/worktrees/a924505d3f87/01KYZ6CTRXV13947WSTWE862R1]
└─$ Captain, the carbon relay is live: your message arrived on mirage as request peer-1785607622-gQM
Pl4 and this reply came back to pane %0.
zsh: command not found: Captain,
┌──(morris㉿mirage)-[~/.no-mistakes/worktrees/a924505d3f87/01KYZ6CTRXV13947WSTWE862R1]
└─$                                                                                           127 ⨯

=== carbon pane: a blind retry after delivery is refused ===
error: peer relay request is resolved; send is allowed only while pending
retry-exit=1
Evidence: Carbon tmux pane capture showing the delivered reply

└─$ PATH=... fm-peer-relay-tell.sh 'firstmate: what is the status of the carbon relay?' ... client-exit=0 └─$ Captain, the carbon relay is live: your message arrived on mirage as request peer-1785607622-gQMPl4 and this reply came back to pane %0.

2R1/bin/fm-peer-relay-tell.sh 'firstmate: what is the status of the carbon relay?' > /var/folders/z5
/m852gsm10ts0cd8hmbk2jjcw0000gn/T/no-mistakes-evidence/01KYZ6CTRXV13947WSTWE862R1/e2e/client.out 2>&
1; echo client-exit=$?
client-exit=0

┌──(morris㉿mirage)-[~/.no-mistakes/worktrees/a924505d3f87/01KYZ6CTRXV13947WSTWE862R1]
└─$ Captain, the carbon relay is live: your message arrived on mirage as request peer-1785607622-gQM
Pl4 and this reply came back to pane %0.
zsh: command not found: Captain,

┌──(morris㉿mirage)-[~/.no-mistakes/worktrees/a924505d3f87/01KYZ6CTRXV13947WSTWE862R1]
└─$                                                                                           127 ⨯
Evidence: Mirage-side durable state after delivery
state=resolved
updated_epoch=1785607626

$ bin/fm-peer-relay-reply.sh send <id> --text-file reply.txt # second attempt
error: peer relay request is resolved; send is allowed only while pending
retry-exit=1
- Outcome: ⚠️ 1 warning across 1 run (4m35s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 5 issues found → auto-fixed (2) ✅
  • ⚠️ bin/fm-peer-relay-poll.sh:48 - The peer poll silently skips a request whose offer marker is not a valid private artifact (line 48-50) and also swallows publish failures (lines 44 and 54) with a bare continue. The established sibling channel, bin/fm-tg-poll.sh:127, treats exactly this state as tampering and surfaces it once via claim-error. Here the request stays state=pending forever, never re-offers, and no diagnostic reaches the captain - contradicting the fmpeer-respond claim that the bounded offer window prevents a stranded message. Reachable path: any leftover/tampered state/peer-relay/offered/&lt;id&gt; with the wrong mode, or a transient failure inside fmx_private_artifact_publish_stdin during the re-offer, permanently silences that captain request. Recommend mirroring the tg poll's error-surfacing so a stuck relay is visible rather than silent.
  • ℹ️ bin/fm-peer-relay-receive.sh:125 - cat &gt; &#34;$STAGE/message&#34; writes the entire SSH stream to disk before the 1..65536-byte cap is checked at line 132, so a malformed or buggy client call (e.g. piping a large file into --stdin bypassed, or a direct receive invocation) can write unbounded data into the primary home's state directory before rejection. Bounding the read (e.g. head -c 65537) enforces the documented cap at ingest.
  • ℹ️ bin/fm-peer-relay-poll.sh:26 - Nothing ever prunes state/peer-relay/requests/&lt;id&gt;/. Resolved and delivery-uncertain records keep the raw message and reply bodies indefinitely, and every poll cycle re-walks and re-stats all of them (4+ stat calls each via fmpeer_request_dir_valid). Telegram mode prunes its stale offer markers; the peer relay has no equivalent retention step, so cost and stored private text grow without bound.
  • ℹ️ bin/fm-peer-relay-receive.sh:160 - Removing the shared state/.last-check marker makes every registered check due on the next watcher tick, not only the peer poll, so each inbound peer message also triggers an early merged-PR/declined-PR gh sweep. It is bounded to one extra sweep per message and is documented in the surrounding comment; noting the wider blast radius of the wake mechanism only.
  • ℹ️ bin/fm-peer-relay-reply.sh:90 - The default remote client is the absolute, user-specific path /Users/morris/.local/bin/fm-peer-relay-tell.sh, while docs/peer-relay.md describes the install location as ~/.local/bin/.... It is overridable via FMPEER_REMOTE_CLIENT and asserted verbatim in tests, so this is a deliberate pin rather than a defect, but it silently breaks if the carbon account home ever differs.

🔧 Fix: surface stranded peer offers, bound relay retention and ingest
3 issues (1 warning, 2 infos) still open:

  • ⚠️ AGENTS.md:501 - The fix adds a new check-wake payload peer-relay-error &lt;message&gt; (bin/fm-peer-relay-poll.sh:40) but no instruction surface routes it. AGENTS.md:501 and .agents/skills/fmpeer-respond/SKILL.md:5,15 still name only peer-relay-request &lt;id&gt; ..., while both siblings explicitly route their error variant (AGENTS.md:499 x-mode-error ... -> fmx-respond, AGENTS.md:500 tg-mode-error ... -> fmtg-respond). Concrete path: a tampered or wrong-mode state/peer-relay/offered/&lt;id&gt; makes the poll emit peer-relay-error cannot record peer relay offer once, then emit_error_once deduplicates it forever; with no skill trigger and no handling step in fmpeer-respond's Failure and recovery section, that single wake can be read past and the captain's request stays pending and unsurfaced - the exact stranding the fix was accepted to eliminate. docs/peer-relay.md:54 also still asserts the poll emits only peer-relay-request &lt;id&gt; [&lt;id&gt;...], contradicting the new line 57.
  • ℹ️ docs/configuration.md:475 - FMPEER_RETENTION_SECS is described in docs/peer-relay.md:61 but is absent from the environment tunables list in docs/configuration.md, where its sibling FMPEER_REOFFER_SECS=1800 was added at line 475. An operator scanning that list sees the re-offer knob but not the retention knob.
  • ℹ️ bin/fm-peer-relay-poll.sh:19 - fmpeer_retention_prune runs only from the poll, which exits at line 19 when the relay is not enabled, and bootstrap's opt-out path removes only the check/trust artifacts. Removing config/peer-relay-carbon therefore freezes state/peer-relay/ - including raw captain request and reply text - with no remaining pruner. This matches the existing Telegram opt-out convention (bin/fm-bootstrap.sh tg_mode_setup leaves state/tg-offered behind), so noting the tradeoff rather than a defect.

🔧 Fix: route peer-relay-error wakes and document retention knob
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 warning
  • ⚠️ tests/fm-session-start.test.sh:681 - tests/fm-session-start.test.sh case "concurrent session-lock acquisition admits exactly one live harness" fails on this machine (0 winners out of 40 concurrent fm-lock.sh acquirers). Reproduced identically on the base commit e645d1e with a clean archive checkout, so it is a pre-existing environment/flakiness issue in the macOS lock concurrency test, not a regression from this change. Every other case in that file passes.
  • ./bin/fm-test-run.sh tests/fm-peer-relay.test.sh - all 13 peer-relay cases pass (receive, poll, wake integration, reply command shape, uncertainty/no-retry)
  • ./bin/fm-test-run.sh tests/fm-bootstrap.test.sh tests/fm-session-start.test.sh tests/fm-supervision-instructions.test.sh tests/fm-documentation-audiences.test.sh tests/fm-x-mode.test.sh tests/fm-tg-mode.test.sh tests/fm-test-run.test.sh - 6/7 pass; session-start lock-concurrency case fails
  • Base-commit control run: git archive e645d1e into a temp dir, then ./bin/fm-test-run.sh tests/fm-session-start.test.sh - same failure, confirming it is pre-existing
  • Manual end-to-end relay on a private tmux server (tmux -L fmpeer-demo): sent bin/fm-peer-relay-tell.sh &#39;firstmate: what is the status of the carbon relay?&#39; from inside real pane %0 with stubbed hostname/ssh bridging to a mirage-side FM_HOME
  • bin/fm-peer-relay-reply.sh inspect &lt;id&gt; on mirage showing origin_host/pane_id/session_name/window_id/client_epoch/received_epoch and state=pending
  • bin/fm-watch.sh (bounded) on the mirage home - emitted peer-relay-request &lt;id&gt; only, with the same ids-only line persisted in state/.wake-queue
  • bin/fm-peer-relay-reply.sh send &lt;id&gt; --text-file … over the ssh-carbon leg, then tmux capture-pane on pane %0 to confirm literal reply text + separate Enter landed
  • Repeat send after resolution - refused with peer relay request is resolved; send is allowed only while pending (exit 1)
⚠️ **Document** - 1 info
  • ℹ️ README.md:52 - README.md was intentionally left untouched: it introduces user-facing product features (optional X and Telegram modes), while the carbon peer relay is a captain-machine-specific operator channel whose activation and mechanics are owned by docs/peer-relay.md and docs/configuration.md. The doc-audience check requires no README routing for it. Flagging as a judgment call in case a README mention is wanted.
🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 127)

🔧 Fix: no lint changes needed; shellcheck run clean
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

@morris2spears

Copy link
Copy Markdown
Owner Author

closed. claude on carbon will raise issues on repos that firsmate on mirage will tackle. no further action needed.

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