@llblab/pi-actors 0.42.2 → 0.43.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 (254) hide show
  1. package/AGENTS.md +121 -175
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +113 -276
  4. package/dist/fixtures/protocol/control-endpoint.json +6 -0
  5. package/dist/fixtures/protocol/control-record.json +9 -0
  6. package/dist/fixtures/protocol/recipe-summary.json +4 -12
  7. package/dist/fixtures/protocol/trace-event.json +9 -0
  8. package/dist/lib/async-runs.d.ts +14 -38
  9. package/dist/lib/async-runs.js +158 -108
  10. package/dist/lib/control.d.ts +12 -0
  11. package/dist/lib/control.js +84 -0
  12. package/dist/lib/execution-sessions.d.ts +17 -0
  13. package/dist/lib/execution-sessions.js +85 -0
  14. package/dist/lib/file-state.d.ts +1 -0
  15. package/dist/lib/file-state.js +17 -5
  16. package/dist/lib/inspector-actions.d.ts +2 -2
  17. package/dist/lib/inspector-actions.js +2 -2
  18. package/dist/lib/inspector-command.js +3 -3
  19. package/dist/lib/inspector-overlay.d.ts +52 -70
  20. package/dist/lib/inspector-overlay.js +532 -905
  21. package/dist/lib/inspector.d.ts +3 -71
  22. package/dist/lib/inspector.js +19 -665
  23. package/dist/lib/limits.d.ts +4 -2
  24. package/dist/lib/limits.js +4 -2
  25. package/dist/lib/observability.d.ts +17 -17
  26. package/dist/lib/observability.js +45 -84
  27. package/dist/lib/pi.d.ts +1 -1
  28. package/dist/lib/prompts.d.ts +1 -1
  29. package/dist/lib/prompts.js +2 -2
  30. package/dist/lib/recipe-control.d.ts +7 -0
  31. package/dist/lib/recipe-control.js +39 -0
  32. package/dist/lib/recipes-discovery.js +2 -0
  33. package/dist/lib/recipes-references.d.ts +1 -14
  34. package/dist/lib/recipes-references.js +6 -21
  35. package/dist/lib/review-projection.js +1 -5
  36. package/dist/lib/run-ui-runtime.js +2 -2
  37. package/dist/lib/runs-control-delivery.d.ts +21 -0
  38. package/dist/lib/runs-control-delivery.js +127 -0
  39. package/dist/lib/runs-controls.d.ts +35 -0
  40. package/dist/lib/runs-controls.js +144 -0
  41. package/dist/lib/runs-retention.d.ts +7 -0
  42. package/dist/lib/runs-retention.js +27 -3
  43. package/dist/lib/runs-start.js +4 -2
  44. package/dist/lib/runs-status.js +11 -6
  45. package/dist/lib/runs-trace.d.ts +24 -0
  46. package/dist/lib/runs-trace.js +98 -0
  47. package/dist/lib/runtime-notifier.d.ts +1 -1
  48. package/dist/lib/runtime-notifier.js +1 -1
  49. package/dist/lib/tools-inspect.d.ts +3 -3
  50. package/dist/lib/tools-inspect.js +203 -708
  51. package/dist/lib/tools-local.js +2 -10
  52. package/dist/lib/tools-message.d.ts +7 -7
  53. package/dist/lib/tools-message.js +95 -396
  54. package/dist/lib/tools-response.d.ts +1 -4
  55. package/dist/lib/tools-response.js +5 -39
  56. package/dist/lib/tools-spawn.js +16 -28
  57. package/dist/lib/tools.js +1 -2
  58. package/dist/lib/trace-projection.d.ts +22 -0
  59. package/dist/lib/trace-projection.js +165 -0
  60. package/dist/recipes/draft-review.json +0 -10
  61. package/dist/recipes/lens-swarm.json +0 -14
  62. package/dist/recipes/music-player.json +10 -19
  63. package/dist/recipes/pipeline-architect-coordinator.json +0 -11
  64. package/dist/recipes/pipeline-artifact-bundle.json +1 -22
  65. package/dist/recipes/pipeline-artifact-report.json +1 -18
  66. package/dist/recipes/pipeline-artifact-write.json +1 -18
  67. package/dist/recipes/pipeline-async-run-ops.json +0 -12
  68. package/dist/recipes/pipeline-checkpoint-continuation.json +0 -14
  69. package/dist/recipes/pipeline-development-tasking.json +0 -12
  70. package/dist/recipes/pipeline-docs-maintenance.json +0 -12
  71. package/dist/recipes/pipeline-media-library.json +0 -12
  72. package/dist/recipes/pipeline-quorum-review.json +0 -12
  73. package/dist/recipes/pipeline-release-readiness.json +0 -12
  74. package/dist/recipes/pipeline-release-summary.json +0 -12
  75. package/dist/recipes/pipeline-repo-health.json +0 -12
  76. package/dist/recipes/pipeline-research-synthesis.json +0 -11
  77. package/dist/recipes/pipeline-review-readiness.json +0 -12
  78. package/dist/recipes/resource-locker.json +27 -0
  79. package/dist/recipes/subagent-artifact.json +0 -9
  80. package/dist/recipes/subagent-checkpoint.json +0 -10
  81. package/dist/recipes/subagent-conflict-report.json +0 -11
  82. package/dist/recipes/subagent-contradiction-map.json +0 -11
  83. package/dist/recipes/subagent-critic.json +0 -11
  84. package/dist/recipes/subagent-evidence-map.json +0 -11
  85. package/dist/recipes/subagent-followup.json +0 -10
  86. package/dist/recipes/subagent-judge.json +0 -11
  87. package/dist/recipes/subagent-merge.json +0 -11
  88. package/dist/recipes/subagent-normalize.json +0 -11
  89. package/dist/recipes/subagent-plan.json +0 -11
  90. package/dist/recipes/subagent-preflight.json +0 -11
  91. package/dist/recipes/subagent-prompt.json +0 -10
  92. package/dist/recipes/subagent-quorum.json +0 -10
  93. package/dist/recipes/subagent-review-coordinator.json +0 -14
  94. package/dist/recipes/subagent-review.json +0 -11
  95. package/dist/recipes/subagent-task-card.json +0 -11
  96. package/dist/recipes/subagent-tools.json +0 -10
  97. package/dist/recipes/subagent-verify.json +0 -11
  98. package/dist/recipes/subagents-prompts.json +0 -10
  99. package/dist/recipes/tool-review.json +0 -10
  100. package/dist/scripts/async-runner.mjs +25 -25
  101. package/dist/scripts/conformance.mjs +4 -2
  102. package/dist/scripts/locker.mjs +200 -66
  103. package/dist/scripts/music-player.mjs +159 -150
  104. package/dist/scripts/recipe-utils.mjs +6 -96
  105. package/dist/scripts/release-gates.mjs +60 -0
  106. package/dist/scripts/validate-recipe.mjs +3 -53
  107. package/dist/skills/actors/SKILL.md +53 -266
  108. package/dist/skills/swarm/SKILL.md +11 -33
  109. package/docs/0.43-baseline.md +44 -0
  110. package/docs/README.md +3 -3
  111. package/docs/actor-inspector.md +26 -64
  112. package/docs/actors-deep-reference.md +92 -50
  113. package/docs/async-runs.md +81 -328
  114. package/docs/command-templates.md +2 -2
  115. package/docs/component-recipes.md +30 -133
  116. package/docs/recipe-library.md +57 -182
  117. package/docs/task-first-recipes.md +10 -12
  118. package/docs/template-recipes.md +76 -289
  119. package/docs/tool-registry.md +41 -161
  120. package/fixtures/protocol/control-endpoint.json +6 -0
  121. package/fixtures/protocol/control-record.json +9 -0
  122. package/fixtures/protocol/recipe-summary.json +4 -12
  123. package/fixtures/protocol/trace-event.json +9 -0
  124. package/lib/async-runs.ts +202 -201
  125. package/lib/control.ts +102 -0
  126. package/lib/execution-sessions.ts +111 -0
  127. package/lib/file-state.ts +17 -4
  128. package/lib/inspector-actions.ts +2 -2
  129. package/lib/inspector-command.ts +3 -3
  130. package/lib/inspector-overlay.ts +577 -1121
  131. package/lib/inspector.ts +46 -979
  132. package/lib/limits.ts +4 -2
  133. package/lib/observability.ts +63 -104
  134. package/lib/pi.ts +1 -1
  135. package/lib/prompts.ts +2 -2
  136. package/lib/recipe-control.ts +45 -0
  137. package/lib/recipes-discovery.ts +2 -0
  138. package/lib/recipes-references.ts +9 -45
  139. package/lib/review-projection.ts +1 -5
  140. package/lib/run-ui-runtime.ts +2 -2
  141. package/lib/runs-control-delivery.ts +181 -0
  142. package/lib/runs-controls.ts +204 -0
  143. package/lib/runs-retention.ts +38 -3
  144. package/lib/runs-start.ts +4 -2
  145. package/lib/runs-status.ts +11 -6
  146. package/lib/runs-trace.ts +132 -0
  147. package/lib/runtime-notifier.ts +1 -1
  148. package/lib/tools-inspect.ts +240 -901
  149. package/lib/tools-local.ts +2 -12
  150. package/lib/tools-message.ts +112 -519
  151. package/lib/tools-response.ts +5 -52
  152. package/lib/tools-spawn.ts +16 -32
  153. package/lib/tools.ts +1 -2
  154. package/lib/trace-projection.ts +221 -0
  155. package/package.json +2 -1
  156. package/recipes/draft-review.json +0 -10
  157. package/recipes/lens-swarm.json +0 -14
  158. package/recipes/music-player.json +10 -19
  159. package/recipes/pipeline-architect-coordinator.json +0 -11
  160. package/recipes/pipeline-artifact-bundle.json +1 -22
  161. package/recipes/pipeline-artifact-report.json +1 -18
  162. package/recipes/pipeline-artifact-write.json +1 -18
  163. package/recipes/pipeline-async-run-ops.json +0 -12
  164. package/recipes/pipeline-checkpoint-continuation.json +0 -14
  165. package/recipes/pipeline-development-tasking.json +0 -12
  166. package/recipes/pipeline-docs-maintenance.json +0 -12
  167. package/recipes/pipeline-media-library.json +0 -12
  168. package/recipes/pipeline-quorum-review.json +0 -12
  169. package/recipes/pipeline-release-readiness.json +0 -12
  170. package/recipes/pipeline-release-summary.json +0 -12
  171. package/recipes/pipeline-repo-health.json +0 -12
  172. package/recipes/pipeline-research-synthesis.json +0 -11
  173. package/recipes/pipeline-review-readiness.json +0 -12
  174. package/recipes/resource-locker.json +27 -0
  175. package/recipes/subagent-artifact.json +0 -9
  176. package/recipes/subagent-checkpoint.json +0 -10
  177. package/recipes/subagent-conflict-report.json +0 -11
  178. package/recipes/subagent-contradiction-map.json +0 -11
  179. package/recipes/subagent-critic.json +0 -11
  180. package/recipes/subagent-evidence-map.json +0 -11
  181. package/recipes/subagent-followup.json +0 -10
  182. package/recipes/subagent-judge.json +0 -11
  183. package/recipes/subagent-merge.json +0 -11
  184. package/recipes/subagent-normalize.json +0 -11
  185. package/recipes/subagent-plan.json +0 -11
  186. package/recipes/subagent-preflight.json +0 -11
  187. package/recipes/subagent-prompt.json +0 -10
  188. package/recipes/subagent-quorum.json +0 -10
  189. package/recipes/subagent-review-coordinator.json +0 -14
  190. package/recipes/subagent-review.json +0 -11
  191. package/recipes/subagent-task-card.json +0 -11
  192. package/recipes/subagent-tools.json +0 -10
  193. package/recipes/subagent-verify.json +0 -11
  194. package/recipes/subagents-prompts.json +0 -10
  195. package/recipes/tool-review.json +0 -10
  196. package/scripts/async-runner.mjs +25 -25
  197. package/scripts/conformance.mjs +4 -2
  198. package/scripts/locker.mjs +200 -66
  199. package/scripts/music-player.mjs +159 -150
  200. package/scripts/recipe-utils.mjs +6 -96
  201. package/scripts/release-gates.mjs +60 -0
  202. package/scripts/validate-recipe.mjs +3 -53
  203. package/skills/actors/SKILL.md +53 -266
  204. package/skills/swarm/SKILL.md +11 -33
  205. package/dist/fixtures/protocol/actor-message-branch.json +0 -13
  206. package/dist/fixtures/protocol/mailbox-contract.json +0 -15
  207. package/dist/fixtures/protocol/room-message.json +0 -11
  208. package/dist/fixtures/protocol/room-roster.json +0 -11
  209. package/dist/fixtures/protocol/run-inbox-message.json +0 -9
  210. package/dist/fixtures/protocol/run-outbox-event.json +0 -9
  211. package/dist/lib/mailbox-loop.d.ts +0 -41
  212. package/dist/lib/mailbox-loop.js +0 -60
  213. package/dist/lib/messages.d.ts +0 -25
  214. package/dist/lib/messages.js +0 -122
  215. package/dist/lib/rooms.d.ts +0 -104
  216. package/dist/lib/rooms.js +0 -647
  217. package/dist/lib/runs-mailbox.d.ts +0 -25
  218. package/dist/lib/runs-mailbox.js +0 -146
  219. package/dist/lib/runs-messages.d.ts +0 -15
  220. package/dist/lib/runs-messages.js +0 -179
  221. package/dist/lib/runs-outbox.d.ts +0 -41
  222. package/dist/lib/runs-outbox.js +0 -87
  223. package/dist/lib/tools-mailbox.d.ts +0 -8
  224. package/dist/lib/tools-mailbox.js +0 -48
  225. package/dist/recipes/actor-worker.json +0 -39
  226. package/dist/recipes/coordinator-locker.json +0 -45
  227. package/dist/recipes/locker.json +0 -45
  228. package/dist/recipes/pipeline-room-swarm.json +0 -50
  229. package/dist/recipes/subagent-message.json +0 -32
  230. package/dist/recipes/utility-actor-message.json +0 -23
  231. package/dist/scripts/actor-worker.mjs +0 -214
  232. package/dist/scripts/coordinator.mjs +0 -799
  233. package/docs/actor-messages.md +0 -225
  234. package/fixtures/protocol/actor-message-branch.json +0 -13
  235. package/fixtures/protocol/mailbox-contract.json +0 -15
  236. package/fixtures/protocol/room-message.json +0 -11
  237. package/fixtures/protocol/room-roster.json +0 -11
  238. package/fixtures/protocol/run-inbox-message.json +0 -9
  239. package/fixtures/protocol/run-outbox-event.json +0 -9
  240. package/lib/mailbox-loop.ts +0 -144
  241. package/lib/messages.ts +0 -151
  242. package/lib/rooms.ts +0 -939
  243. package/lib/runs-mailbox.ts +0 -208
  244. package/lib/runs-messages.ts +0 -252
  245. package/lib/runs-outbox.ts +0 -144
  246. package/lib/tools-mailbox.ts +0 -56
  247. package/recipes/actor-worker.json +0 -39
  248. package/recipes/coordinator-locker.json +0 -45
  249. package/recipes/locker.json +0 -45
  250. package/recipes/pipeline-room-swarm.json +0 -50
  251. package/recipes/subagent-message.json +0 -32
  252. package/recipes/utility-actor-message.json +0 -23
  253. package/scripts/actor-worker.mjs +0 -214
  254. package/scripts/coordinator.mjs +0 -799
