remote-pi 0.1.0

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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/config.d.ts +18 -0
  4. package/dist/config.js +51 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/index.d.ts +77 -0
  7. package/dist/index.js +1285 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/pairing/crypto.d.ts +8 -0
  10. package/dist/pairing/crypto.js +22 -0
  11. package/dist/pairing/crypto.js.map +1 -0
  12. package/dist/pairing/handshake.d.ts +28 -0
  13. package/dist/pairing/handshake.js +113 -0
  14. package/dist/pairing/handshake.js.map +1 -0
  15. package/dist/pairing/noise-sha256.d.ts +16 -0
  16. package/dist/pairing/noise-sha256.js +103 -0
  17. package/dist/pairing/noise-sha256.js.map +1 -0
  18. package/dist/pairing/qr.d.ts +41 -0
  19. package/dist/pairing/qr.js +96 -0
  20. package/dist/pairing/qr.js.map +1 -0
  21. package/dist/pairing/storage.d.ts +10 -0
  22. package/dist/pairing/storage.js +65 -0
  23. package/dist/pairing/storage.js.map +1 -0
  24. package/dist/protocol/codec.d.ts +7 -0
  25. package/dist/protocol/codec.js +46 -0
  26. package/dist/protocol/codec.js.map +1 -0
  27. package/dist/protocol/types.d.ts +119 -0
  28. package/dist/protocol/types.js +2 -0
  29. package/dist/protocol/types.js.map +1 -0
  30. package/dist/rooms.d.ts +9 -0
  31. package/dist/rooms.js +22 -0
  32. package/dist/rooms.js.map +1 -0
  33. package/dist/session/agent_bridge.d.ts +55 -0
  34. package/dist/session/agent_bridge.js +146 -0
  35. package/dist/session/agent_bridge.js.map +1 -0
  36. package/dist/session/broker.d.ts +37 -0
  37. package/dist/session/broker.js +206 -0
  38. package/dist/session/broker.js.map +1 -0
  39. package/dist/session/envelope.d.ts +24 -0
  40. package/dist/session/envelope.js +89 -0
  41. package/dist/session/envelope.js.map +1 -0
  42. package/dist/session/global_config.d.ts +14 -0
  43. package/dist/session/global_config.js +51 -0
  44. package/dist/session/global_config.js.map +1 -0
  45. package/dist/session/leader_election.d.ts +16 -0
  46. package/dist/session/leader_election.js +78 -0
  47. package/dist/session/leader_election.js.map +1 -0
  48. package/dist/session/local_config.d.ts +18 -0
  49. package/dist/session/local_config.js +47 -0
  50. package/dist/session/local_config.js.map +1 -0
  51. package/dist/session/peer.d.ts +80 -0
  52. package/dist/session/peer.js +268 -0
  53. package/dist/session/peer.js.map +1 -0
  54. package/dist/session/setup_wizard.d.ts +32 -0
  55. package/dist/session/setup_wizard.js +60 -0
  56. package/dist/session/setup_wizard.js.map +1 -0
  57. package/dist/session/tool_gate.d.ts +5 -0
  58. package/dist/session/tool_gate.js +11 -0
  59. package/dist/session/tool_gate.js.map +1 -0
  60. package/dist/session/tools.d.ts +16 -0
  61. package/dist/session/tools.js +123 -0
  62. package/dist/session/tools.js.map +1 -0
  63. package/dist/session/wizard.d.ts +13 -0
  64. package/dist/session/wizard.js +20 -0
  65. package/dist/session/wizard.js.map +1 -0
  66. package/dist/settings.d.ts +15 -0
  67. package/dist/settings.js +52 -0
  68. package/dist/settings.js.map +1 -0
  69. package/dist/transport/peer_channel.d.ts +37 -0
  70. package/dist/transport/peer_channel.js +85 -0
  71. package/dist/transport/peer_channel.js.map +1 -0
  72. package/dist/transport/relay_client.d.ts +81 -0
  73. package/dist/transport/relay_client.js +154 -0
  74. package/dist/transport/relay_client.js.map +1 -0
  75. package/dist/ui/footer.d.ts +32 -0
  76. package/dist/ui/footer.js +32 -0
  77. package/dist/ui/footer.js.map +1 -0
  78. package/package.json +77 -0
  79. package/skills/agent-network/SKILL.md +429 -0
