@llblab/pi-actors 0.12.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 (86) hide show
  1. package/AGENTS.md +72 -0
  2. package/BACKLOG.md +38 -0
  3. package/CHANGELOG.md +179 -0
  4. package/README.md +338 -0
  5. package/docs/README.md +21 -0
  6. package/docs/actor-messages.md +149 -0
  7. package/docs/async-runs.md +335 -0
  8. package/docs/command-templates.md +424 -0
  9. package/docs/component-recipes.md +148 -0
  10. package/docs/recipe-library.md +176 -0
  11. package/docs/task-first-recipes.md +233 -0
  12. package/docs/template-recipes.md +285 -0
  13. package/docs/tool-registry.md +142 -0
  14. package/index.ts +198 -0
  15. package/lib/actor-messages.ts +120 -0
  16. package/lib/async-runs.ts +688 -0
  17. package/lib/command-templates.ts +795 -0
  18. package/lib/config.ts +266 -0
  19. package/lib/execution.ts +720 -0
  20. package/lib/file-state.ts +24 -0
  21. package/lib/identity.ts +29 -0
  22. package/lib/observability.ts +525 -0
  23. package/lib/output.ts +123 -0
  24. package/lib/paths.ts +35 -0
  25. package/lib/prompts.ts +75 -0
  26. package/lib/recipe-references.ts +586 -0
  27. package/lib/registry.ts +302 -0
  28. package/lib/runtime.ts +101 -0
  29. package/lib/schema.ts +402 -0
  30. package/lib/temp.ts +44 -0
  31. package/lib/tools.ts +651 -0
  32. package/package.json +52 -0
  33. package/recipes/music-player.json +25 -0
  34. package/recipes/pipeline-architect-coordinator.json +88 -0
  35. package/recipes/pipeline-artifact-report.json +52 -0
  36. package/recipes/pipeline-artifact-write.json +66 -0
  37. package/recipes/pipeline-async-run-ops.json +67 -0
  38. package/recipes/pipeline-checkpoint-continuation.json +57 -0
  39. package/recipes/pipeline-development-tasking.json +73 -0
  40. package/recipes/pipeline-docs-maintenance.json +72 -0
  41. package/recipes/pipeline-media-library.json +51 -0
  42. package/recipes/pipeline-quorum-review.json +72 -0
  43. package/recipes/pipeline-release-readiness.json +83 -0
  44. package/recipes/pipeline-repo-health.json +81 -0
  45. package/recipes/pipeline-research-synthesis.json +87 -0
  46. package/recipes/pipeline-review-readiness.json +49 -0
  47. package/recipes/subagent-artifact.json +26 -0
  48. package/recipes/subagent-checkpoint.json +27 -0
  49. package/recipes/subagent-conflict-report.json +25 -0
  50. package/recipes/subagent-contradiction-map.json +26 -0
  51. package/recipes/subagent-critic.json +28 -0
  52. package/recipes/subagent-evidence-map.json +26 -0
  53. package/recipes/subagent-followup.json +27 -0
  54. package/recipes/subagent-judge.json +26 -0
  55. package/recipes/subagent-merge.json +26 -0
  56. package/recipes/subagent-message.json +29 -0
  57. package/recipes/subagent-normalize.json +24 -0
  58. package/recipes/subagent-plan.json +26 -0
  59. package/recipes/subagent-prompt.json +22 -0
  60. package/recipes/subagent-quorum.json +41 -0
  61. package/recipes/subagent-review-coordinator.json +107 -0
  62. package/recipes/subagent-review.json +30 -0
  63. package/recipes/subagent-task-card.json +28 -0
  64. package/recipes/subagent-tools.json +17 -0
  65. package/recipes/subagent-verify.json +27 -0
  66. package/recipes/subagents-prompts.json +32 -0
  67. package/recipes/utility-actor-message.json +24 -0
  68. package/recipes/utility-artifact-manifest.json +17 -0
  69. package/recipes/utility-artifact-write.json +17 -0
  70. package/recipes/utility-changelog-head.json +12 -0
  71. package/recipes/utility-changelog-section.json +14 -0
  72. package/recipes/utility-git-log.json +12 -0
  73. package/recipes/utility-git-status.json +10 -0
  74. package/recipes/utility-jsonl-tail.json +11 -0
  75. package/recipes/utility-markdown-index.json +15 -0
  76. package/recipes/utility-package-summary.json +12 -0
  77. package/recipes/utility-playlist-build.json +18 -0
  78. package/recipes/utility-playlist-scan.json +12 -0
  79. package/recipes/utility-run-state-files.json +14 -0
  80. package/recipes/utility-run-summary.json +12 -0
  81. package/recipes/utility-validate-recipe.json +14 -0
  82. package/recipes/utility-validation-wrapper.json +14 -0
  83. package/scripts/async-runner.mjs +170 -0
  84. package/scripts/music-player.mjs +637 -0
  85. package/scripts/recipe-utils.mjs +273 -0
  86. package/scripts/validate-recipe.mjs +89 -0
