DEVELOPMENT DOCUMENTATION / JOIN A RELAY
Connect your computer to a relay someone already runs.
For a tester whose operator already runs a relay, such as the project's alpha relay at agentuplink-relay.fly.dev. You install the device half on Linux or macOS, get a certificate, connect, and call the synthetic echo. You do not run a relay, Redis or an identity issuer.
To run your own relay, follow Setup instead. Windows cannot create or import device credentials in this alpha.
What you exchange with the operator
| When | From | What |
|---|---|---|
| Before you start | Operator | The device URL, for example wss://agentuplink-relay.fly.dev:9443/v1/tunnel/control, and the consumer URL, for example https://agentuplink-relay.fly.dev. |
| Before you start | Operator | The relay CA certificate, relay-ca.pem. The relay uses a private CA. |
| Before you start | Operator | Your device UUID, your echo service UUID, and the release tag to install. |
| Step 3 | You | Your certificate signing request, device.csr. Never the private key. |
| Step 4 | Operator | Your signed device certificate, once your user, device, service and grant are in the relay catalog. |
| Step 6 | Operator | A consumer token, or access to their issuer, with your user's sub and echo:invoke in its scope. |
1. Download and verify
Download the release your operator names from GitHub releases. On an Apple silicon Mac:
mkdir -p ~/agentuplink && cd ~/agentuplink
gh release download TAG -R andymac4182/agentuplink -p '*aarch64-apple-darwin*'
shasum -a 256 -c agentuplink-*.tar.gz.sha256
mkdir release && tar -xzf agentuplink-*.tar.gz -C release && cd release
cat release.json
export PATH="$PWD/bin:$PATH"
tunnel-client --version
On Linux use the x86_64-unknown-linux-gnu archive and sha256sum -c. The checksum proves integrity, not authorship. Releases built after the release-hardening change also carry a build attestation: gh attestation verify agentuplink-*.tar.gz -R andymac4182/agentuplink --signer-workflow andymac4182/agentuplink/.github/workflows/release.yml --source-ref refs/heads/main checks that the file was built by this repository's release workflow on main (older releases answer HTTP 404); see Downloads for what that does and does not prove. The archive has no top-level folder, which is why it is unpacked into release/; archives built after M6-C50 also carry docs/. README.txt in newer archives says to start with docs/operator.md: that guide is for running a relay, so follow this page instead. release.json names the source commit; archives built after M6-C102 carry every example their documents name; older ones hold only m1-client.toml and m1-relay.toml. On macOS, a browser download is quarantined: after the checksum passes, run xattr -dr com.apple.quarantine bin. Keep the three binaries together. The --help heading of releases up to f9f7abf is stale ("M1 connector"); in those releases a subcommand's --help exits 2; releases built after the D6 fix exit 0.
2. Write the device profile
mkdir -m 700 ~/agentuplink-device
cp examples/m1-client.toml ~/agentuplink-device/client.toml
cp /path/to/relay-ca.pem ~/agentuplink-device/relay-ca.pem
Edit client.toml and change three values, keeping the rest of the example:
device_id = "DEVICE_UUID"
relay_url = "wss://RELAY_HOST:9443/v1/tunnel/control"
[exports."SERVICE_UUID"]
type = "echo"
device_canary = "my-device"
The device and service UUIDs must be exactly the operator's. device_canary is any short string the echo returns before your payload. Then run tunnel-client config check --config ~/agentuplink-device/client.toml and expect Runtime client configuration is valid.
3. Create your key and request
tunnel-client credentials create --config ~/agentuplink-device/client.toml --csr-out device.csr
Paths resolve from the profile's directory, so this writes ~/agentuplink-device/device.csr and an owner-only key at ~/agentuplink-device/credentials/device-key.pem. Send the operator device.csr only.
4. Import your certificate
Save the certificate you receive as ~/agentuplink-device/device-cert.pem:
tunnel-client credentials import --config ~/agentuplink-device/client.toml --certificate device-cert.pem --server-ca relay-ca.pem
tunnel-client doctor --config ~/agentuplink-device/client.toml
Expect Imported 1 client certificate(s) and 1 server CA certificate(s). and a healthy local doctor, exit 0. Its second line, Supervisor IPC: not implemented in this operations slice., is expected, not an error. doctor does not contact the relay. An import refusal (X.509 v1, no device SAN, another device, or a key mismatch) needs the operator to sign again.
5. Connect
tunnel-client connect --config ~/agentuplink-device/client.toml --json
It runs in the foreground and reports "state":"ready", then "phase":"active". The device uses the operator's configured outbound port, 9443 in this example. Default data rotation is every 300 seconds; network loss triggers reconnect with backoff. Ctrl-C requests an orderly stop. Exit 7 OWNER_BUSY can mean another connector or a retained owner lease; exit 3 CREDENTIAL_ERROR requires checking enrollment. Investigate exit 1 PROTOCOL_ERROR with the operator rather than blindly retrying. Older releases through f9f7abf could misclassify one missing-ACK timeout this way; current main treats that narrow case as retryable transport loss. Reconnect never proves an interrupted operation did not run.
6. Call the echo
From any machine with relay-ca.pem and a token. read -rs keeps the token out of history and off screen:
read -rs TOKEN
curl --cacert ~/agentuplink-device/relay-ca.pem \
-H "Authorization: Bearer $TOKEN" --data-binary hello \
https://RELAY_HOST/v1/devices/DEVICE_UUID/services/SERVICE_UUID/echo
| Answer | Meaning |
|---|---|
200, my-devicehello | The tunnel works: the device canary followed by your payload. |
503 DEVICE_OFFLINE, not dispatched | The device is not connected. |
| 401 | No token, or a token not accepted: expired, wrong issuer, audience or key, or an unknown user. |
| 403 | A valid token without echo:invoke. Relays built before this was split out, including the one serving agentuplink-relay.fly.dev on 2026-09-25, answer 401 instead. |
404 SERVICE_NOT_FOUND | Wrong service UUID, or no grant for your user. |
503 RESOURCE_EXHAUSTED, not dispatched | Seen for about half a second at a data-socket rotation. The request did not run; repeat it. |
503 REVERSE_CHANNEL_INTERRUPTED, unknown | The connection broke mid-request. Repeating an echo is harmless; check side-effecting services first. |
| curl error 60 | --cacert is missing or names the wrong CA. |
When you are done
Stop connect and ask the operator to revoke your device. A revoked device cannot be reactivated. Delete ~/agentuplink-device to remove the private key.
Source & review
Reviewed against cb94dc3ccbf4 on 2026-09-25. Based on docs/join-relay.md, scripts/package_release.py, crates/tunnel-client/src/main.rs. Historical test evidence is not a new test run.