@@ -0,0 +1,429 @@
1
+ ---
2
+ name: agent-network
3
+ description: Use when you (a Pi agent) are running inside a local agent session — i.e., when the Pi footer shows "📡 <session-name>". This skill teaches how to receive messages from other agents, how to reply in a correlatable way, how to ask things of other agents without losing track, and how to act when you don't yet have the context you need.
4
+ ---
5
+
6
+ # Agent Network (skill — message protocol for Pi agents)
7
+
8
+ You are connected to a **local agent session** over a Unix Domain Socket.
9
+ Other Pi agents running on the same machine, in the same session, can send
10
+ you messages. You can send messages to them too.
11
+
12
+ This skill teaches how to participate in that network reliably. Read it to
13
+ the end before acting — understanding the protocol avoids silence and
14
+ deadlocks.
15
+
16
+ ---
17
+
18
+ ## The most important rule
19
+
20
+ **You only receive messages that were explicitly addressed to you.** The
21
+ session broker filters before delivery. You will never see messages
22
+ intended for other agents or "broadcast with `exclude_self`".
23
+
24
+ **Practical consequence**: if a message arrived in your inbox, someone
25
+ wanted your attention. Don't ignore it. Don't assume it was for someone
26
+ else.
27
+
28
+ ---
29
+
30
+ ## Anatomy of a message (envelope)
31
+
32
+ Every message has 5 fields:
33
+
34
+ ```json
35
+ {
36
+ "from": "orchestrator",
37
+ "to": "backend",
38
+ "id": "uuid-v7",
39
+ "re": null,
40
+ "body": <message contents>
41
+ }
42
+ ```
43
+
44
+ | Field | Meaning |
45
+ |---|---|
46
+ | `from` | Who sent it. Use this to know who to reply to |
47
+ | `to` | You (or "broadcast", or a list of names including yours) |
48
+ | `id` | Unique identifier of this specific message |
49
+ | `re` | If this message is a REPLY to another, echoes that one's `id`. Otherwise `null` |
50
+ | `body` | Free-form content. String or JSON object, sender's choice |
51
+
52
+ ---
53
+
54
+ ## When you receive a message
55
+
56
+ Do this in order, don't skip steps:
57
+
58
+ 1. **Look at `body`** to understand what's being asked
59
+ 2. **Look at `from`** to know who to reply to
60
+ 3. **Look at `id`** — this is the `correlation_id` you'll need to echo
61
+ 4. **Execute the work** described in `body`
62
+ 5. **Reply** with a new message:
63
+ - `to`: the `from` of the original message
64
+ - `id`: a fresh UUID v7 (your reply has its own identity)
65
+ - `re`: the `id` of the original message (correlation)
66
+ - `from`: your name
67
+ - `body`: your answer
68
+
69
+ **Always reply.** If the sender sent something that clearly expects a
70
+ reply (not a broadcast announcement), silence breaks their coordination.
71
+ Even errors must be replied (with `body.status: "error"`).
72
+
73
+ ### Concrete example
74
+
75
+ You (name: `backend`) receive:
76
+
77
+ ```json
78
+ {
79
+ "from": "orchestrator",
80
+ "to": "backend",
81
+ "id": "abc-uuid",
82
+ "re": null,
83
+ "body": {
84
+ "task": "Implement the POST /auth/login endpoint",
85
+ "context_ref": "./contracts/auth.md"
86
+ }
87
+ }
88
+ ```
89
+
90
+ You do the work. You reply:
91
+
92
+ ```json
93
+ {
94
+ "from": "backend",
95
+ "to": "orchestrator",
96
+ "id": "xyz-uuid",
97
+ "re": "abc-uuid",
98
+ "body": {
99
+ "status": "done",
100
+ "summary": "Endpoint implemented per contract",
101
+ "files_changed": ["src/auth/login.ts", "src/auth/jwt.ts"]
102
+ }
103
+ }
104
+ ```
105
+
106
+ The orchestrator correlates via `re === "abc-uuid"` and knows this was
107
+ the reply to their task. Without `re`, they receive the message but
108
+ can't match it against the question — and wait until timeout.
109
+
110
+ ---
111
+
112
+ ## When you need to ask another agent (mid-task)
113
+
114
+ Before replying to a task, you may discover you need info from another
115
+ agent. Typical scenario: you are `frontend`, you received a task to
116
+ implement the login screen, but you don't know the exact JWT shape the
117
+ `backend` exposes.
118
+
119
+ **Correct flow** (synchronous via request/reply):
120
+
121
+ 1. Pause your current task (don't reply to the orchestrator yet)
122
+ 2. Send a message to `backend`:
123
+ ```json
124
+ {
125
+ "from": "frontend",
126
+ "to": "backend",
127
+ "id": "new-uuid",
128
+ "re": null,
129
+ "body": {
130
+ "question": "What's the exact payload shape of the JWT returned by POST /auth/login?",
131
+ "context": "needed for FE parsing"
132
+ }
133
+ }
134
+ ```
135
+ 3. **Wait for the reply** with `re === "new-uuid"`
136
+ 4. Use the received info to complete your original task
137
+ 5. Reply to the orchestrator (with `re === "<original task id>"`)
138
+
139
+ The transport layer (`agent_request()`) blocks until the reply arrives
140
+ or times out. Use a reasonable timeout (30–60s for simple questions).
141
+
142
+ ### Limits
143
+
144
+ - **Ask focused questions**, not disguised delegations. "What's the
145
+ shape of X?" is fine. "Can you implement Y for me?" is not — that's
146
+ work the orchestrator should distribute.
147
+ - **Maximum 1 hop**: if you asked B, and B needs to ask C to answer
148
+ you, B should **fail** with `status: "blocked"` and let the
149
+ orchestrator re-plan. Don't chain A → B → C → ...
150
+ - **Timeout mandatory**: never wait indefinitely. If no reply in 60s,
151
+ fail with `status: "blocked"` in your answer to the orchestrator,
152
+ citing which peer didn't respond.
153
+
154
+ ---
155
+
156
+ ## Asking multiple agents in parallel
157
+
158
+ You frequently need info from **several agents at once** before you can
159
+ proceed. The transport supports this natively — every `agent_request()`
160
+ returns a `Promise`, and each request has a unique `id` so the pending
161
+ map demuxes replies correctly. **Multiple requests in flight never get
162
+ confused with each other.**
163
+
164
+ Don't serialize what can run in parallel. Sequential = sum of all
165
+ latencies. Parallel = max of latencies. With 3 agents at 200ms each:
166
+ serial is 600ms, parallel is 200ms.
167
+
168
+ ### Pattern 1 — wait for all (most common)
169
+
170
+ ```typescript
171
+ const [beAnswer, feAnswer] = await Promise.all([
172
+ agent_request("backend", { question: "JWT shape?" }),
173
+ agent_request("frontend", { question: "current theme tokens?" }),
174
+ ]);
175
+ // both arrived, you have both answers, continue your work
176
+ ```
177
+
178
+ ### Pattern 2 — fan-out structured
179
+
180
+ ```typescript
181
+ const peers = ["backend", "frontend", "infra"];
182
+ const answers = await Promise.all(
183
+ peers.map((p) => agent_request(p, { question: "ETA for Y?" }))
184
+ );
185
+ // answers[i] correlates to peers[i] by array index
186
+ ```
187
+
188
+ ### Pattern 3 — race (first answer wins)
189
+
190
+ ```typescript
191
+ const winner = await Promise.race([
192
+ agent_request("worker-1", taskBody),
193
+ agent_request("worker-2", taskBody),
194
+ ]);
195
+ // useful for redundant queries; losing requests still finish
196
+ // in the background but their replies are silently dropped
197
+ ```
198
+
199
+ ### Pattern 4 — tolerant of partial failure
200
+
201
+ ```typescript
202
+ const settled = await Promise.allSettled([
203
+ agent_request("a", q1, 30_000),
204
+ agent_request("b", q2, 30_000),
205
+ ]);
206
+ const okReplies = settled
207
+ .filter((r) => r.status === "fulfilled")
208
+ .map((r) => r.value);
209
+ const failures = settled
210
+ .filter((r) => r.status === "rejected")
211
+ .map((r) => r.reason);
212
+ // proceed with what you got; report failures honestly
213
+ ```
214
+
215
+ ### Limits (same as 1-on-1 questions)
216
+
217
+ - **Max 1 hop still applies to fan-out.** You can ask N agents in
218
+ parallel, but each of them must reply directly to you. They cannot
219
+ themselves fan-out to satisfy your question. If B needs C and D to
220
+ answer your question, B replies `status: "blocked"` and the
221
+ orchestrator re-plans.
222
+ - **Per-request timeout**: each call has its own timer. One slow agent
223
+ doesn't block the others — `Promise.all` rejects fast on first
224
+ failure (use `allSettled` if you need tolerance).
225
+ - **Focused questions, not delegations** — same rule as 1-on-1.
226
+
227
+ ### Mental model
228
+
229
+ The `pending` map inside the transport correlates replies by their `re`
230
+ field against the original `id`s you sent. As long as `id`s are unique
231
+ (UUID v7, guaranteed), N parallel requests stay isolated. You can have
232
+ dozens in flight without confusion — though if you need that many,
233
+ question whether you should be a worker instead of an orchestrator.
234
+
235
+ ---
236
+
237
+ ## Advanced addressing
238
+
239
+ ### Broadcast
240
+
241
+ `to: "broadcast"` delivers to everyone except the sender. Use rarely:
242
+
243
+ - ✅ Announcements: "wave 2 started", "leader changed to X"
244
+ - ❌ Questions: no one replies to broadcasts, because no one knows
245
+ who's supposed to answer
246
+
247
+ ### Multicast
248
+
249
+ `to: ["backend", "frontend"]` delivers to the listed recipients. Useful
250
+ for directed notifications, e.g.: "both of you: stop touching
251
+ `contracts/` while I update it".
252
+
253
+ Each recipient gets the same message (same `id`). If you reply, `re`
254
+ correlates normally.
255
+
256
+ ### Self
257
+
258
+ You never receive your own messages (even on broadcast). No need to
259
+ filter; the broker does it.
260
+
261
+ ---
262
+
263
+ ## Auto-discovery of who's in the session
264
+
265
+ You may receive, at some point after joining, `system` events from the
266
+ broker:
267
+
268
+ ```json
269
+ {
270
+ "from": "broker",
271
+ "to": "backend",
272
+ "id": "uuid",
273
+ "re": null,
274
+ "body": {
275
+ "type": "peer_joined",
276
+ "name": "frontend",
277
+ "capabilities": ["typescript", "react"]
278
+ }
279
+ }
280
+ ```
281
+
282
+ ```json
283
+ {
284
+ "from": "broker",
285
+ "to": "backend",
286
+ "id": "uuid",
287
+ "re": null,
288
+ "body": {
289
+ "type": "peer_left",
290
+ "name": "frontend"
291
+ }
292
+ }
293
+ ```
294
+
295
+ Use these events to know who's online. Keep a mental list (or session
296
+ state) of active peers. Don't ask a peer you know is offline.
297
+
298
+ If you need to list active peers on demand, ask the broker:
299
+
300
+ ```json
301
+ {
302
+ "from": "backend",
303
+ "to": "broker",
304
+ "id": "uuid",
305
+ "re": null,
306
+ "body": { "type": "list_peers" }
307
+ }
308
+ ```
309
+
310
+ The broker replies with `body: { peers: [...] }`.
311
+
312
+ ---
313
+
314
+ ## Situations where you're in doubt
315
+
316
+ ### "I received a message I don't understand"
317
+
318
+ Reply with `status: "error"` and say what was unclear. Don't go silent.
319
+
320
+ ```json
321
+ {
322
+ "from": "backend",
323
+ "to": "<original sender>",
324
+ "id": "...",
325
+ "re": "<original id>",
326
+ "body": {
327
+ "status": "error",
328
+ "summary": "I didn't understand the request. The 'task' field is unclear."
329
+ }
330
+ }
331
+ ```
332
+
333
+ ### "I received a message with `re` set, but I never sent a request"
334
+
335
+ Probably a late reply to a request that already timed out or was
336
+ cancelled. Ignore silently. Don't reply.
337
+
338
+ ### "I received a message without `re`, but it's clearly a reply"
339
+
340
+ Treat it as a new message (task). The sender didn't follow protocol —
341
+ you can't correlate it with your original request even if you sent one.
342
+ If genuinely confused, reply asking: "Is this message a reply to
343
+ something? I didn't see a `re`."
344
+
345
+ ### "I'm in a session but no message ever arrives"
346
+
347
+ Normal. You only receive when someone addresses you. Keep working in
348
+ solo mode until someone calls. Don't poll the broker periodically.
349
+
350
+ ### "The leader died (peer_left event from `broker`)"
351
+
352
+ The transport layer will automatically promote another peer to leader.
353
+ You (client) will reconnect transparently in ~500ms. During that
354
+ window, your `send/request` calls may fail — retry once after 1s
355
+ before propagating an error.
356
+
357
+ ---
358
+
359
+ ## Single-page summary
360
+
361
+ 1. You only receive what's addressed to you. Don't filter. Trust the broker.
362
+ 2. Every reply carries `re` = `id` of the original message. Without it,
363
+ the sender can't correlate.
364
+ 3. Reply's `to` = question's `from`.
365
+ 4. Always reply — success or error — when you receive something that
366
+ looks like a task.
367
+ 5. You can ask other agents mid-task (request/reply, synchronous), but:
368
+ - Max 1 hop
369
+ - Always with timeout
370
+ - Read-only question ("what is X?"), not delegation ("do Y")
371
+ 6. **You can ask multiple agents in parallel** with `Promise.all` —
372
+ each request's `id` keeps replies isolated. Don't serialize what can
373
+ run in parallel.
374
+ 7. Broadcast is for announcements, not questions.
375
+ 8. When confused, reply with `status: "error"` instead of staying silent.
376
+
377
+ That skill is everything you need to participate in the session without
378
+ breaking other agents' flow. Re-read it when in doubt.
379
+
380
+ ---
381
+
382
+ ## Mini-FAQ
383
+
384
+ **Q: Can I send a message to myself?**
385
+ A: No. Both `agent_send` and `agent_request` refuse early with an
386
+ error (`"cannot agent_send to yourself"`) when `to` matches your
387
+ assigned name. The broker also drops unicast self-loops as a second
388
+ line of defense. There's no upside — just do the work directly
389
+ instead of round-tripping through the network.
390
+
391
+ **Q: What happens to messages I sent before the recipient joined?**
392
+ A: The broker drops them with a warning log. There is no persistent
393
+ message queue. If you need delivery guarantees, wait for the
394
+ `peer_joined` event before sending.
395
+
396
+ **Q: Can I have the same name as another agent?**
397
+ A: No. The broker auto-suffixes (e.g., you asked for `backend`, you
398
+ get `backend#2` in `register_ack`). Use the name the broker gave you
399
+ (`name_assigned`) in all your messages.
400
+
401
+ **Q: Can `body` be binary?**
402
+ A: Not directly. Use base64 inside a string if needed. But you're
403
+ probably using this for text/JSON — don't make binary the use case.
404
+
405
+ **Q: Is there message priority?**
406
+ A: Not in MVP. Order is FIFO of arrival at the broker. If you need
407
+ priority, open an issue.
408
+
409
+ **Q: How do I discover other peers' capabilities (stack, role)?**
410
+ A: `peer_joined` events carry `capabilities` in `body`. Save them when
411
+ peers enter. Or ask the broker via `list_peers`.
412
+
413
+ **Q: Can I disconnect any time?**
414
+ A: Yes. The transport sends `peer_left` automatically when you close.
415
+ Other agents will see you go.
416
+
417
+ **Q: How many parallel requests is "too many"?**
418
+ A: There's no hard limit, but if you're firing 10+ in parallel,
419
+ question whether you're the wrong layer. Orchestrators dispatch wide;
420
+ workers should answer narrow. If you're a worker fanning out to many
421
+ peers, you may be doing the orchestrator's job.
422
+
423
+ ---
424
+
425
+ ## See also
426
+
427
+ - [`plan/19-agent-network-rfc.md`](../plan/19-agent-network-rfc.md) — motivation and context
428
+ - [`plan/19-agent-network.md`](../plan/19-agent-network.md) — implementation plan
429
+ - `~/.pi/remote/sessions/<name>/audit.jsonl` — append-only log of everything that passed through the broker (read-only audit). Legacy path preserved (2026-05-21 decision — no storage migration)