@@ -0,0 +1,335 @@
1
+ # Async Run Standard
2
+
3
+ Async runs are detached executions of a template recipe or inline command template.
4
+
5
+ **Meta-contract:** the command template is still the execution graph; the async run is only a lifecycle envelope with state, logs, script-authored events, status, cancellation, and coordinator-scoped observability.
6
+
7
+ **Scope:** run id, state path, runner pid, process-group cancellation, logs, status, tail, list, script-authored event outbox, line-message send to a run-local Unix FIFO, cancel, force-kill, terminal result state, ambient activity indicators, and extension-owned temp storage. No scheduler, queue daemon, workflow DSL, distributed worker, or second execution language.
8
+
9
+ Layer boundary: async-run configuration may inject lifecycle values such as `{run_id}` and `{state_dir}` and may choose detached execution through `async: true`, but it does not add command-template graph syntax. Recipe imports and recipe-local references belong to the template-recipe layer; status, send, events, cancel, and kill belong to the async-run layer.
10
+
11
+ ---
12
+
13
+ ## Layer Ownership
14
+
15
+ Async-run standard owns:
16
+
17
+ - Detached process lifecycle for one execution instance.
18
+ - Run identity, state directory, pid/process-group tracking, logs, status, list, tail, events, send, cancel, and kill.
19
+ - Injected lifecycle values such as `{run_id}` and `{state_dir}`.
20
+ - Coordinator-scoped observability and script-authored actor messages.
21
+
22
+ Async-run standard does not own:
23
+
24
+ - Command-template syntax, placeholders, graph semantics, or branch policy.
25
+ - Recipe import resolution, recipe names, or recipe storage format.
26
+ - Domain semantics for subagents, swarms, release readiness, media playback, or project policy.
27
+ - Scheduling, queue daemons, distributed workers, or workflow DSLs.
28
+
29
+ ## Reading Model
30
+
31
+ ```text
32
+ recipe = saved JSON definition
33
+ run = one execution instance
34
+ lifecycle = state/logs/events/status/send/cancel/kill envelope
35
+ state dir = ordinary files for status/logs/events/result
36
+ coordinator = agent session that started the run
37
+ ```
38
+
39
+ Use async runs when work may outlive the current agent turn, should not block the agent, or should remain cancellable after launch.
40
+
41
+ Rule of thumb:
42
+
43
+ ```text
44
+ short call or pipeline → foreground template/tool
45
+ reusable saved graph → template recipe
46
+ long or background work → spawn run actor
47
+ ```
48
+
49
+ ## Starting Runs
50
+
51
+ A recipe with `async: true` starts detached when invoked through its registered tool:
52
+
53
+ ```json
54
+ {
55
+ "name": "music-player",
56
+ "async": true,
57
+ "template": "play-audio {source}"
58
+ }
59
+ ```
60
+
61
+ A caller can also start any recipe or inline template explicitly through `spawn`:
62
+
63
+ ```json
64
+ {
65
+ "as": "run:music",
66
+ "file": "music-player",
67
+ "values": {
68
+ "source": "~/Music"
69
+ }
70
+ }
71
+ ```
72
+
73
+ `spawn` always starts a detached run actor. Registered recipe tools follow the recipe's `async` flag.
74
+
75
+ Use `run_id` on async recipe tools or `as: "run:<id>"` on `spawn` when the caller wants a stable id for later inspection or control. Recipe `name` identifies the saved definition; the run id identifies one execution instance of that recipe. Async runs inject `{run_id}` and `{state_dir}` into template values so scripts can write run-local status files or control endpoints.
76
+
77
+ ## State Files
78
+
79
+ Use ordinary files under the extension temp directory so status tools stay simple and inspectable:
80
+
81
+ - `run.json`: pid, optional source metadata (`tool`, `recipe`, `recipe_file`), command-template config, cwd, coordinator owner id, values, named `artifacts`, mailbox metadata, created time, and state dir.
82
+ - `progress.json`: phase, active command count, completed count, failures, and updated time.
83
+ - `events.jsonl`: append-only lifecycle events.
84
+ - `outbox.jsonl`: optional script-authored JSONL events for coordinator inspection, notifications, or follow-up context.
85
+ - `stdout.log` and `stderr.log`: detached process output.
86
+ - `result.json`: final code, killed flag, output selector, and optional full-output path.
87
+
88
+ For pi-actors, actor run state defaults to:
89
+
90
+ ```text
91
+ ~/.pi/agent/tmp/pi-actors/runs/
92
+ ```
93
+
94
+ State files use this shape:
95
+
96
+ ```text
97
+ ~/.pi/agent/tmp/pi-actors/runs/<run>/run.json
98
+ ~/.pi/agent/tmp/pi-actors/runs/<run>/progress.json
99
+ ~/.pi/agent/tmp/pi-actors/runs/<run>/events.jsonl
100
+ ~/.pi/agent/tmp/pi-actors/runs/<run>/outbox.jsonl
101
+ ~/.pi/agent/tmp/pi-actors/runs/<run>/stdout.log
102
+ ~/.pi/agent/tmp/pi-actors/runs/<run>/stderr.log
103
+ ~/.pi/agent/tmp/pi-actors/runs/<run>/result.json
104
+ ```
105
+
106
+ Terminal status is `done` for result code 0 and `failed` for non-zero result code. A stopped run reports `cancelled` after graceful cancel or `killed` after force kill once the runner is no longer alive. If the runner process exits before writing a result and no stop event was recorded, status is `exited`.
107
+
108
+ ## Reactive Coordinator Loop
109
+
110
+ Async runs are designed for event-driven coordination, not polling loops. A good coordinator starts long-lived or multi-agent work, lets completion and decision-point events bubble through `outbox.jsonl`, and sends corrective commands only when the run asks for input or the operator changes direction.
111
+
112
+ The core loop is:
113
+
114
+ 1. Start an async recipe and keep the coordinator free:
115
+
116
+ ```json
117
+ { "recipe": "music-player.json", "as": "run:music" }
118
+ ```
119
+
120
+ 2. Let terminal events, `command.done`, and script-authored `followup` messages reach the launching coordinator automatically.
121
+
122
+ 3. Respond with explicit run-local messages when needed:
123
+
124
+ ```json
125
+ { "to": "run:music", "type": "player.next", "body": "next" }
126
+ ```
127
+
128
+ 4. Do not inspect just because time passed. Inspect `status`, `tail`, or `events` only when a follow-up asks for inspection, a real decision depends on it, or a suspected stuck run needs diagnosis.
129
+
130
+ Addressed `message` calls and coordinator follow-ups are the paired control plane: run-to-coordinator actor messages flow upward, while coordinator-to-run actor messages flow downward. Recipe scripts own the message vocabulary (`next`, `pause`, `approve`, `revise`, `continue`, and so on); pi-actors owns the safe run-local transport, ownership checks, and coordinator attention policy.
131
+
132
+ ## Tool Surface
133
+
134
+ The actor-level surface is:
135
+
136
+ - `spawn`: start a detached `run:<id>` actor from `file`, `recipe`, or inline `template`.
137
+ - `message`: send one typed envelope to `run:<id>`, `branch:<run>/<branch>`, `tool:<name>`, or `coordinator`.
138
+ - `inspect`: intentionally read `run:<id>` status, tail, events, artifacts, files, or mailbox metadata; read `session:<id>` or `session:all` run inventory with optional status filtering.
139
+
140
+ Low-level async actions map into the actor surface instead of forming a second public model:
141
+
142
+ - start → `spawn`
143
+ - send/control → `message`
144
+ - status/tail/events/list → `inspect`
145
+ - stop/kill → runtime control messages with synchronous results
146
+
147
+ Compact text is returned by default so async management does not flood agent context; use verbose inspection when the full state object is needed. List output intentionally shares one state root across music, subagents, timers, and other async work; source fields such as `tool` and `recipe` distinguish run purpose when the launcher recorded them. Registered tools are the preferred user-facing surface for reusable recipes.
148
+
149
+ ## Run-Local Messages
150
+
151
+ `message` is the explicit coordinator-to-actor command channel. Use it when a running recipe exposes a control vocabulary, a branch needs parent-mediated control, a registered tool should be invoked as `tool:<name>`, or the coordinator needs to redirect work without killing or restarting it.
152
+
153
+ On Unix-like hosts, async runs may expose a control FIFO at:
154
+
155
+ ```text
156
+ <state_dir>/control.fifo
157
+ ```
158
+
159
+ When present, a caller can send a typed actor message:
160
+
161
+ ```json
162
+ {
163
+ "to": "run:music",
164
+ "type": "player.next",
165
+ "body": "next"
166
+ }
167
+ ```
168
+
169
+ For `run:<id>`, `message` adapts the body to the FIFO command line. For `branch:<run>/<branch>`, it sends the full envelope through the parent run mailbox so the run can dispatch branch-local control. For `tool:<name>`, object bodies become the target tool parameters and primitive bodies are passed as `{ "input": body }`. The generic runtime records send events but does not interpret arbitrary run mailbox content. For example, a music player may accept `play`, `pause`, `next`, and `stop`, while a collaborative agent recipe may accept `continue`, `revise:<note>`, `approve`, or `abort`. Recipes may treat terminal control messages such as `stop` as synchronously handled so the later process exit does not generate a duplicate async follow-up.
170
+
171
+ Native Windows does not support this Unix FIFO contract. Use WSL/Linux/macOS for FIFO-controlled recipes, or let a Windows-specific recipe expose its own transport such as a Windows named pipe or localhost socket.
172
+
173
+ ## Coordinator Notifications
174
+
175
+ The launching coordinator should not busy-poll long-running async runs. The extension watches run state directories and delivers terminal `done`/`failed`/unhandled `killed`/`exited` transitions plus script-authored `notify`/`followup` actor messages back to the owning session. This gives the top-level async task a completion signal on the happy path while still letting recipe-local messages bubble up when scripts need finer-grained notifications. Terminal follow-ups include recipe-level named `artifacts` when declared. The generic runner also emits compact `command.done` actor messages for completed leaf commands; recipe authors declare that capability in `mailbox.emits` rather than configuring a separate delivery policy. Packaged multi-agent fanout recipes bubble these completion messages by default because async branch completion is base coordinator context rather than optional diagnostics. Branch-level `command.done` follow-ups omit artifact manifests because the top-level terminal follow-up carries them once. Intentional `runtime.cancel`, `runtime.kill`, and control messages such as `stop` stay out of follow-up context because the initiating message already returns synchronously. If a follow-up asks for direction, answer with `message` rather than starting a polling loop. Use explicit `inspect` only when a delivered follow-up requests inspection, a real decision depends on state, or a suspected stuck run needs diagnosis — never merely because a timeout elapsed.
176
+
177
+ Ambient status indicators may refresh while work is active, but coordinator attention is event-driven from state-file changes rather than a coordinator agent loop. This lets the coordinator continue other work after `spawn`; the run signals back through `events.jsonl`, `result.json`, and `outbox.jsonl`. The ambient triangle count represents active async work units: each running async run contributes at least one triangle, and a run with multiple active parallel command/subagent branches contributes the reported active branch count. If a coordinator starts one parent run with four active parallel branches, four triangles are shown; if the same coordinator starts five independent single-branch runs, five triangles are shown.
178
+
179
+ ## Run Actor Messages
180
+
181
+ A recipe or script may append coordinator-bound actor message records to:
182
+
183
+ ```text
184
+ <state_dir>/outbox.jsonl
185
+ ```
186
+
187
+ Shape:
188
+
189
+ ```json
190
+ {
191
+ "event": "player.track",
192
+ "summary": "Now playing: track.flac",
193
+ "level": "info",
194
+ "delivery": "log",
195
+ "ts": "2026-05-19T00:00:00.000Z",
196
+ "data": { "track": "/Music/track.flac", "index": 3, "count": 42 }
197
+ }
198
+ ```
199
+
200
+ `level` is `info`, `warning`, or `error`. `delivery` is `log`, `notify`, or `followup`:
201
+
202
+ - `log`: stored only; read explicitly with `inspect view=events`.
203
+ - `notify`: shown as a UI notification to the launching coordinator session.
204
+ - `followup`: notification plus compact follow-up context to the launching coordinator session.
205
+
206
+ Use `followup` for completion and decision-point messages that should reach the coordinator, not for every progress tick. Packaged multi-agent branch completion is a completion message and should bubble by default. Follow-up path lists use Markdown hierarchy: a section heading, `- Base: ...`, and `- Files: ...`, so repeated run-state prefixes do not flood agent context.
207
+
208
+ ## Cancellation And Ownership
209
+
210
+ An async run belongs to the current user, cwd, and launching agent session at start time. Send, cancellation, and force-kill target only the recorded runner pid when command line and cwd still match the recorded owner data. Stale pid reuse must fail closed.
211
+
212
+ On Unix-like systems, cancel and kill signal the runner process group when available, then fall back to the runner pid. The runner starts command-template children in that process group, so long-running descendants such as audio players stop with the run instead of becoming orphaned background processes. After the process exits, status reflects the operator action as `cancelled` or `killed` instead of a generic `exited`.
213
+
214
+ State is append-only where practical. Final result writes should be atomic. Recipe-local control endpoints and actor-message logs may live in the state dir. pi-actors core owns only the generic Unix FIFO write action and runtime attention policy; command and message vocabularies belong to the recipe/script.
215
+
216
+ ## Extension Temp Directory
217
+
218
+ Extension-owned temporary runtime files live under the pi agent directory:
219
+
220
+ ```text
221
+ ~/.pi/agent/tmp/<extension-name>/
222
+ ```
223
+
224
+ Rules:
225
+
226
+ - Use the pi agent temp tree, not system temp, for extension-owned state.
227
+ - Use system temp only for OS-level scratch files or explicit operator overrides.
228
+ - Keep each extension in its own subdirectory named after the local extension name.
229
+ - Prepare the extension temp directory on session start.
230
+ - Prune stale entries on session start.
231
+ - Default stale age is 24 hours unless the extension has a stronger reason.
232
+ - Cleanup must be fail-open: cleanup races should not prevent extension startup.
233
+ - State that must survive restarts belongs in the agent root, not in `tmp`.
234
+
235
+ ## Ambient Observability
236
+
237
+ Interactive sessions expose compact activity with minimal screen cost:
238
+
239
+ - Footer status is shown only while async runs launched by the current coordinator session are active.
240
+ - Each running async run contributes at least one `▷`; if the run reports multiple active command/sub-agent branches, those branches contribute additional triangles.
241
+ - One `▶` moves across the triangles as a small wave.
242
+ - With one active command, the triangle blinks between `▶` and `▷`.
243
+ - Triangles disappear as concrete commands exit.
244
+ - No prompt-area widget is shown by default.
245
+ - Terminal `done`/`failed`/unhandled `killed`/`exited` transitions trigger compact follow-up context only in the launching coordinator session; intentional `cancel`, `kill`, and `stop` actions stay out of agent context because the action already reports synchronously.
246
+ - Full logs remain in state files and are accessed through `inspect target=run:<id> view=tail` or the low-level tail adapter.
247
+
248
+ This keeps background work visible without blocking the agent, occupying the prompt area, or leaking async context into unrelated sessions.
249
+
250
+ ## Swarm Mapping
251
+
252
+ Swarm coordinator responsibilities split like this:
253
+
254
+ - Generic async runtime: start, pid tracking, status, tail, list, cancellation, stdout, stderr, logs.
255
+ - Swarm semantics: lock rules, quorum manifest shape, raw review retention, merger, post-merge review, conflict policy.
256
+ - Adapter config: model pool, default merger, default reviewer, prompt lens, tool allowlist, timeout.
257
+
258
+ pi-actors owns generic actor recipes and run primitives. Swarm should keep domain-specific quorum and implementation-team semantics unless they become reusable across multiple domains.
259
+
260
+ ## Collaborative Subagent Branch Adapter
261
+
262
+ A collaborative implementation swarm can use pi-actors as the actor runtime without making pi-actors own swarm semantics. The coordinator prepares scope files and chooses branch names. A trusted local runner owns one branch lifecycle. The async run owns fanout, state, logs, status, tail, cancel, and terminal result metadata.
263
+
264
+ Recommended flow:
265
+
266
+ ```text
267
+ coordinator writes scope files
268
+ → async recipe starts one branch runner per scope
269
+ → each runner clones or worktrees the repo
270
+ → each runner creates one feature branch
271
+ → each runner launches one subagent
272
+ → each runner verifies commit and push
273
+ → coordinator inspects status and tail
274
+ → integrator reviews and merges ready branches
275
+ ```
276
+
277
+ Scope files are preferable to large inline prompts because they are inspectable, reusable from logs, and safe for longer task groups. Keep them under an agent-owned run directory such as:
278
+
279
+ ```text
280
+ ~/.pi/agent/tmp/pi-actors/collab-runs/<run>/scopes/agent-01.md
281
+ ```
282
+
283
+ Example recipe:
284
+
285
+ ```json
286
+ {
287
+ "name": "collab-{run}",
288
+ "async": true,
289
+ "parallel": true,
290
+ "timeout": 1800000,
291
+ "template": [
292
+ {
293
+ "label": "agent-01",
294
+ "failure": "branch",
295
+ "retry": 2,
296
+ "recover": "git -C {work_dir_1} reset --hard HEAD",
297
+ "timeout": 1800000,
298
+ "template": "node {runner} --repo {repo} --base {base=dev} --branch {branch_1} --work-dir {work_dir_1} --scope {scope_1} --model {model}"
299
+ },
300
+ {
301
+ "label": "agent-02",
302
+ "failure": "branch",
303
+ "retry": 2,
304
+ "recover": "git -C {work_dir_2} reset --hard HEAD",
305
+ "timeout": 1800000,
306
+ "template": "node {runner} --repo {repo} --base {base=dev} --branch {branch_2} --work-dir {work_dir_2} --scope {scope_2} --model {model}"
307
+ }
308
+ ]
309
+ }
310
+ ```
311
+
312
+ The runner is intentionally outside pi-actors. It is a trusted local executable, like any other command-template target. Its minimum contract is clone or worktree, checkout branch, run subagent with bounded tools, verify expected branch, verify a commit exists, push branch, and emit a structured result. If one runner fails, `failure: "branch"` preserves sibling results as a degraded run.
313
+
314
+ Coordinator responsibilities stay outside the async runtime:
315
+
316
+ - Partition backlog tasks by stable task IDs and non-overlapping mutation zones.
317
+ - Write scope files before starting the run.
318
+ - Pass scope paths and branch names as values.
319
+ - Use `inspect target=run:<id> view=status` or `view=tail` after terminal events.
320
+ - Treat pushed branches as artifacts for review, not as automatic merges.
321
+ - Record failed scopes back into the backlog.
322
+
323
+ Do not encode backlog parsing, task assignment, pull-request policy, merge policy, or model selection into pi-actors core. Those are swarm, project, or operator policy.
324
+
325
+ ## Crystallization Questions
326
+
327
+ Before adding an async feature, ask:
328
+
329
+ - Is this generic for any long-running command template?
330
+ - Can it be represented as state files instead of a daemon?
331
+ - Does it preserve `template` plus boolean `parallel` as the only execution language?
332
+ - Does failure degrade into observable metadata instead of hidden retries?
333
+ - Can a registered tool own the policy instead of the runtime?
334
+
335
+ If implementing async primitives requires a scheduler, queue daemon, or custom DAG syntax, stop. The async extension should remain command-template execution with a small detached run envelope.