@geohar/un-bien 0.7.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 (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -0
  3. package/dist/actions/handlers.d.ts +77 -0
  4. package/dist/actions/handlers.js +26 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +62 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/bin/supervisord.d.ts +2 -0
  10. package/dist/bin/supervisord.js +86 -0
  11. package/dist/bin/supervisord.js.map +1 -0
  12. package/dist/config.d.ts +84 -0
  13. package/dist/config.js +115 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/client.d.ts +20 -0
  16. package/dist/daemon/client.js +131 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/control_protocol.d.ts +176 -0
  19. package/dist/daemon/control_protocol.js +63 -0
  20. package/dist/daemon/control_protocol.js.map +1 -0
  21. package/dist/daemon/cron_log.d.ts +45 -0
  22. package/dist/daemon/cron_log.js +71 -0
  23. package/dist/daemon/cron_log.js.map +1 -0
  24. package/dist/daemon/cron_registry.d.ts +80 -0
  25. package/dist/daemon/cron_registry.js +198 -0
  26. package/dist/daemon/cron_registry.js.map +1 -0
  27. package/dist/daemon/id.d.ts +24 -0
  28. package/dist/daemon/id.js +36 -0
  29. package/dist/daemon/id.js.map +1 -0
  30. package/dist/daemon/install.d.ts +170 -0
  31. package/dist/daemon/install.js +603 -0
  32. package/dist/daemon/install.js.map +1 -0
  33. package/dist/daemon/registry.d.ts +63 -0
  34. package/dist/daemon/registry.js +153 -0
  35. package/dist/daemon/registry.js.map +1 -0
  36. package/dist/daemon/rpc_child.d.ts +194 -0
  37. package/dist/daemon/rpc_child.js +419 -0
  38. package/dist/daemon/rpc_child.js.map +1 -0
  39. package/dist/daemon/supervisor.d.ts +93 -0
  40. package/dist/daemon/supervisor.js +666 -0
  41. package/dist/daemon/supervisor.js.map +1 -0
  42. package/dist/extension_ui_bridge.d.ts +26 -0
  43. package/dist/extension_ui_bridge.js +341 -0
  44. package/dist/extension_ui_bridge.js.map +1 -0
  45. package/dist/index.d.ts +197 -0
  46. package/dist/index.js +5750 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/mcp/mesh_result.d.ts +10 -0
  49. package/dist/mcp/mesh_result.js +42 -0
  50. package/dist/mcp/mesh_result.js.map +1 -0
  51. package/dist/mcp/mesh_server.d.ts +16 -0
  52. package/dist/mcp/mesh_server.js +373 -0
  53. package/dist/mcp/mesh_server.js.map +1 -0
  54. package/dist/mesh/canonical.d.ts +30 -0
  55. package/dist/mesh/canonical.js +63 -0
  56. package/dist/mesh/canonical.js.map +1 -0
  57. package/dist/mesh/client.d.ts +18 -0
  58. package/dist/mesh/client.js +127 -0
  59. package/dist/mesh/client.js.map +1 -0
  60. package/dist/mesh/encoding.d.ts +38 -0
  61. package/dist/mesh/encoding.js +217 -0
  62. package/dist/mesh/encoding.js.map +1 -0
  63. package/dist/mesh/self_revoke.d.ts +73 -0
  64. package/dist/mesh/self_revoke.js +407 -0
  65. package/dist/mesh/self_revoke.js.map +1 -0
  66. package/dist/mesh/siblings.d.ts +49 -0
  67. package/dist/mesh/siblings.js +159 -0
  68. package/dist/mesh/siblings.js.map +1 -0
  69. package/dist/mesh/types.d.ts +34 -0
  70. package/dist/mesh/types.js +11 -0
  71. package/dist/mesh/types.js.map +1 -0
  72. package/dist/mesh/verify.d.ts +17 -0
  73. package/dist/mesh/verify.js +86 -0
  74. package/dist/mesh/verify.js.map +1 -0
  75. package/dist/pairing/crypto.d.ts +8 -0
  76. package/dist/pairing/crypto.js +22 -0
  77. package/dist/pairing/crypto.js.map +1 -0
  78. package/dist/pairing/qr.d.ts +59 -0
  79. package/dist/pairing/qr.js +127 -0
  80. package/dist/pairing/qr.js.map +1 -0
  81. package/dist/pairing/storage.d.ts +120 -0
  82. package/dist/pairing/storage.js +614 -0
  83. package/dist/pairing/storage.js.map +1 -0
  84. package/dist/panel_bridge.d.ts +15 -0
  85. package/dist/panel_bridge.js +409 -0
  86. package/dist/panel_bridge.js.map +1 -0
  87. package/dist/paths.d.ts +39 -0
  88. package/dist/paths.js +51 -0
  89. package/dist/paths.js.map +1 -0
  90. package/dist/protocol/codec.d.ts +7 -0
  91. package/dist/protocol/codec.js +48 -0
  92. package/dist/protocol/codec.js.map +1 -0
  93. package/dist/protocol/types.d.ts +404 -0
  94. package/dist/protocol/types.js +2 -0
  95. package/dist/protocol/types.js.map +1 -0
  96. package/dist/rooms.d.ts +42 -0
  97. package/dist/rooms.js +78 -0
  98. package/dist/rooms.js.map +1 -0
  99. package/dist/session/bridge.d.ts +37 -0
  100. package/dist/session/bridge.js +134 -0
  101. package/dist/session/bridge.js.map +1 -0
  102. package/dist/session/broker.d.ts +189 -0
  103. package/dist/session/broker.js +527 -0
  104. package/dist/session/broker.js.map +1 -0
  105. package/dist/session/broker_remote.d.ts +74 -0
  106. package/dist/session/broker_remote.js +581 -0
  107. package/dist/session/broker_remote.js.map +1 -0
  108. package/dist/session/cwd_lock.d.ts +33 -0
  109. package/dist/session/cwd_lock.js +127 -0
  110. package/dist/session/cwd_lock.js.map +1 -0
  111. package/dist/session/debug_log.d.ts +3 -0
  112. package/dist/session/debug_log.js +32 -0
  113. package/dist/session/debug_log.js.map +1 -0
  114. package/dist/session/envelope.d.ts +33 -0
  115. package/dist/session/envelope.js +117 -0
  116. package/dist/session/envelope.js.map +1 -0
  117. package/dist/session/global_config.d.ts +33 -0
  118. package/dist/session/global_config.js +73 -0
  119. package/dist/session/global_config.js.map +1 -0
  120. package/dist/session/ipc.d.ts +27 -0
  121. package/dist/session/ipc.js +22 -0
  122. package/dist/session/ipc.js.map +1 -0
  123. package/dist/session/leader_election.d.ts +32 -0
  124. package/dist/session/leader_election.js +108 -0
  125. package/dist/session/leader_election.js.map +1 -0
  126. package/dist/session/local_config.d.ts +67 -0
  127. package/dist/session/local_config.js +193 -0
  128. package/dist/session/local_config.js.map +1 -0
  129. package/dist/session/mesh_node.d.ts +116 -0
  130. package/dist/session/mesh_node.js +596 -0
  131. package/dist/session/mesh_node.js.map +1 -0
  132. package/dist/session/peer.d.ts +139 -0
  133. package/dist/session/peer.js +474 -0
  134. package/dist/session/peer.js.map +1 -0
  135. package/dist/session/peer_inventory.d.ts +13 -0
  136. package/dist/session/peer_inventory.js +48 -0
  137. package/dist/session/peer_inventory.js.map +1 -0
  138. package/dist/session/peer_limits.d.ts +9 -0
  139. package/dist/session/peer_limits.js +24 -0
  140. package/dist/session/peer_limits.js.map +1 -0
  141. package/dist/session/rpc_envelope.d.ts +108 -0
  142. package/dist/session/rpc_envelope.js +221 -0
  143. package/dist/session/rpc_envelope.js.map +1 -0
  144. package/dist/session/rpc_inbound.d.ts +55 -0
  145. package/dist/session/rpc_inbound.js +136 -0
  146. package/dist/session/rpc_inbound.js.map +1 -0
  147. package/dist/session/setup_wizard.d.ts +39 -0
  148. package/dist/session/setup_wizard.js +63 -0
  149. package/dist/session/setup_wizard.js.map +1 -0
  150. package/dist/session/tools.d.ts +24 -0
  151. package/dist/session/tools.js +272 -0
  152. package/dist/session/tools.js.map +1 -0
  153. package/dist/session/wizard.d.ts +13 -0
  154. package/dist/session/wizard.js +20 -0
  155. package/dist/session/wizard.js.map +1 -0
  156. package/dist/transport/peer_channel.d.ts +52 -0
  157. package/dist/transport/peer_channel.js +145 -0
  158. package/dist/transport/peer_channel.js.map +1 -0
  159. package/dist/transport/pi_forward_client.d.ts +29 -0
  160. package/dist/transport/pi_forward_client.js +68 -0
  161. package/dist/transport/pi_forward_client.js.map +1 -0
  162. package/dist/transport/relay_client.d.ts +96 -0
  163. package/dist/transport/relay_client.js +210 -0
  164. package/dist/transport/relay_client.js.map +1 -0
  165. package/dist/ui/footer.d.ts +32 -0
  166. package/dist/ui/footer.js +34 -0
  167. package/dist/ui/footer.js.map +1 -0
  168. package/docs/daemon.md +289 -0
  169. package/package.json +92 -0
  170. package/service-templates/launchd.plist.template +35 -0
  171. package/service-templates/systemd.service.template +19 -0
  172. package/service-templates/task-launcher.vbs.template +10 -0
  173. package/service-templates/task-scheduler.xml.template +38 -0
  174. package/skills/agent-network/SKILL.md +310 -0
@@ -0,0 +1,310 @@
1
+ ---
2
+ name: agent-network
3
+ description: Use when the remote-pi mesh tools (`list_peers`, `agent_send`, and — on Claude — `get_messages`) are available. You are an agent (a Claude session or a Pi coding 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 a delivery ACK (`agent_send`), how incoming messages reach you (via `get_messages` on Claude, or delivered into your turn on Pi), how to reply (echo `re`), and how to treat every peer address as an opaque routing key that must be echoed verbatim.
4
+ ---
5
+
6
+ # Agent Network (remote-pi mesh)
7
+
8
+ You are connected to the **remote-pi agent mesh**. Other agents — other Claude
9
+ sessions, Pi coding agents on this machine, and agents on the Owner's other PCs
10
+ (reached through the relay) — can send you messages, and you can send messages
11
+ 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
+ **Your tools:** `list_peers` and `agent_send` always. On Claude you also have
17
+ `get_messages` (a Pi agent receives messages directly into its turn instead —
18
+ see below).
19
+
20
+ ---
21
+
22
+ ## The most important rule: read your inbox every turn
23
+
24
+ You only ever receive messages addressed to you — the broker filters before
25
+ delivery. **If a message arrived, someone wanted your attention. Don't ignore
26
+ it.** How a message reaches you depends on your runtime:
27
+
28
+ - **Claude (MCP):** incoming messages are buffered. **At the start of every
29
+ turn, call `get_messages`** to drain and read them:
30
+
31
+ ```
32
+ get_messages()
33
+ → "[2026-05-30T12:00:01Z] from=backend re=<your-id>
34
+ id=<msg-id>
35
+ { "shape": { "sub": "string", "exp": "number" } }"
36
+ ```
37
+
38
+ It returns all pending messages and clears the buffer (call once per turn),
39
+ or `(no messages)` when nothing is waiting — that's normal, keep working. A
40
+ channel push (`📨 Message from …`) may nudge you mid-session; still call
41
+ `get_messages` for the full structured payload.
42
+
43
+ - **Pi:** the runtime delivers each incoming message directly as a new turn
44
+ input the moment it arrives — no polling, no `get_messages`. You'll see it
45
+ prefixed `[agent-network] message from "<peer>" (id=…, re=…)`.
46
+
47
+ Either way: no wait/sleep/poll-loop. Replies to your own sends arrive on a
48
+ **later turn**, never inline.
49
+
50
+ ---
51
+
52
+ ## First thing in a new session: `list_peers`
53
+
54
+ Before sending anything, find out who's actually online:
55
+
56
+ ```
57
+ list_peers()
58
+ → /Users/jo/acme/backend@backend
59
+ /Users/jo/acme/backend@reviewer
60
+ /Users/jo/acme/web@web
61
+ casa:/Users/jo/acme/api@api
62
+ ```
63
+
64
+ Synchronous (resolves in milliseconds — not another agent's turn). Use it:
65
+
66
+ - At the start of a session, to see what mesh you're in
67
+ - Before any `agent_send` whose target is uncertain
68
+ - To refresh — peers join and leave over time
69
+
70
+ **Presence is passive (pull, not push).** A peer joining or leaving does **not**
71
+ wake your turn. When your view feels stale, just call `list_peers` again — it's
72
+ the authoritative snapshot. Don't expect `peer_joined`/`peer_left` events.
73
+
74
+ **Each entry is a complete ADDRESS, not a bare name. Treat the entire value as
75
+ an opaque routing key.** Local values may look like `<cwd>@<name>` and remote
76
+ values may appear with a receiver-local PC alias, but that appearance is for
77
+ presentation only. A PC alias can contain percent-encoded bytes such as `%3A`
78
+ or `%25`, or a collision suffix containing `~`.
79
+
80
+ **Echo every address VERBATIM into `agent_send` (and as your `to` when
81
+ replying).** Never split it on `:` or `@`, decode or re-encode `%` bytes, remove
82
+ a `~` suffix, change case, normalize it, or construct it from a path, agent
83
+ name, or PC label. Copy the exact whole string returned by `list_peers` or
84
+ received in `from`. Parsing or rebuilding an address is unsafe for routing and
85
+ must never be used for a security decision.
86
+
87
+ The technical identity of a PC is its canonical 32-byte Ed25519 Pi public key.
88
+ PC aliases are receiver-local presentation and routing labels only: each
89
+ receiving PC allocates aliases for its siblings independently, so two PCs can
90
+ list the same sibling under different aliases. Never use an alias as proof of
91
+ identity or for authorization. The Relay currently permits a route when any
92
+ correctly signed Owner blob lists both canonical Pi keys; that does not prove
93
+ the Owner paired with or controls either Pi.
94
+
95
+ You are excluded from the result — no need to filter yourself out.
96
+
97
+ ---
98
+
99
+ ## Anatomy of a message (envelope)
100
+
101
+ Each message carries: `from`, `to`, `id`, `re`, and `body`.
102
+
103
+ | Field | Meaning |
104
+ |---|---|
105
+ | `from` | Sender's ADDRESS (`<cwd>@<name>`). Use it verbatim as your `to` when replying — never reconstruct it. |
106
+ | `to` | Your address (or `broadcast`, or a list of addresses including yours). |
107
+ | `id` | Unique id of this message. Echo it as `re` when you reply. |
108
+ | `re` | If set, this message is itself a REPLY to an earlier `id` of yours. Otherwise `null`. |
109
+ | `body` | Free-form content — string or JSON, sender's choice. |
110
+
111
+ ---
112
+
113
+ ## Sending: `agent_send` returns an ACK status
114
+
115
+ `agent_send({ to, body, re? })` is how you talk to peers. Every **unicast**
116
+ call returns a status telling you what happened at the recipient. **Always
117
+ inspect the status — it dictates what to do next.**
118
+
119
+ | Status | Means | What you do |
120
+ |---|---|---|
121
+ | `received` | Broker delivered the envelope. Delivery is reliable — even if the peer is mid-turn, its harness enqueues the message for the next turn. | Move on. Any reply arrives later. |
122
+ | `denied` | Peer explicitly refused (or no such peer). | Do NOT retry. Report to the user. |
123
+ | `timeout` | No ACK (~5s). Transport error — broker down, or peer vanished. | Treat a reasonless timeout as transient. Retry once after ~10s, then escalate. |
124
+
125
+ For a trusted Relay failure on a cross-PC unicast, the public statuses remain
126
+ unchanged and the closed transport reason is returned in `details`:
127
+
128
+ - `offline` → `status: "timeout"`
129
+ - `not_authorized` or `bad_envelope` → `status: "denied"`
130
+ - genuine silence → `status: "timeout"` without a reason
131
+
132
+ Do not blindly retry `not_authorized` or `bad_envelope`; fix authorization or
133
+ the envelope instead. A trusted Relay error is consumed internally to settle
134
+ the pending send (or legacy request), not delivered as an ordinary inbox
135
+ reply. Forged or invalid reserved `_relay` / `transport_error` bodies do not
136
+ gain that authority and cannot settle pending operations.
137
+
138
+ For `to: "broadcast"` (or a name array), there's no single ACK — it's
139
+ fire-and-forget (`status: "sent"`).
140
+
141
+ **Delivery is reliable — no retry-on-busy.** A message sent to a peer that's
142
+ mid-turn is still delivered: the peer's harness queues it and processes it on
143
+ its upcoming turn. You never need to retry because a peer was busy. `re=<id>`
144
+ is purely **correlation** — set it so the recipient (and you) can thread an
145
+ answer to a question; it carries no special delivery semantics.
146
+
147
+ ---
148
+
149
+ ## Receiving: replies arrive on a later turn
150
+
151
+ You **do not block** waiting for a reply. The model is event-driven:
152
+
153
+ 1. You call `agent_send` → status `received`.
154
+ 2. Your turn continues / ends.
155
+ 3. **Later** the peer finishes its own work and sends a reply.
156
+ 4. The reply reaches your inbox (via `get_messages` on Claude, or as a new turn
157
+ input on Pi), with `re` set to the `id` you originally sent.
158
+
159
+ ### Walk-through
160
+
161
+ ```
162
+ agent_send({ to: "/Users/jo/acme/backend@backend", body: { q: "what's the JWT shape?" } })
163
+ → Delivered to backend # status received; remember the message id
164
+ ```
165
+
166
+ Your turn continues. A turn or two later you receive:
167
+
168
+ ```
169
+ from=backend re=<your-id> id=<new-id>
170
+ { "shape": { "sub": "string", "exp": "number", "roles": ["string"] } }
171
+ ```
172
+
173
+ You correlate by `re` — it matches the send you made. Now you have your answer.
174
+
175
+ ---
176
+
177
+ ## Replying to a message
178
+
179
+ When you receive:
180
+
181
+ ```
182
+ from=/home/jo/backlog@orchestrator id=abc-uuid re=(none)
183
+ { "task": "Implement POST /auth/login" }
184
+ ```
185
+
186
+ Reply with `re` set to that `id`, and `to` set to the sender's `from`:
187
+
188
+ ```
189
+ agent_send({
190
+ to: "/home/jo/backlog@orchestrator",
191
+ body: { status: "done", files_changed: [...] },
192
+ re: "abc-uuid"
193
+ })
194
+ ```
195
+
196
+ Without `re`, the sender gets your message but can't match it to the
197
+ question — coordination drifts. **Always echo `re` on a reply.**
198
+
199
+ ---
200
+
201
+ ## Asking multiple peers at once
202
+
203
+ Fire multiple `agent_send` in one turn — each returns its own ACK. Replies
204
+ arrive on future turns as peers finish.
205
+
206
+ ```
207
+ agent_send({ to: "/repo/api@backend", body: { q: "JWT shape?" } }) // received
208
+ agent_send({ to: "/repo/web@frontend", body: { q: "theme tokens?" } }) // received
209
+ agent_send({ to: "/repo/ops@infra", body: { q: "ETA for Y?" } }) // received (queued if mid-turn)
210
+ ```
211
+
212
+ Track which `id` maps to which question. Don't assume replies arrive in send
213
+ order — use `re` to identify what each reply answers.
214
+
215
+ ---
216
+
217
+ ## Cross-PC addressing (`<pc>:<cwd>@<name>`)
218
+
219
+ When the Owner has paired multiple PCs, remote peers have a receiver-local
220
+ `<pc>:` prefix. Send and reply with the complete address verbatim; never add
221
+ your own prefix. Relay routing keeps the same `received | denied | timeout`
222
+ semantics. A reasonless `denied` can mean a stale remote roster; refresh with
223
+ `list_peers`. Trusted Relay reasons in `details` map `offline` to `timeout` and
224
+ `not_authorized` / `bad_envelope` to `denied`; they settle the pending operation
225
+ internally, not as inbox replies.
226
+
227
+ ---
228
+
229
+ ## Broadcast and multicast
230
+
231
+ - `to: "broadcast"` → every other peer **in your folder (same cwd)** — broadcast
232
+ is folder-scoped and local-only (it does NOT cross PCs or reach other folders).
233
+ `to: ["addr1", "addr2"]` → the listed addresses (echo them verbatim).
234
+ - Use for announcements ("wave 2 started", "I'm taking the lock on /contracts"),
235
+ never for questions (replies would be uncorrelated).
236
+ - Broadcast/multicast skip the ACK — status is `sent`, you don't know who
237
+ received it. For delivery confirmation, use individual unicast sends.
238
+
239
+ ---
240
+
241
+ ## When in doubt
242
+
243
+ - **Received a task you don't understand** → reply with `body.status:"error"`,
244
+ echoing the original `id` in `re`. Don't go silent.
245
+ - **Received a `re` you never sent** → late reply to something already wrapped
246
+ up. Ignore. Don't reply to a reply.
247
+ - **No messages ever arrive** → normal. You only receive when addressed. Keep
248
+ working; don't poll the broker.
249
+ - **Reasonless `timeout` on send** → broker restarting (failover), relay
250
+ silence, or a vanished peer. The client reconnects transparently in ~500ms;
251
+ retry once after a beat, then escalate. For a reason in `details`, follow the
252
+ mapping above; never blindly retry authorization or envelope failures.
253
+
254
+ ---
255
+
256
+ ## Legacy: `agent_request` (Pi only, deprecated)
257
+
258
+ On Pi you may see a tool called `agent_request` that takes a target + body and
259
+ **blocks the entire turn** waiting for the peer's content reply. It still
260
+ works but emits a deprecation warning. It blocks your turn (costs tokens and
261
+ wall time), gives no ACK signal, and pairs badly with parallel multi-peer
262
+ questions. **Migrate every `agent_request` to `agent_send`** + reading your
263
+ inbox on a later turn. (Claude has no `agent_request` — use `agent_send`.)
264
+
265
+ ---
266
+
267
+ ## Single-page summary
268
+
269
+ 1. **Every turn**: read your inbox first — `get_messages()` on Claude; on Pi
270
+ messages arrive as turn input automatically.
271
+ 2. **Discover**: `list_peers()` returns opaque, receiver-local addresses.
272
+ Echo them verbatim; never parse, decode, normalize, or compose them.
273
+ Presence is pull-based.
274
+ 3. **Send**: `agent_send({to, body, re?})` → inspect the status.
275
+ 4. **Unicast status**: `received | denied | timeout`. `received` queues work
276
+ even for a mid-turn peer; abandon on `denied`; investigate `timeout`.
277
+ Closed Relay reasons in `details` map `offline` to `timeout` and
278
+ `not_authorized` / `bad_envelope` to `denied`. No retry-on-busy.
279
+ 5. **Broadcast/multicast**: status `sent`. Fire-and-forget.
280
+ 6. **Reply**: set `re` to their `id`, `to` to their `from` (the full address,
281
+ prefix and all). `re` is correlation only.
282
+ 7. You never receive your own messages.
283
+
284
+ Re-read when in doubt.
285
+
286
+ ---
287
+
288
+ ## Mini-FAQ
289
+
290
+ **Q: Can I send a message to myself?**
291
+ A: No. `agent_send` refuses early (`status: "refused"`) when `to` matches your
292
+ own address (or a legacy bare self-name), and the broker drops unicast
293
+ self-loops as a second line of defense.
294
+
295
+ **Q: What if the peer never replies?**
296
+ A: Then you never see a reply. Your send returned `received` (the broker handed
297
+ it over); the peer just chose not to answer. There's no implicit timeout on
298
+ replies.
299
+
300
+ **Q: How many sends can I fire in one turn?**
301
+ A: No hard limit. But if you fire 10+ unicasts, question whether you should be a
302
+ worker (answer narrow) rather than an orchestrator (dispatch wide).
303
+
304
+ **Q: Is order preserved?**
305
+ A: Per-pair, yes — the broker is FIFO. Across pairs, replies arrive whenever the
306
+ senders finish. Don't assume reply order matches send order.
307
+
308
+ **Q: Can `body` be binary?**
309
+ A: Not directly. Base64 inside a string if you must. JSON is the intended
310
+ payload.