remote-pi 0.2.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/actions/handlers.d.ts +116 -0
- package/dist/actions/handlers.js +152 -0
- package/dist/actions/handlers.js.map +1 -0
- package/dist/actions/registry.d.ts +25 -0
- package/dist/actions/registry.js +34 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/bin/supervisord.js +43 -1
- package/dist/bin/supervisord.js.map +1 -1
- package/dist/commands/builtin_mirror.d.ts +58 -0
- package/dist/commands/builtin_mirror.js +71 -0
- package/dist/commands/builtin_mirror.js.map +1 -0
- package/dist/commands/list_commands.d.ts +60 -0
- package/dist/commands/list_commands.js +73 -0
- package/dist/commands/list_commands.js.map +1 -0
- package/dist/daemon/control_protocol.d.ts +9 -1
- package/dist/daemon/control_protocol.js +1 -1
- package/dist/daemon/control_protocol.js.map +1 -1
- package/dist/daemon/rpc_child.d.ts +24 -0
- package/dist/daemon/rpc_child.js +41 -2
- package/dist/daemon/rpc_child.js.map +1 -1
- package/dist/daemon/supervisor.d.ts +11 -0
- package/dist/daemon/supervisor.js +56 -4
- package/dist/daemon/supervisor.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +749 -208
- package/dist/index.js.map +1 -1
- package/dist/mcp/mesh_server.d.ts +16 -0
- package/dist/mcp/mesh_server.js +207 -0
- package/dist/mcp/mesh_server.js.map +1 -0
- package/dist/protocol/types.d.ts +103 -0
- package/dist/session/bridge.d.ts +39 -0
- package/dist/session/bridge.js +41 -0
- package/dist/session/bridge.js.map +1 -0
- package/dist/session/mesh_node.d.ts +123 -0
- package/dist/session/mesh_node.js +203 -0
- package/dist/session/mesh_node.js.map +1 -0
- package/dist/session/setup_wizard.d.ts +6 -23
- package/dist/session/setup_wizard.js +6 -15
- package/dist/session/setup_wizard.js.map +1 -1
- package/dist/session/tools.d.ts +1 -1
- package/dist/transport/relay_client.d.ts +8 -0
- package/dist/transport/relay_client.js +50 -2
- package/dist/transport/relay_client.js.map +1 -1
- package/package.json +5 -3
- package/skills/claude-agent-network/SKILL.md +239 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup_wizard.js","sourceRoot":"","sources":["../../src/session/setup_wizard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup_wizard.js","sourceRoot":"","sources":["../../src/session/setup_wizard.ts"],"names":[],"mappings":"AAqBA,MAAM,GAAG,GAAG,KAAK,CAAC;AAClB,MAAM,EAAE,GAAG,IAAI,CAAC;AAChB,MAAM,YAAY,GAAG,YAAY,CAAC;AAElC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAY,EACZ,QAAwB;IAExB,MAAM,UAAU,GAAG,MAAM,QAAQ,CAC/B,EAAE,EACF,aAAa,EACb,QAAQ,CAAC,UAAU,CACpB,CAAC;IACF,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAErC,EAAE,CAAC,MAAM,EAAE,CACT,6JAA6J,EAC7J,MAAM,CACP,CAAC;IACF,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,MAAM,CACpC,8EAA8E,EAC9E,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAC3C,CAAC;IACF,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,gBAAgB,GAAG,cAAc,KAAK,GAAG,CAAC;IAEhD,mBAAmB;IACnB,MAAM,OAAO,GAAG;QACd,oBAAoB,UAAU,EAAE;QAChC,oBAAoB,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;KAClD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,EAAE,CAAC,MAAM,EAAE,CAAC,aAAa,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IAE5C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IACjE,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAEjC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,QAAQ,CACrB,EAAY,EACZ,KAAa,EACb,YAAoB;IAEpB,MAAM,aAAa,GAAG,GAAG,KAAK,cAAc,YAAY,GAAG,CAAC;IAC5D,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK;QAClB,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,YAAY,EAAE,CAAC;QACjD,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;IACjE,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,GAAG,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,wEAAwE;IACxE,0DAA0D;IAC1D,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;AACrD,CAAC"}
|
package/dist/session/tools.d.ts
CHANGED
|
@@ -49,6 +49,9 @@ export declare class RelayClient extends EventEmitter {
|
|
|
49
49
|
private readonly url;
|
|
50
50
|
private readonly keypair;
|
|
51
51
|
private ws;
|
|
52
|
+
/** Epoch ms of the last inbound frame (message / relay ping / pong). */
|
|
53
|
+
private lastActivityAt;
|
|
54
|
+
private livenessTimer;
|
|
52
55
|
constructor(url: string, keypair: Ed25519Keypair);
|
|
53
56
|
/**
|
|
54
57
|
* Connects and completes Ed25519 auth. Resolves when relay is ready.
|
|
@@ -74,6 +77,11 @@ export declare class RelayClient extends EventEmitter {
|
|
|
74
77
|
*/
|
|
75
78
|
sendControl(frame: object): void;
|
|
76
79
|
close(): void;
|
|
80
|
+
/** Force-close the WS when no inbound frame has arrived for
|
|
81
|
+
* `LIVENESS_TIMEOUT_MS` — see the constant's doc for why. `terminate()`
|
|
82
|
+
* fires `close`, which the owner turns into a reconnect. */
|
|
83
|
+
private _startLiveness;
|
|
84
|
+
private _stopLiveness;
|
|
77
85
|
private _authenticate;
|
|
78
86
|
/** Waits for the next single WS message with a timeout. */
|
|
79
87
|
private _nextMsg;
|
|
@@ -2,6 +2,17 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import WebSocket from "ws";
|
|
3
3
|
import { ed25519Sign } from "../pairing/crypto.js";
|
|
4
4
|
const AUTH_TIMEOUT_MS = 5_000;
|
|
5
|
+
/**
|
|
6
|
+
* Liveness watchdog. The relay sends a WS Ping every ~25s (relay `peer.rs`),
|
|
7
|
+
* so a healthy connection sees inbound frames at least that often. If NOTHING
|
|
8
|
+
* arrives for this long the socket is silently dead — NAT/router idle drop,
|
|
9
|
+
* laptop sleep, or the relay/Cloudflare reaping the connection WITHOUT a clean
|
|
10
|
+
* close frame. That half-open case never fires `close`, so reconnect never
|
|
11
|
+
* triggers and a background daemon sits "online" but dead after a few idle
|
|
12
|
+
* hours. We force-close on timeout so `close` drives the caller's reconnect.
|
|
13
|
+
*/
|
|
14
|
+
const LIVENESS_TIMEOUT_MS = 70_000; // ~2.8 missed relay pings → confidently dead
|
|
15
|
+
const LIVENESS_CHECK_MS = 20_000;
|
|
5
16
|
/** Relay rejected hello because another peer already holds (pubkey, room_id). */
|
|
6
17
|
export class RoomAlreadyOpenError extends Error {
|
|
7
18
|
roomId;
|
|
@@ -32,6 +43,9 @@ export class RelayClient extends EventEmitter {
|
|
|
32
43
|
url;
|
|
33
44
|
keypair;
|
|
34
45
|
ws = null;
|
|
46
|
+
/** Epoch ms of the last inbound frame (message / relay ping / pong). */
|
|
47
|
+
lastActivityAt = 0;
|
|
48
|
+
livenessTimer = null;
|
|
35
49
|
constructor(url, keypair) {
|
|
36
50
|
super();
|
|
37
51
|
this.url = url;
|
|
@@ -59,8 +73,12 @@ export class RelayClient extends EventEmitter {
|
|
|
59
73
|
ws.on("open", async () => {
|
|
60
74
|
try {
|
|
61
75
|
await this._authenticate(ws, options);
|
|
62
|
-
// Auth done — wire persistent message handler
|
|
76
|
+
// Auth done — wire persistent message handler. Every inbound frame
|
|
77
|
+
// (data, plus the relay's keepalive ping/pong below) refreshes the
|
|
78
|
+
// liveness clock.
|
|
79
|
+
this.lastActivityAt = Date.now();
|
|
63
80
|
ws.on("message", (raw) => {
|
|
81
|
+
this.lastActivityAt = Date.now();
|
|
64
82
|
const text = Buffer.isBuffer(raw) ? raw.toString() : String(raw);
|
|
65
83
|
for (const line of text.split("\n")) {
|
|
66
84
|
const trimmed = line.trim();
|
|
@@ -68,7 +86,17 @@ export class RelayClient extends EventEmitter {
|
|
|
68
86
|
this.emit("message", trimmed);
|
|
69
87
|
}
|
|
70
88
|
});
|
|
71
|
-
ws
|
|
89
|
+
// The relay pings every ~25s; `ws` auto-replies Pong (keeping the
|
|
90
|
+
// relay's view of us alive). The relay ignores client pings rather
|
|
91
|
+
// than ponging, so these inbound pings — not a ping/pong we initiate
|
|
92
|
+
// — are our liveness signal.
|
|
93
|
+
ws.on("ping", () => { this.lastActivityAt = Date.now(); });
|
|
94
|
+
ws.on("pong", () => { this.lastActivityAt = Date.now(); });
|
|
95
|
+
ws.on("close", () => {
|
|
96
|
+
this._stopLiveness();
|
|
97
|
+
this.emit("close");
|
|
98
|
+
});
|
|
99
|
+
this._startLiveness(ws);
|
|
72
100
|
resolve();
|
|
73
101
|
}
|
|
74
102
|
catch (err) {
|
|
@@ -97,9 +125,29 @@ export class RelayClient extends EventEmitter {
|
|
|
97
125
|
this.ws.send(JSON.stringify(frame));
|
|
98
126
|
}
|
|
99
127
|
close() {
|
|
128
|
+
this._stopLiveness();
|
|
100
129
|
this.ws?.close();
|
|
101
130
|
this.ws = null;
|
|
102
131
|
}
|
|
132
|
+
// ── Liveness watchdog ─────────────────────────────────────────────────────
|
|
133
|
+
/** Force-close the WS when no inbound frame has arrived for
|
|
134
|
+
* `LIVENESS_TIMEOUT_MS` — see the constant's doc for why. `terminate()`
|
|
135
|
+
* fires `close`, which the owner turns into a reconnect. */
|
|
136
|
+
_startLiveness(ws) {
|
|
137
|
+
this._stopLiveness();
|
|
138
|
+
this.livenessTimer = setInterval(() => {
|
|
139
|
+
if (Date.now() - this.lastActivityAt > LIVENESS_TIMEOUT_MS) {
|
|
140
|
+
this._stopLiveness();
|
|
141
|
+
ws.terminate();
|
|
142
|
+
}
|
|
143
|
+
}, LIVENESS_CHECK_MS);
|
|
144
|
+
}
|
|
145
|
+
_stopLiveness() {
|
|
146
|
+
if (this.livenessTimer) {
|
|
147
|
+
clearInterval(this.livenessTimer);
|
|
148
|
+
this.livenessTimer = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
103
151
|
// ── Auth ────────────────────────────────────────────────────────────────────
|
|
104
152
|
async _authenticate(ws, opts) {
|
|
105
153
|
const pubkeyB64 = Buffer.from(this.keypair.publicKey).toString("base64");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay_client.js","sourceRoot":"","sources":["../../src/transport/relay_client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,eAAe,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"relay_client.js","sourceRoot":"","sources":["../../src/transport/relay_client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remote-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Mobile remote control and local agent mesh for the Pi coding agent. Pair your phone via QR over a relay, watch tool calls in real time, run multi-Pi sessions over a local Unix Domain Socket broker, and let agents talk to each other through structured request/reply.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -75,11 +75,13 @@
|
|
|
75
75
|
"vitest": "^4.1.6"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@
|
|
78
|
+
"@earendil-works/pi-coding-agent": "^0.78.0",
|
|
79
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
79
80
|
"@napi-rs/keyring": "^1.3.0",
|
|
80
81
|
"@noble/ed25519": "^3.1.0",
|
|
81
82
|
"qrcode-terminal": "^0.12.0",
|
|
82
83
|
"typebox": "^1.1.38",
|
|
83
|
-
"ws": "^8.20.1"
|
|
84
|
+
"ws": "^8.20.1",
|
|
85
|
+
"zod": "^4.4.3"
|
|
84
86
|
}
|
|
85
87
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-network
|
|
3
|
+
description: Use when the remote-pi mesh tools (`list_peers`, `agent_send`, `get_messages`) are available — you are a Claude agent connected to the remote-pi agent mesh over a local broker. This skill teaches how to discover who's online (`list_peers`), how to send messages with delivery status (`agent_send` + ACK), how incoming messages arrive (`get_messages` at the start of each turn, plus channel push), how to reply (echo `re`), and how cross-PC addressing works (`<pc_label>:<peer>`).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent Network (Claude ↔ remote-pi mesh)
|
|
7
|
+
|
|
8
|
+
You are connected to the **remote-pi agent mesh** through an MCP server.
|
|
9
|
+
Other agents — other Claude sessions, Pi coding agents on this machine, and
|
|
10
|
+
agents on the Owner's other PCs (via the relay) — can send you messages, and
|
|
11
|
+
you can send messages to them.
|
|
12
|
+
|
|
13
|
+
Read this to the end before acting. The protocol is **event-driven**, not
|
|
14
|
+
request/reply. Getting the receive model wrong leaves coordination broken.
|
|
15
|
+
|
|
16
|
+
You have exactly three tools: `list_peers`, `agent_send`, `get_messages`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The most important rule: check your inbox every turn
|
|
21
|
+
|
|
22
|
+
Incoming messages are buffered for you. **At the start of every turn, call
|
|
23
|
+
`get_messages`** to drain and read anything other agents sent you:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
get_messages()
|
|
27
|
+
→ "[2026-05-30T12:00:01Z] from=backend re=<your-id>
|
|
28
|
+
id=<msg-id>
|
|
29
|
+
{ "shape": { "sub": "string", "exp": "number" } }"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- Returns all pending messages and clears the buffer (call once per turn).
|
|
33
|
+
- Returns `(no messages)` when nothing is waiting — that's normal, keep working.
|
|
34
|
+
- A channel push may also surface a message mid-session (a `📨 Message from …`
|
|
35
|
+
notification). When it does, still call `get_messages` to get the full,
|
|
36
|
+
structured payload (`from`, `id`, `re`, `body`) — the push is just a nudge.
|
|
37
|
+
|
|
38
|
+
**If a message arrived, someone wanted your attention. Don't ignore it.**
|
|
39
|
+
You only ever receive messages addressed to you — the broker filters before
|
|
40
|
+
delivery.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## First thing in a new session: `list_peers`
|
|
45
|
+
|
|
46
|
+
Before sending anything, find out who's actually online:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
list_peers()
|
|
50
|
+
→ backend
|
|
51
|
+
frontend
|
|
52
|
+
casa:agent-1
|
|
53
|
+
trab:worker
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Synchronous (resolves in milliseconds — not another agent's turn). Use it:
|
|
57
|
+
|
|
58
|
+
- At the start of a session, to see what mesh you're in
|
|
59
|
+
- Before any `agent_send` whose target name is uncertain
|
|
60
|
+
- After a while, to refresh (peers join/leave over time)
|
|
61
|
+
|
|
62
|
+
**Entry shape:**
|
|
63
|
+
- `backend` → local peer (this machine, same broker)
|
|
64
|
+
- `casa:agent-1` → cross-PC peer on the PC labeled `casa` (the Owner's other
|
|
65
|
+
machine, reached through the relay)
|
|
66
|
+
|
|
67
|
+
You are excluded from the result — no need to filter yourself out.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Anatomy of a message (envelope)
|
|
72
|
+
|
|
73
|
+
`get_messages` shows you, per message: `from`, `id`, `re`, and `body`.
|
|
74
|
+
|
|
75
|
+
| Field | Meaning |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `from` | Who sent it. Use this verbatim as your `to` when replying. |
|
|
78
|
+
| `id` | Unique id of this message. Echo it as `re` when you reply. |
|
|
79
|
+
| `re` | If set, this message is itself a REPLY to an earlier `id` of yours. |
|
|
80
|
+
| `body` | Free-form content — string or JSON, sender's choice. |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Sending: `agent_send` returns an ACK status
|
|
85
|
+
|
|
86
|
+
`agent_send({ to, body, re? })` is how you talk to peers. Every **unicast**
|
|
87
|
+
call returns a status telling you what happened at the recipient. **Always
|
|
88
|
+
inspect the status — it dictates what to do next.**
|
|
89
|
+
|
|
90
|
+
| Status | Means | What you do |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `received` | Peer was idle; broker delivered the envelope; peer will process it on its next turn. | Move on. Any reply arrives later — check `get_messages` on future turns. |
|
|
93
|
+
| `busy` | Peer is mid-turn — envelope **dropped**. | Retry 2× with backoff (~2s, ~5s). Still busy → abandon or escalate. You own the retry. |
|
|
94
|
+
| `denied` | Peer explicitly refused. | Do NOT retry. Report to the user. |
|
|
95
|
+
| `timeout` | No ACK (~5s). Transport error — broker down, or peer vanished. | Treat as transient. Retry once after ~10s, then escalate. |
|
|
96
|
+
|
|
97
|
+
For `to: "broadcast"`, there's no single ACK — it's fire-and-forget
|
|
98
|
+
("Broadcast sent").
|
|
99
|
+
|
|
100
|
+
**Replies bypass the busy gate.** A message with `re=<some-id>` (an answer to
|
|
101
|
+
something the recipient asked) is always delivered — it resolves their pending
|
|
102
|
+
state instead of starting a new turn. So if you fan out questions to several
|
|
103
|
+
peers, every reply reaches you even while they're busy.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Receiving: replies arrive on a later turn
|
|
108
|
+
|
|
109
|
+
You **do not block** waiting for a reply. The model is event-driven:
|
|
110
|
+
|
|
111
|
+
1. You call `agent_send` → status `received`.
|
|
112
|
+
2. Your turn continues / ends.
|
|
113
|
+
3. **Later** the peer finishes its own work and sends a reply.
|
|
114
|
+
4. The reply lands in your inbox. You see it the next time you call
|
|
115
|
+
`get_messages`, with `re` set to the `id` you originally sent.
|
|
116
|
+
|
|
117
|
+
No wait/sleep/poll-loop. Just call `get_messages` at the start of your turns.
|
|
118
|
+
|
|
119
|
+
### Walk-through
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
agent_send({ to: "backend", body: { q: "what's the JWT shape?" } })
|
|
123
|
+
→ Delivered to backend # status received; remember the message id
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Your turn continues. A turn or two later:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
get_messages()
|
|
130
|
+
→ "[…] from=backend re=<your-id>
|
|
131
|
+
id=<new-id>
|
|
132
|
+
{ "shape": { "sub": "string", "exp": "number", "roles": ["string"] } }"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You correlate by `re` — it matches the send you made. Now you have your answer.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Replying to a message
|
|
140
|
+
|
|
141
|
+
When you receive (via `get_messages`):
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
from=orchestrator id=abc-uuid re=(none)
|
|
145
|
+
{ "task": "Implement POST /auth/login" }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Reply with `re` set to that `id`, and `to` set to the sender's `from`:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
agent_send({
|
|
152
|
+
to: "orchestrator",
|
|
153
|
+
body: { status: "done", files_changed: [...] },
|
|
154
|
+
re: "abc-uuid"
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Without `re`, the sender gets your message but can't match it to the
|
|
159
|
+
question — coordination drifts. **Always echo `re` on a reply.**
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Asking multiple peers at once
|
|
164
|
+
|
|
165
|
+
Fire multiple `agent_send` in one turn — each returns its own ACK. Replies
|
|
166
|
+
arrive on future turns as peers finish.
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
agent_send({ to: "backend", body: { q: "JWT shape?" } }) // received
|
|
170
|
+
agent_send({ to: "frontend", body: { q: "theme tokens?" } }) // received
|
|
171
|
+
agent_send({ to: "infra", body: { q: "ETA for Y?" } }) // busy — retry
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Track which `id` maps to which question. Don't assume replies arrive in send
|
|
175
|
+
order — use `re` to identify what each reply answers.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Cross-PC addressing (`<pc_label>:<peer>`)
|
|
180
|
+
|
|
181
|
+
When the Owner has paired multiple PCs, remote peers appear with a prefix:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
list_peers() → backend frontend casa:agent-1 trab:worker
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Send to a remote peer with the prefixed name verbatim:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
agent_send({ to: "casa:agent-1", body: { ... } })
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The relay routes it across the mesh; `received | busy | denied | timeout`
|
|
194
|
+
semantics are identical to local. When you **reply** to a cross-PC message,
|
|
195
|
+
use the sender's `from` verbatim (it already carries the prefix) as your `to`.
|
|
196
|
+
You never prefix your own name — the broker handles that.
|
|
197
|
+
|
|
198
|
+
Cross-PC failure notes:
|
|
199
|
+
- `denied` → the remote broker has no peer by that name (left, or stale cache
|
|
200
|
+
→ call `list_peers` again).
|
|
201
|
+
- `timeout` → the other PC is offline or the relay is unreachable.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Broadcast and multicast
|
|
206
|
+
|
|
207
|
+
- `to: "broadcast"` → every other peer. Use for announcements
|
|
208
|
+
("wave 2 started"), never for questions (replies would be uncorrelated).
|
|
209
|
+
- Broadcast skips ACK — you don't know who received it. For delivery
|
|
210
|
+
confirmation, use individual unicast sends.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## When in doubt
|
|
215
|
+
|
|
216
|
+
- **Received a task you don't understand** → reply with `body.status:"error"`,
|
|
217
|
+
echoing the original `id` in `re`. Don't go silent.
|
|
218
|
+
- **Received a `re` you never sent** → late reply to something already wrapped
|
|
219
|
+
up. Ignore. Don't reply to a reply.
|
|
220
|
+
- **No messages ever arrive** → normal. You only receive when addressed. Keep
|
|
221
|
+
working; just keep calling `get_messages` each turn.
|
|
222
|
+
- **`timeout` on send** → broker restarting (failover) or peer vanished. Retry
|
|
223
|
+
once after ~10s, then escalate.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Single-page summary
|
|
228
|
+
|
|
229
|
+
1. **Every turn**: `get_messages()` first — drain your inbox.
|
|
230
|
+
2. **Discover**: `list_peers()` → locals + `<pc>:<peer>` cross-PC. Synchronous.
|
|
231
|
+
3. **Send**: `agent_send({to, body, re?})` → inspect the status.
|
|
232
|
+
4. **Unicast status**: `received | busy | denied | timeout`. Retry on `busy`
|
|
233
|
+
(backoff); abandon on `denied`; investigate on `timeout`.
|
|
234
|
+
5. **Broadcast**: fire-and-forget, no ACK.
|
|
235
|
+
6. **Reply**: set `re` to their `id`, `to` to their `from` (prefix and all).
|
|
236
|
+
7. You never receive your own messages. The broker does not queue — if a peer
|
|
237
|
+
is busy, your message is dropped; you own the retry.
|
|
238
|
+
|
|
239
|
+
Re-read when in doubt.
|