@@ -1,225 +0,0 @@
1
- # Actor Messages
2
-
3
- Protocol target for organic communication across pi-actors.
4
-
5
- ## Contract
6
-
7
- Compress communication to three durable verbs:
8
-
9
- - `spawn`: create an addressable actor from a recipe, template, or tool.
10
- - `message`: send one typed message to one address.
11
- - `inspect`: intentionally observe state, logs, actor messages, or artifacts.
12
-
13
- Everything else is an adapter until proven otherwise.
14
-
15
- ## Nouns
16
-
17
- - **Actor**: any addressable execution or coordination endpoint.
18
- - **Address**: stable route string for an actor or sub-actor.
19
- - **Message**: typed envelope flowing between addresses.
20
- - **Artifact**: durable result path declared by recipe or produced by actor.
21
- - **Inspection**: explicit diagnostic/read operation, not a coordination loop.
22
-
23
- ## Addresses
24
-
25
- Initial address forms:
26
-
27
- ```text
28
- run:<id>
29
- branch:<run>/<branch>
30
- coordinator
31
- session:<id>
32
- tool:<name>
33
- ```
34
-
35
- Cross-branch communication adds one organic endpoint kind:
36
-
37
- ```text
38
- room:<run>
39
- ```
40
-
41
- A room is the task's single shared discussion channel: an addressable mailbox with an append-only message log and a compact member roster stored under the owning run state. It is not a broker or coordinator: it accepts normal actor-message envelopes, records shared timeline entries, tracks join/leave presence, and lets actors discover peers for direct messages. `room:<run>` is the public address; named subrooms are intentionally not exposed in 0.17.
42
-
43
- An alternate implementation shape is a dedicated non-LLM communication actor: a small script-backed service recipe, possibly singleton-scoped, that owns room timelines, rosters, subscriptions, and fanout. This is attractive when communication needs outgrow simple file-backed room state, but it should remain an implementation adapter behind the same `room:<run>` address and message envelope. The public model should not fork into a separate chat API.
44
-
45
- That actor-backed shape can also reduce direct file storage. Instead of every protocol feature owning JSON files as primary state, a helper actor can keep live room/roster structures in memory or another local structure and write files only as snapshots, audit logs, artifacts, or recovery checkpoints. The decision boundary is practical: keep files when durability and inspectability are the main value; prefer actor-owned structures when live coordination, subscriptions, fanout, unread state, or mutation consistency becomes the main value.
46
-
47
- Current backend decision: keep the file-backed adapter for now. The covered workload is append-heavy room coordination plus direct branch inbox queueing/claiming, where durable local files are still the useful source of truth for recovery and `inspect`. Live notification is a separate advisory wake layer: actors may subscribe to `wake.jsonl` changes through a cross-platform file notifier and still reconcile canonical mailbox files if a wake is missed. A communication helper should be introduced only when a real workflow needs long-lived subscriptions, live fanout policy, or shared mutable room state beyond the current lock/debounce/compaction safeguards.
48
-
49
- Package-specific endpoints may still exist, but the envelope stays the same.
50
-
51
- ## Message Envelope
52
-
53
- One shape covers upward, downward, lateral, parent-to-branch, and branch-to-parent traffic:
54
-
55
- ```json
56
- {
57
- "to": "run:review",
58
- "from": "coordinator",
59
- "type": "control.approve",
60
- "summary": "Approve checkpoint",
61
- "body": "approve",
62
- "reply_to": "msg_123",
63
- "correlation_id": "task_456",
64
- "metadata": {}
65
- }
66
- ```
67
-
68
- Field rules:
69
-
70
- - `to`: required address.
71
- - `from`: optional address; runtime fills when known.
72
- - `type`: required semantic message type. Prefer compact dotted names such as `control.stop`, `task.claim`, or `player.next`, where the prefix is the interaction channel/domain and the suffix is the action. Many script-backed actors should be able to dispatch from `type` alone without requiring a structured body.
73
- - `summary`: short human-facing line for notifications/follow-ups.
74
- - `body`: optional string or JSON payload. Use it when extra context is needed: scripts may ignore it for action-only messages, while LLM-backed agents can accept free-form natural-language prompts without a rigid schema.
75
- - Routing/delivery is inferred from `to`, actor ownership, and coordinator runtime policy; recipes should not expose delivery knobs. When a coordinator session is known, addressed run/branch/control messages fail closed before controlling or emitting from runs owned by another session.
76
- - `reply_to`: optional message id for conversational checkpoints.
77
- - `correlation_id`: optional task/run/workflow id.
78
- - `metadata`: optional structured routing or domain hints.
79
-
80
- ## Symmetry
81
-
82
- The same `message` primitive must represent:
83
-
84
- ```text
85
- coordinator -> run
86
- run -> coordinator
87
- run -> run
88
- parent -> branch
89
- branch -> parent
90
- branch -> room
91
- room -> branch notification
92
- coordinator -> tool
93
- ```
94
-
95
- Transports differ, but the public contract does not:
96
-
97
- - `to: run:<id>` routes through the run-local control channel selected by that recipe or runtime adapter.
98
- - `to: coordinator` routes to the runtime attention path when `from` names a run actor. `to: session:<id>` uses the same actor-message path only when the sender run is owned by that session, making explicit session-directed checkpoints possible without exposing runtime delivery knobs. Generic async-runner `command.done` messages and explicit coordinator/session-bound messages include the actor envelope fields alongside runtime metadata.
99
- - `to: branch:<run>/<branch>` currently routes through the parent run mailbox with the full envelope preserved so the run or recipe-specific worker protocol can dispatch branch-local control. It also persists a queued branch-local copy under `branches/<branch>/inbox.jsonl`, inspectable with `inspect branch:<run>/<branch> view=mailbox`; compact inspection includes the inbox message `id`, status, route, type, and timestamps so worker protocols can correlate claims/retries. Branch-local inbox append and status rewrites are guarded by a small lock so direct delivery and coordinator claims do not overwrite each other during bursts. Status transitions preserve active queued/claimed records and compact older handled/failed terminal records with bounded retention, so persistent runners do not accumulate unbounded completed inbox history. Coordinator claim handling also assigns an ID to older/manual queued records that do not have one so they can still transition to `handled` or `failed` instead of repeating forever. It is not a broadcast room and it does not make an arbitrary prompt process consume the message automatically. Target direction: direct branch messages should become initiating inbox work for long-lived branch runners, delivered into the recipient's next prompt/context as soon as the runner can accept work.
100
- - `to: room:<run>` appends the full envelope to the room timeline, updates room state for room-control types such as `actor.join` and `actor.leave`, and can route selected-recipient multicast when `metadata.recipients` contains same-run `branch:<run>/<branch>` addresses.
101
- - `to: tool:<name>` invokes an executable pi tool by name. Object bodies become tool parameters; primitive bodies are passed as `{ "input": body }`.
102
-
103
- Transport is not public API unless a recipe explicitly documents a custom endpoint.
104
-
105
- ## Rooms and Rosters
106
-
107
- The task room is the discovery and shared-context layer for actors whose spawn-tree positions do not give them each other's addresses. The spawn tree remains the lifecycle/provenance structure; the task room describes the group communication graph. Direct messages and room messages can share the same semantic `type` such as `chat.message`; the route (`to: branch:*` versus `to: room:*`) determines whether delivery is private or group-wide.
108
-
109
- Use direct branch messages only when the receiving branch is backed by a worker or recipe that reads the parent run mailbox or branch inbox and dispatches branch-targeted envelopes. Room roster contacts are discovery hints, not a guarantee that an independent prompt process is subscribed to its branch address. The current branch inbox records queued mailbox work; runner-side claiming/handling turns direct messages into prompt work for the recipient branch, while room messages remain shared transcript entries. A direct message may ask the recipient to inspect room history when broader shared context is needed. For ad hoc or transcript-driven swarms without such a runner, prefer room-visible replies and mentions so every participant can inspect the shared timeline.
110
-
111
- Direct branch delivery is prompt steering for worker-backed branches, not a coordinator follow-up. Packaged coordinator flows claim queued branch inbox records immediately before launching the branch's next prompt, append a bounded "direct messages for you" section to that prompt, and then mark the claimed records `handled` or `failed` based on the prompt result. Generic one-shot `pi -p` children do not receive this injection automatically; a recipe must own the runner loop or use the packaged coordinator path for direct messages to become next-prompt work.
112
-
113
- Selected-recipient multicast stays route-based: send one `to: room:<run>` envelope with `metadata.recipients` set to same-run branch addresses. The room timeline keeps the original room-visible envelope, and the runtime also forwards branch-targeted copies to each listed recipient. This is not a subroom; it is a shared transcript plus explicit direct delivery for actors whose worker protocol consumes branch envelopes.
114
-
115
- A minimal join message:
116
-
117
- ```json
118
- {
119
- "to": "room:review",
120
- "from": "branch:review/security",
121
- "type": "actor.join",
122
- "summary": "Security reviewer joined",
123
- "body": {
124
- "role": "reviewer",
125
- "caps": ["security-review", "risk-analysis"],
126
- "claim": "Review auth boundary risks"
127
- }
128
- }
129
- ```
130
-
131
- A leave message removes that actor from the roster while preserving the timeline entry:
132
-
133
- ```json
134
- {
135
- "to": "room:review",
136
- "from": "branch:review/security",
137
- "type": "actor.leave"
138
- }
139
- ```
140
-
141
- Room messages require `from` so roster presence and provenance stay explicit. The sender must belong to the room's run (`run:<run>` or `branch:<run>/<branch>`), which prevents accidental cross-run roster pollution. Other room posts also refresh sender presence, defaulting the role hint to `actor` when no richer role is known.
142
-
143
- Roster entries should keep identity axes separate:
144
-
145
- - `address`: Stable route for direct messages.
146
- - `parent`: Spawn-tree parent for provenance and ownership checks.
147
- - `role`: Current task function; dynamic and prompt-dependent.
148
- - `caps`: Capabilities the actor can offer.
149
- - `claim`: Current work claim or focus.
150
- - `status` / `last_seen`: Presence and staleness hints.
151
-
152
- Compact roster inspection includes these hints when present so agents can discover direct-message targets without verbose JSON.
153
-
154
- Actors should receive a compact visible communication snapshot rather than a full global tree: self, parent/root, joined rooms, relevant sibling/member addresses, and role/capability hints. Current run actors get `communication.json` in their state dir, and async templates receive `{communication_file}`, `{actor_address}`, and `{default_room}` values. The snapshot includes `self`, `root`, optional `parent`, the default room, current default-room members, direct-message `contacts` derived from the room roster, and `updated_at`. Branch-local snapshots are refreshed when a branch joins or posts in the default room, so actors can discover peers without reading full timelines. Full timelines and rosters remain intentional inspection surfaces. For TUI and compact operator display, `view=previews` returns bounded message preview records with `timestamp`, `from`, `to`, `type`, optional `summary`, and optional `body_preview`.
155
-
156
- ## Mailbox Declaration
157
-
158
- Recipes can declare their conversational surface:
159
-
160
- ```json
161
- {
162
- "mailbox": {
163
- "accepts": [
164
- "control.continue",
165
- "control.revise",
166
- "control.approve",
167
- "control.kill"
168
- ],
169
- "emits": ["checkpoint.needs_scope", "branch.done", "run.done"]
170
- }
171
- }
172
- ```
173
-
174
- `mailbox.accepts` is a contract for coordinator-to-actor messages. `mailbox.emits` is a contract for actor-to-coordinator or actor-to-actor messages. Packaged interactive and message-producing recipes declare mailbox metadata so coordinators can discover semantic message types without reading transport details. Message-producing recipes produce actor-message-envelope-shaped records with `to`, `from`, `type`, `summary`, `body`, optional `correlation_id`/`reply_to`, and optional `metadata` fields. Coordinator follow-ups preserve bounded body previews and metadata so checkpoints do not lose their actionable payload. Deterministic pipelines should prefer `utility-actor-message` for this wrapping so message shape is validated and guaranteed instead of delegated to a prompt; its recipe args intentionally mirror the envelope field names.
175
-
176
- ## Spawn
177
-
178
- `spawn` creates an actor and returns its address:
179
-
180
- ```json
181
- {
182
- "recipe": "subagents-prompts.json",
183
- "as": "run:review",
184
- "values": {},
185
- "artifacts": { "report": "{state_dir}/report.md" }
186
- }
187
- ```
188
-
189
- `spawn` creates detached `run:<id>` actors from a recipe file/name or inline command template. Public spawn state directories are runtime-owned so every actor remains addressable and retention cannot target a caller-selected directory; spawn metadata may include named `artifacts` for terminal follow-ups and inspection. Room rosters are durable but burst-safe: repeated messages that only update `last_seen` may be coalesced briefly, while semantic roster changes such as role/status/display still write immediately.
190
-
191
- ## Inspect
192
-
193
- `inspect` reads state intentionally:
194
-
195
- ```json
196
- {
197
- "target": "run:review",
198
- "view": "status"
199
- }
200
- ```
201
-
202
- The implementation supports `status`, `tail`, `messages`, `artifacts`, `files`, `mailbox`, and `communication` for `run:<id>` actors, `status`, `messages`, `previews`, `roster`, and `contacts` for `room:<run>` actors, `status`/`runs` for `coordinator`, `session:<id>`, and `session:all` actors with optional status filtering, and `status`/`schema` for registered `tool:<name>` actors. Run mailbox inspection shows recipe-declared mailbox metadata plus recent durable run inbox entries; branch mailbox inspection shows branch-local queued/claimed/handled records. Room `status` returns compact message/roster counts plus `last_message_at`, `last_message_from`, `last_message_type`, and `last_message_summary` when available, without parsing the full timeline into actor envelopes. Use `messages` for actor-envelope inspection. `inspect target=coordinator` requires a current coordinator session; use `session:<id>` or `session:all` when the session is intentionally explicit. Direct `run:<id>` and `room:<run>` inspection respects coordinator-session ownership when the current session is known. Ownership denials use `reason=session_mismatch owner_session=<id> current_session=<id> hint=inspect_session:<id>`; recover by inspecting the hinted `session:<id>` instead of forcing cross-session control. `inspect` is for decision points and diagnosis only; examples must not teach sleep-then-inspect polling.
203
-
204
- ## Runtime Direction
205
-
206
- Runtime operations use the actor/message vocabulary:
207
-
208
- ```text
209
- create detached work -> spawn
210
- run-local control -> message to run:<id>
211
- run force-kill -> message type control.kill
212
- platform control -> internal adapter selected from run state
213
- coordinator signal -> message to coordinator/session
214
- tool execution -> message to tool:<name>
215
- intentional observe -> inspect
216
- ```
217
-
218
- ## Non-goals
219
-
220
- - No generic expression language in templates.
221
- - No public transport-path vocabulary in recipe args.
222
- - No polling-first examples.
223
- - No separate upward and downward message schemas.
224
- - No heavyweight chat/broker subsystem when an addressable room mailbox is enough.
225
- - No broad facade that hides artifacts, logs, or ownership checks.
@@ -1,13 +0,0 @@
1
- {
2
- "to": "branch:demo/reviewer",
3
- "from": "run:demo",
4
- "type": "task.assign",
5
- "summary": "Review the current slice",
6
- "body": {
7
- "task": "Check mailbox loop semantics"
8
- },
9
- "correlation_id": "task-001",
10
- "metadata": {
11
- "requires_response": true
12
- }
13
- }
@@ -1,15 +0,0 @@
1
- {
2
- "accepts": [
3
- "task.assign",
4
- {
5
- "type": "control.stop",
6
- "description": "Request graceful worker shutdown"
7
- }
8
- ],
9
- "emits": [
10
- "task.claim",
11
- "task.result",
12
- "awaiting_assignment",
13
- "actor.leave"
14
- ]
15
- }
@@ -1,11 +0,0 @@
1
- {
2
- "to": "room:demo",
3
- "from": "branch:demo/worker",
4
- "type": "task.result",
5
- "summary": "Worker completed task",
6
- "body": {
7
- "id": "inbox-001",
8
- "result": "ok"
9
- },
10
- "received_at": "2026-05-26T00:00:00.000Z"
11
- }
@@ -1,11 +0,0 @@
1
- {
2
- "room": "main",
3
- "members": {
4
- "branch:demo/worker": {
5
- "display": "worker",
6
- "role": "worker",
7
- "status": "present",
8
- "last_seen": "2026-05-26T00:00:00.000Z"
9
- }
10
- }
11
- }
@@ -1,9 +0,0 @@
1
- {
2
- "id": "inbox-001",
3
- "status": "queued",
4
- "queued_at": "2026-05-26T00:00:00.000Z",
5
- "to": "run:demo",
6
- "from": "coordinator",
7
- "type": "control.continue",
8
- "body": "continue"
9
- }
@@ -1,9 +0,0 @@
1
- {
2
- "id": "event-001",
3
- "type": "task.result",
4
- "summary": "Worker completed task",
5
- "body": {
6
- "result": "ok"
7
- },
8
- "created_at": "2026-05-26T00:00:00.000Z"
9
- }
@@ -1,144 +0,0 @@
1
- /**
2
- * Mailbox worker loop primitives.
3
- * Zones: mailbox-consuming actors, run/branch inbox claiming, handler status transitions
4
- * Owns reusable claim/handle/drain behavior across run and branch inboxes; scheduling and task policy stay outside.
5
- */
6
-
7
- import {
8
- claimBranchInboxMessage,
9
- updateBranchInboxMessageStatus,
10
- type BranchInboxRecord,
11
- } from "./rooms.ts";
12
- import {
13
- claimRunInboxMessage,
14
- updateRunInboxMessageStatus,
15
- type RunInboxMessage,
16
- } from "./async-runs.ts";
17
-
18
- export type MailboxLoopMessage = RunInboxMessage | BranchInboxRecord;
19
-
20
- export type MailboxLoopTarget =
21
- | {
22
- kind: "run";
23
- runOrDir: string;
24
- }
25
- | {
26
- address: string;
27
- kind: "branch";
28
- run: string;
29
- stateDir: string;
30
- };
31
-
32
- export interface MailboxLoopClaimOptions {
33
- owner?: string;
34
- statuses?: string[];
35
- }
36
-
37
- export interface MailboxLoopHandleResult {
38
- handled: boolean;
39
- id?: string;
40
- message?: MailboxLoopMessage;
41
- target: MailboxLoopTarget;
42
- }
43
-
44
- export interface MailboxLoopDrainOptions extends MailboxLoopClaimOptions {
45
- maxMessages?: number;
46
- stopOnControl?: boolean;
47
- }
48
-
49
- export interface MailboxLoopDrainResult {
50
- handled: number;
51
- stopped: boolean;
52
- target: MailboxLoopTarget;
53
- }
54
-
55
- export function isMailboxLoopStopMessage(message: unknown): boolean {
56
- const type =
57
- message && typeof message === "object" && "type" in message
58
- ? (message as { type?: unknown }).type
59
- : undefined;
60
- return type === "control.kill";
61
- }
62
-
63
- function messageId(
64
- message: MailboxLoopMessage | undefined,
65
- ): string | undefined {
66
- return typeof message?.id === "string" ? message.id : undefined;
67
- }
68
-
69
- export function claimMailboxLoopMessage(
70
- target: MailboxLoopTarget,
71
- options: MailboxLoopClaimOptions = {},
72
- ): MailboxLoopMessage | undefined {
73
- const owner = options.owner ?? "mailbox-loop";
74
- const statuses = options.statuses ?? ["queued"];
75
- return target.kind === "run"
76
- ? claimRunInboxMessage(target.runOrDir, owner, statuses)
77
- : claimBranchInboxMessage(
78
- target.stateDir,
79
- target.run,
80
- target.address,
81
- owner,
82
- statuses,
83
- );
84
- }
85
-
86
- export function updateMailboxLoopMessageStatus(
87
- target: MailboxLoopTarget,
88
- id: string,
89
- status: "claimed" | "handled" | "failed",
90
- metadata: Record<string, unknown> = {},
91
- ): boolean {
92
- return target.kind === "run"
93
- ? updateRunInboxMessageStatus(target.runOrDir, id, status, metadata)
94
- : updateBranchInboxMessageStatus(
95
- target.stateDir,
96
- target.run,
97
- target.address,
98
- id,
99
- status,
100
- metadata,
101
- );
102
- }
103
-
104
- export async function handleMailboxLoopOnce(
105
- target: MailboxLoopTarget,
106
- handler: (message: MailboxLoopMessage) => Promise<void> | void,
107
- options: MailboxLoopClaimOptions = {},
108
- ): Promise<MailboxLoopHandleResult> {
109
- const message = claimMailboxLoopMessage(target, options);
110
- const id = messageId(message);
111
- if (!message || !id) return { handled: false, target };
112
- try {
113
- await handler(message);
114
- updateMailboxLoopMessageStatus(target, id, "handled");
115
- return { handled: true, id, message, target };
116
- } catch (error) {
117
- updateMailboxLoopMessageStatus(target, id, "failed", {
118
- error: error instanceof Error ? error.message : String(error),
119
- });
120
- throw error;
121
- }
122
- }
123
-
124
- export async function drainMailboxLoopMessages(
125
- target: MailboxLoopTarget,
126
- handler: (message: MailboxLoopMessage) => Promise<void> | void,
127
- options: MailboxLoopDrainOptions = {},
128
- ): Promise<MailboxLoopDrainResult> {
129
- const maxMessages = Math.max(1, Math.floor(options.maxMessages ?? 100));
130
- let handled = 0;
131
- for (; handled < maxMessages; handled += 1) {
132
- const result = await handleMailboxLoopOnce(target, handler, options);
133
- if (!result.handled || !result.message) {
134
- return { handled, stopped: false, target };
135
- }
136
- if (
137
- options.stopOnControl !== false &&
138
- isMailboxLoopStopMessage(result.message)
139
- ) {
140
- return { handled: handled + 1, stopped: true, target };
141
- }
142
- }
143
- return { handled, stopped: false, target };
144
- }
package/lib/messages.ts DELETED
@@ -1,151 +0,0 @@
1
- /**
2
- * Actor message protocol.
3
- * Zones: addressed envelopes, address parsing, route normalization
4
- * Owns pure validation/normalization for semantic actor messages; transport routing stays in adapters.
5
- */
6
-
7
- export type ActorAddressKind =
8
- | "branch"
9
- | "coordinator"
10
- | "room"
11
- | "run"
12
- | "session"
13
- | "tool";
14
-
15
- export interface ActorAddress {
16
- kind: ActorAddressKind;
17
- value?: string;
18
- branch?: string;
19
- room?: string;
20
- }
21
-
22
- export interface ActorMessage {
23
- to: string;
24
- type: string;
25
- body?: unknown;
26
- correlation_id?: string;
27
- from?: string;
28
- metadata?: Record<string, unknown>;
29
- reply_to?: string;
30
- summary?: string;
31
- }
32
-
33
- const ADDRESS_PATTERN = /^[A-Za-z0-9_.-]+$/;
34
- const MESSAGE_TYPE_PATTERN = /^[A-Za-z][A-Za-z0-9_.:-]*$/;
35
-
36
- function assertToken(value: string, label: string): string {
37
- const normalized = value.trim();
38
- if (!normalized) throw new Error(`${label} is required`);
39
- if (!ADDRESS_PATTERN.test(normalized)) {
40
- throw new Error(`${label} contains unsupported characters: ${value}`);
41
- }
42
- return normalized;
43
- }
44
-
45
- export function parseActorAddress(address: string): ActorAddress {
46
- const value = address.trim();
47
- if (value === "coordinator") return { kind: "coordinator" };
48
- const separator = value.indexOf(":");
49
- if (separator < 0)
50
- throw new Error(`Actor address must include kind: ${address}`);
51
- const kind = value.slice(0, separator) as ActorAddressKind;
52
- const rest = value.slice(separator + 1);
53
- switch (kind) {
54
- case "branch": {
55
- const [run, branch, ...extra] = rest.split("/");
56
- if (extra.length > 0)
57
- throw new Error(`Branch address has too many parts: ${address}`);
58
- return {
59
- kind,
60
- value: assertToken(run || "", "branch run"),
61
- branch: assertToken(branch || "", "branch id"),
62
- };
63
- }
64
- case "room": {
65
- const [run, room, ...extra] = rest.split("/");
66
- if (extra.length > 0)
67
- throw new Error(`Room address has too many parts: ${address}`);
68
- if (room && room !== "main") {
69
- throw new Error("Task rooms do not support named subrooms; use room:<run>.");
70
- }
71
- return {
72
- kind,
73
- value: assertToken(run || "", "room run"),
74
- room: "main",
75
- };
76
- }
77
- case "run":
78
- case "session":
79
- case "tool":
80
- return { kind, value: assertToken(rest, `${kind} address`) };
81
- default:
82
- throw new Error(`Unsupported actor address kind: ${kind}`);
83
- }
84
- }
85
-
86
- export function formatActorAddress(address: ActorAddress): string {
87
- if (address.kind === "coordinator") return "coordinator";
88
- if (address.kind === "branch") {
89
- return `branch:${assertToken(address.value || "", "branch run")}/${assertToken(address.branch || "", "branch id")}`;
90
- }
91
- if (address.kind === "room") {
92
- return `room:${assertToken(address.value || "", "room run")}`;
93
- }
94
- return `${address.kind}:${assertToken(address.value || "", `${address.kind} address`)}`;
95
- }
96
-
97
- function normalizeOptionalString(
98
- value: unknown,
99
- label: string,
100
- ): string | undefined {
101
- if (value === undefined || value === null) return undefined;
102
- if (typeof value !== "string") throw new Error(`${label} must be a string`);
103
- const normalized = value.trim();
104
- return normalized || undefined;
105
- }
106
-
107
- function normalizeMetadata(
108
- value: unknown,
109
- ): Record<string, unknown> | undefined {
110
- if (value === undefined || value === null) return undefined;
111
- if (typeof value !== "object" || Array.isArray(value)) {
112
- throw new Error("message metadata must be an object");
113
- }
114
- return value as Record<string, unknown>;
115
- }
116
-
117
- export function normalizeActorMessage(input: unknown): ActorMessage {
118
- if (!input || typeof input !== "object" || Array.isArray(input)) {
119
- throw new Error("actor message must be an object");
120
- }
121
- const record = input as Record<string, unknown>;
122
- const to = normalizeOptionalString(record.to, "message.to");
123
- if (!to) throw new Error("message.to is required");
124
- const parsedTo = parseActorAddress(to);
125
- const type = normalizeOptionalString(record.type, "message.type");
126
- if (!type) throw new Error("message.type is required");
127
- if (!MESSAGE_TYPE_PATTERN.test(type)) {
128
- throw new Error(`message.type contains unsupported characters: ${type}`);
129
- }
130
- const from = normalizeOptionalString(record.from, "message.from");
131
- if (from) parseActorAddress(from);
132
- const normalizedTo = formatActorAddress(parsedTo);
133
- return {
134
- to: normalizedTo,
135
- type,
136
- ...(record.body !== undefined ? { body: record.body } : {}),
137
- ...(record.correlation_id !== undefined
138
- ? { correlation_id: String(record.correlation_id) }
139
- : {}),
140
- ...(from ? { from: formatActorAddress(parseActorAddress(from)) } : {}),
141
- ...(record.metadata !== undefined
142
- ? { metadata: normalizeMetadata(record.metadata) }
143
- : {}),
144
- ...(record.reply_to !== undefined
145
- ? { reply_to: String(record.reply_to) }
146
- : {}),
147
- ...(record.summary !== undefined
148
- ? { summary: String(record.summary) }
149
- : {}),
150
- };
151
- }