Skip to content

Reach a paired computer through the relay sync uses - #2073

Merged
santoshkumarradha merged 3 commits into
devfrom
fix/2071-hosted-relay-serve
Oct 30, 2026
Merged

santoshkumarradha merged 3 commits into
devfrom
fix/2071-hosted-relay-serve

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Fixes #2071.

Summary

A machine whose chats already sync can now run codeaf serve with no settings and be reached with codeaf chat --at <name>, through the same relay sync uses.

 codeaf serve / chat --at
-  relay = --via | CODEAF_RELAY | v3/remote/relay file | (none → "no sync address is set up")
+  relay = --via | CODEAF_RELAY | v3/remote/relay file | the relay sync resolves
+          (CODEAF_SYNC_URL, the relay a pairing saved, the hosted relay)

 relay.upgrade(base, door)
-  GET <host>/v1/engine            (base path dropped → 522 on the hosted relay)
+  GET <base>/v1/engine            (wireauth.Endpoint, as every other client)
-  Upgrade: aforge-relay/1         (a Cloudflare Worker cannot answer this)
+  WebSocket, subprotocol aforge-relay/1, one carrier frame per message
+    426 from an older relay → raw Upgrade: aforge-relay/1, as before
sequenceDiagram
    participant Serve as codeaf serve (machine A)
    participant W as Hosted relay Worker
    participant DO as MachineDO (one per machine name)
    participant At as chat --at (machine B)
    Serve->>W: WebSocket /fabric/v1/engine
    W->>DO: idFromName(name)
    DO-->>Serve: challenge → proof → verdict (handshake.go, ported)
    At->>W: WebSocket /fabric/v1/dial/<name>
    W->>DO: same object
    DO-->>Serve: opOpen; then opData both ways (payload never read)
Loading

What changed:

  • internal/relay: base path kept; WebSocket carrier with raw fallback; codeaf relay accepts both carriers; the machine pings over the WebSocket so a hosted relay can stay asleep; frames reuse one buffer and go out in one write; a non-101 answer becomes a fact (ErrNoDoor, ErrUnreachable for 5xx, ErrTooMany for 429) instead of an upstream body.
  • internal/pair, cmd/codeaf: Relay/RelayFor/Reach.Sync take sync's answer as the last fallback. codeaf serve says lost sync — trying again once per outage, and waits out its own stale name or a redeploying relay instead of quitting.
  • relay/hosted: /v1/engine and /v1/dial/<name> served by MachineDO (new MACHINE binding, migration v4-machines). Every socket hibernates; routing lives in socket tags and attachments. A live name is never taken over; a name whose socket no longer answers a ping within 5 s is replaced. Stream ceiling 16 (429), and 30 dials / 10 registrations a minute per network and name.
  • Manual: Reaching a machine with a pairing code no longer says no relay is running; it documents the relay order, the outage line and the new refusal.

Evidence

Before (issue): codeaf serve with no settings → no sync address is set up on this machine…; with the hosted address → Error: error code: 522.

After, two separate codeaf homes standing for two computers, model deepseek/deepseek-v4.1-flash for every role (the call log on the serving machine records only that model for turn, memory, router and reflex):

Acceptance Result
Serve works with only sync's address this machine is reachable as mustang-cactus-73 + pairing code, keeps running
--at from a second computer paired with the code, approved on the first machine, reply MARKER-A-… ran-in-wsA: 2 tool calls ran in the first machine's folder
A project path --at name:<path>/sub/project → MARKER-PROJECT-… read in that folder
Dropped connection relay stopped: lost sync — trying again (once); relay back: name and code printed again, --at answers, serve never restarted
A silent old socket first serve frozen, a new serve under the same key took the name after the probe; --at reached it
Base path kept TestARelayAddressKeepsItsPath (/fabric, /a/relay, with and without slash, and root)
Sync and serve agree TestServeAtPairingAndSyncResolveOneRelay (variable, saved pairing relay, hosted default)
Failures explained against the relay without machine doors: the service at https://codeaf.agentfield.ai/fabric syncs chats but does not connect machines — run your own with codeaf relay …; a dead address: … cannot be reached from here — check this machine's network …; TestUpstreamFailuresBecomeFacts pins that error code: 522 is never shown
Self-hosted keeps working codeaf relay → serve + --at round trip with a tool call; TestTheCarrierCrossesAWebSocketOnlyFrontAndAnOlderRelay covers a raw-only (older) relay

The two-computer runs were done against wrangler dev of relay/hosted with BASE_PATH=/fabric, against a staging deployment of this branch on Cloudflare, and against codeaf relay. internal/relay/hosted_test.go (CODEAF_RELAY_HOSTED_URL=<relay>) passes on the staging deployment: round trip, a 400 KB echo, several surfaces, a live name refused, a freed name reusable, an unknown name, the stream ceiling as busy.

Tests: go test -race ./internal/relay, ./internal/pair, ./internal/relayserve, ./cmd/codeaf focused; make test-quick; make pr-ready; relay/hosted npm test (including test/engine.test.js, which pins the JS name derivation and possession proof to values the Go code computes); make test-packed-manual manual-gates; relay conformance against the staging deployment.

Merge Danger

Door: two-way for the code; one-way for the hosted deployment that follows.

The client change is backward compatible: a new client falls back to the raw upgrade on 426, and an old client keeps using the raw upgrade with codeaf relay. The hosted relay is currently an older build; deploying this branch to it also ships the directory upgrade already on dev, which upgrades each identity in place and cannot be rolled back (see relay/hosted/README.md). The deploy is a separate step after merge: staging first, then production with the previous version id recorded.

Blast Radius: remote.

codeaf serve, chat --at and pairing through a relay. Sync's store and directory routes are unchanged. Limits: a dial to a machine that is not connected waits for the surface's own handshake timeout; a second codeaf serve started while the first is in an outage keeps the name, and the first keeps saying it lost sync until it is closed; the saved v3/remote/relay file still overrides sync's relay, as an explicit choice.

🤖 Generated with Claude Code

santoshkumarradha and others added 3 commits October 30, 2026 10:30
codeaf serve and chat --at fall back to the relay sync resolves, the relay
client keeps the relay address's path, the carrier rides a WebSocket (with a
fallback to the raw upgrade for older relays), the hosted relay gains the
machine doors, and upstream failures become plain sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full machine answers 429 on both relays, a registration being decided is
not taken over, a closed stream is not closed twice, a running serve waits
out a redeploying relay, frames reuse one buffer, the door paths are one
constant, per-address limits match codeaf relay, and the hosted README
lists the binding, the migration and the doors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@santoshkumarradha
santoshkumarradha marked this pull request as ready for review October 30, 2026 15:26
@santoshkumarradha
santoshkumarradha merged commit 34ced44 into dev Oct 30, 2026
8 checks passed
@santoshkumarradha
santoshkumarradha deleted the fix/2071-hosted-relay-serve branch October 30, 2026 15:31
AbirAbbas added a commit that referenced this pull request Oct 30, 2026
…2076)

Relay streams get deadlines so silent dials and a lone pair byte end at 30 s; only the same key may replace a silent registration; the hosted front limits each caller (30 dials, 10 registrations a minute) and refuses impossible names before any object exists; serve backs off properly and answers lost sync with a back line; the upgrade honours proxies and leaks nothing on 426; upstream bodies become one-sentence facts. Hand-tested against the Worker under wrangler dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Reaching a paired computer without ssh fails on the hosted relay

1 participant