@llblab/pi-actors 0.36.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.36.0
5
+ version: 0.37.1
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -194,13 +194,14 @@ Rules:
194
194
  2. `async: true` makes spawned work a detached actor run.
195
195
  3. Public knobs belong in `args`/`defaults`; hidden launch mechanics stay inside `template`.
196
196
  4. Use `imports` to compose recipes; imported recipes are definitions, not nested async runs.
197
- 5. When exposing an already-authored recipe as a user tool, make a small wrapper recipe in `~/.pi/agent/recipes` that imports the source recipe and uses a `{ "name": "alias" }` node. Do not copy the ready recipe's script command, defaults, mailbox, or artifacts into a second template.
198
- 6. Declare `mailbox` for actors that accept or emit meaningful messages.
199
- 7. Declare `artifacts` for durable outputs the coordinator should inspect.
200
- 8. File-backed recipe identity comes from the filename basename; legacy top-level `name` fields are ignored by loaders.
201
- 9. File-backed async recipes pass child `pi -p` actors a bounded JSONL recipe context bundle by default: raw entry/import recipe records, derived `name`, import path/alias, and `"you_are_here": true` on the launching recipe node. Set `"actor_context": false` or `"off"` to suppress it for minimal prompts.
202
- 10. Keep packaged recipes generic: no machine-local paths, no private companion identities, no project-specific defaults unless the recipe is explicitly project-specific.
203
- 11. Do not ship concrete model-version defaults in packaged recipes; expose `model`, `models`, and stage-specific model args so the caller must choose current policy at launch.
197
+ 5. Direct recipe delegation is the thin-wrapper case: when a `template` value is just a ready recipe name/path, the intended behavior is to delegate to that recipe rather than execute the recipe file as a program. Use this for simple handoffs and wrapper tools; use `imports` + `{ "name": "alias" }` when you need rich composition, multiple nodes, or import-specific values/defaults.
198
+ 6. When exposing an already-authored recipe as a user tool before direct delegation is available or when composition is needed, make a small wrapper recipe in `~/.pi/agent/recipes` that imports the source recipe and uses a `{ "name": "alias" }` node. Do not copy the ready recipe's script command, defaults, mailbox, or artifacts into a second template.
199
+ 7. Declare `mailbox` for actors that accept or emit meaningful messages.
200
+ 8. Declare `artifacts` for durable outputs the coordinator should inspect.
201
+ 9. File-backed recipe identity comes from the filename basename; legacy top-level `name` fields are ignored by loaders.
202
+ 10. File-backed async recipes pass child `pi -p` actors a bounded JSONL recipe context bundle by default: raw entry/import recipe records, derived `name`, import path/alias, and `"you_are_here": true` on the launching recipe node. Set `"actor_context": false` or `"off"` to suppress it for minimal prompts.
203
+ 11. Keep packaged recipes generic: no machine-local paths, no private companion identities, no project-specific defaults unless the recipe is explicitly project-specific.
204
+ 12. Do not ship concrete model-version defaults in packaged recipes; expose `model`, `models`, and stage-specific model args so the caller must choose current policy at launch.
204
205
 
205
206
  Priority for same-id recipes:
206
207
 
@@ -209,7 +210,7 @@ Priority for same-id recipes:
209
210
  3. Explicit ad hoc user recipe file outside `~/.pi/agent/recipes`.
210
211
  4. User recipe in `~/.pi/agent/recipes/*.json` or `*.md`: highest-priority operator tool surface.
211
212
 
212
- Only matching filename ids compete. Higher priority shadows lower priority; within one priority layer, same-id JSON shadows Markdown. An invalid or `disabled: true` higher-priority recipe blocks fallback so the agent does not silently run standard-library behavior when a user override is broken or intentionally disabled.
213
+ Only matching filename ids compete. Higher priority shadows lower priority; within one priority layer, same-id JSON shadows Markdown. Same-id overrides are normal composition/delegation behavior, not startup-warning material. An invalid or `disabled: true` higher-priority recipe blocks fallback so the agent does not silently run standard-library behavior when a user override is broken or intentionally disabled.
213
214
 
214
215
  Muscle-memory lens: pi-actors has two durable executable-memory layers.
215
216
 
@@ -230,7 +231,21 @@ Cleanup rule: periodically inspect `~/.pi/agent/recipes` as the live muscle-memo
230
231
 
231
232
  Use it when a command/template/recipe should become durable agent muscle memory. Prefer typed args or placeholder-derived args; use `update=true` for replacement and `template=null` or `template=""` for deletion. `register_tool` should create/update/delete simple recipe files in the user recipe root; direct recipe-file editing is the right path when the wrapper needs `imports` or other top-level recipe metadata not exposed by the interactive mutation API.
232
233
 
233
- Ready-recipe registration pattern:
234
+ Ready-recipe registration patterns:
235
+
236
+ Thin delegation target shape:
237
+
238
+ ```json
239
+ {
240
+ "description": "Run a ready recipe through a local tool name.",
241
+ "args": ["source:path", "volume:int=70"],
242
+ "template": "/path/to/ready-recipe.json"
243
+ }
244
+ ```
245
+
246
+ Delegation is for one-to-one handoff: expose or call a maintained recipe directly, preserving that recipe as the source of truth. If the runtime does not yet support direct recipe references in `template`, or if you need composition, use the import-node wrapper below.
247
+
248
+ Composition/import wrapper:
234
249
 
235
250
  ```json
236
251
  {
@@ -243,16 +258,16 @@ Ready-recipe registration pattern:
243
258
  }
244
259
  ```
245
260
 
246
- Use this pattern whenever a reusable recipe already exists: packaged pi-actors components, project-local recipes, ad hoc reviewed recipe files, and especially skill-owned recipes that wrap skill scripts. The wrapper owns only the public tool name, description, optional narrowed args/defaults, and local usage metadata. The imported recipe remains the source of truth for the script path, default values, mailbox contract, artifacts, and future fixes.
261
+ Use delegation or this import pattern whenever a reusable recipe already exists: packaged pi-actors components, project-local recipes, ad hoc reviewed recipe files, and especially skill-owned recipes that wrap skill scripts. The wrapper owns only the public tool name, description, optional narrowed args/defaults, and local usage metadata. The delegated/imported recipe remains the source of truth for the script path, default values, mailbox contract, artifacts, and future fixes.
247
262
 
248
263
  Tool-registration lenses are open-ended prompts for deciding what deserves durable tool status:
249
264
 
250
265
  1. **Reliability lens**: register wrappers for operations where agents commonly omit checks, run steps out of order, pass ambiguous inputs, or recover poorly from partial failure.
251
266
  2. **Safety lens**: prefer read-only diagnostics, dry-runs, preflights, confirmations, or bounded adapters around high-impact operations before registering direct action tools.
252
267
  3. **Context-affordance lens**: register tools whose mere presence in the injected capability list should steer agents toward the right operational habit.
253
- 4. **Existing-recipe lens**: scan already-authored recipes before inventing a new tool. Packaged recipes, ad hoc project recipes, and recipes co-located under skill directories are the first candidates to import from a user-root wrapper when they match a recurring local workflow.
254
- 5. **Skill-recipe lens**: when a skill ships a recipe for its script, local tools must import that recipe instead of calling the skill script directly. This preserves the skill's maintained interface and keeps future script/default changes centralized.
255
- 6. **Composition lens**: register small semantic entrypoints over reusable recipe components instead of baking one large scenario-specific shell command into a tool.
268
+ 4. **Existing-recipe lens**: scan already-authored recipes before inventing a new tool. Packaged recipes, ad hoc project recipes, and recipes co-located under skill directories are the first candidates to delegate to or import from a user-root wrapper when they match a recurring local workflow.
269
+ 5. **Skill-recipe lens**: when a skill ships a recipe for its script, local tools must delegate to or import that recipe instead of calling the skill script directly. This preserves the skill's maintained interface and keeps future script/default changes centralized.
270
+ 6. **Composition lens**: register small semantic entrypoints over reusable recipe components instead of baking one large scenario-specific shell command into a tool; prefer direct delegation for one recipe, imports for composed graphs.
256
271
  7. **Portability lens**: keep recipe files transportable; make tool exposure a consequence of placement in `~/.pi/agent/recipes`, not recipe-owned markers or machine-local assumptions.
257
272
 
258
273
  Default bias: register diagnostic/preflight tools before action tools, and promote existing recipes before writing new orchestration. A good persistent tool shrinks the chance of a subtle operational mistake, not just the number of keystrokes.
@@ -260,7 +275,7 @@ Default bias: register diagnostic/preflight tools before action tools, and promo
260
275
  Tool templates may be:
261
276
 
262
277
  - A foreground command template.
263
- - A file-backed recipe name/path.
278
+ - A file-backed recipe name/path for thin delegation.
264
279
  - A complete recipe body, optionally `async: true`.
265
280
 
266
281
  The user recipe root is the default tool set by location. It accepts canonical JSON recipes and literate Markdown recipes with frontmatter plus fenced `template`/`json recipe` blocks; same-id JSON shadows Markdown in the same priority layer. Packaged recipes are lower-priority standard-library components and are not tools unless copied or registered into the agent recipe root. Ideal runtime behavior is reactive: create/edit/delete recipe files, validate them, then connect valid tools or surface diagnostics without requiring agents to hand-maintain a separate registry.
@@ -269,6 +284,8 @@ The user recipe root is the default tool set by location. It accepts canonical J
269
284
 
270
285
  Use packaged recipes by name with `spawn file=<name>` for async actors, or register/call them as tools when repeated use deserves a stable shortcut.
271
286
 
287
+ Packaged review recipes are directly spawnable. Use `spawn file="pipeline-review-readiness" values={...}` for readiness review or `spawn file="subagent-review" values={...}` for one reviewer; pass model/thinking/tool policy through values, then inspect the run. Do not recreate their script commands, call packaged scripts directly, or create wrapper recipes just to launch the maintained recipe.
288
+
272
289
  - [`pipeline-room-swarm`](../../recipes/pipeline-room-swarm.json): room-visible swarm coordination with roles, rounds, optional locker, artifact synthesis, and `subagent_ttl_ms` for hard participant budgets.
273
290
  - [`pipeline-repo-health`](../../recipes/pipeline-repo-health.json): git/doc/validation evidence → normalized repository health report.
274
291
  - [`pipeline-release-readiness`](../../recipes/pipeline-release-readiness.json): changelog/package/skill/validation evidence → release review → artifact report.
@@ -2,7 +2,7 @@
2
2
  name: swarm
3
3
  description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
4
4
  metadata:
5
- version: 0.36.0
5
+ version: 0.37.1
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -48,6 +48,8 @@ Core subagent recipes:
48
48
 
49
49
  Most atoms expose policy knobs such as `model`, `thinking`, `tools`, `output_format`, `evidence_policy`, `risk_policy`, source policy, continuity policy, handoff format, or model pools. Packaged recipes intentionally do not ship concrete model-version defaults: callers must pass current model policy at launch, which keeps reusable recipe components from aging around old provider aliases. The generic prompt launchers, including `subagent-tools` and `subagents-prompts`, expose the same core model/thinking/tool/output knobs so callers do not need separate recipe families for policy tuning. Interactive async atoms also declare mailbox metadata for their basic control, completion, and domain-result message surface. Higher-level recipes pass these knobs through instead of hard-coding local policy.
50
50
 
51
+ For one-off packaged subagent reviews, launch the recipe directly with `spawn file="subagent-review" values={...}` or `spawn file="pipeline-review-readiness" values={...}`. Do not copy the underlying `pi -p` command or wrap the recipe unless you are creating a durable operator tool with a narrower interface.
52
+
51
53
  For build-oriented swarms, prefer a consensus-first shape over parallel writers: proposer roles coordinate in a room with message/inspect tools, a named implementer owns the first artifact write, a QA reviewer inspects the result, and a finalizer applies review-grounded fixes before `run.done`. This pattern keeps creative/lens diversity while preserving one coherent artifact and gives recipes concrete artifact assertions instead of treating room discussion as success.
52
54
 
53
55
  Register one atom:
@@ -292,9 +292,26 @@ An import binding may be either a string recipe path/name or an object with:
292
292
 
293
293
  A template node of `{ "name": "alias" }` is replaced with the imported recipe's command-template graph. Imported recipe defaults are merged with import `defaults`, import `values`, node `defaults`, and node `values`; later layers win. This lets a parent recipe embed a reusable recipe in a sequence or `parallel: true` branch without inventing a workflow language.
294
294
 
295
- Use imports as the default adapter for exposing ready recipes as local tools. A user-root recipe such as `~/.pi/agent/recipes/repo_context_check.json` should import the maintained source recipe and call `{ "name": "alias" }`, not duplicate the source recipe's script command. Skill scripts are the strongest version of this rule: when a skill provides `recipes/<name>.json` for its `scripts/*` entrypoint, local tools must import the skill recipe via `{agent}/skills/<skill>/recipes/<name>.json` instead of invoking the script path directly.
295
+ Use imports as the default adapter for composed ready recipes. A user-root recipe such as `~/.pi/agent/recipes/repo_context_check.json` can import the maintained source recipe and call `{ "name": "alias" }` instead of duplicating the source recipe's script command. Skill scripts are the strongest version of this rule: when a skill provides `recipes/<name>.json` for its `scripts/*` entrypoint, local tools should delegate to or import the skill recipe instead of invoking the script path directly.
296
296
 
297
- Async composition stays explicit: importing a recipe reuses its command-template-shaped definition. It does not start a nested async run. Put `async: true` on the parent recipe when the combined imported graph should run detached as one run with one state dir. Ephemeral coordinator recipes may declare `retire_when: "children_terminal"` as an opt-in lifecycle hint for future graceful retirement handling; persistent services and implementer loops should omit it. For agent-callable fanout, prefer public inputs such as `prompts:array` plus `repeat: "{prompts.length}"`, then select each branch value with `{prompts[index]}` instead of baking concrete prompts or file names into the reusable recipe.
297
+ ## Direct Recipe Delegation
298
+
299
+ A `template` string that resolves to a recipe name or recipe file path delegates to that recipe instead of executing the recipe file as a binary:
300
+
301
+ ```json
302
+ {
303
+ "description": "Play music through the packaged player recipe.",
304
+ "async": true,
305
+ "defaults": { "source": "~/Music", "volume": "70" },
306
+ "template": "music-player"
307
+ }
308
+ ```
309
+
310
+ Delegation is a one-to-one handoff for thin wrappers and simple reuse. It uses the same bare-name priority as imports: user-root recipes under `~/.pi/agent/recipes`, then the importing recipe's directory, then packaged standard-library recipes. A higher-priority invalid or disabled recipe still blocks lower-priority fallback so operator overrides fail closed.
311
+
312
+ Delegated recipes remain the source of truth for their command template, async setting, args/defaults, mailbox, artifacts, and future fixes. Wrapper recipe fields may narrow args/defaults or override lifecycle metadata. Use imports plus `{ "name": "alias" }` when you need rich composition, multiple recipe nodes, import-specific values/defaults, or a pipeline where the reusable recipe is only one step.
313
+
314
+ Async composition stays explicit: importing or delegating to a recipe reuses its command-template-shaped definition. It does not start a nested async run. Put `async: true` on the parent recipe when the combined imported graph should run detached as one run with one state dir; thin delegation may inherit `async: true` from the delegated recipe. Ephemeral coordinator recipes may declare `retire_when: "children_terminal"` as an opt-in lifecycle hint for future graceful retirement handling; persistent services and implementer loops should omit it. For agent-callable fanout, prefer public inputs such as `prompts:array` plus `repeat: "{prompts.length}"`, then select each branch value with `{prompts[index]}` instead of baking concrete prompts or file names into the reusable recipe.
298
315
 
299
316
  ```json
300
317
  {
package/index.ts CHANGED
@@ -106,7 +106,6 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
106
106
  configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
107
107
  exec: CommandTemplates.execCommandTemplate,
108
108
  getActiveTools: () => pi.getActiveTools(),
109
- getAllTools: () => pi.getAllTools(),
110
109
  registerTool: (definition) => {
111
110
  actorToolDefinitions.set(definition.name, definition);
112
111
  pi.registerTool(definition);
package/lib/async-runs.ts CHANGED
@@ -67,11 +67,7 @@ import {
67
67
  deliverRunMessage,
68
68
  type SendRunMessageOptions,
69
69
  } from "./runs-messages.ts";
70
- import {
71
- buildRunStatus,
72
- tailFile,
73
- tailLines,
74
- } from "./runs-status.ts";
70
+ import { buildRunStatus, tailFile, tailLines } from "./runs-status.ts";
75
71
  import { readJsonFileResilient } from "./state-readers.ts";
76
72
 
77
73
  const RUNNER_IDENTITY_GRACE_MS = 5000;
@@ -213,8 +209,9 @@ function assertNoActiveRunState(stateDir: string): void {
213
209
 
214
210
  function resolveRecipeFile(file: string): string {
215
211
  return (
216
- RecipesReferences.getRecipePath(file, DEFAULT_RECIPE_ROOT) ??
217
- RecipesReferences.resolveRecipePath(file, DEFAULT_RECIPE_ROOT)
212
+ RecipesReferences.resolveRecipeReferencePath(file, Paths.getRecipeRoot()) ??
213
+ RecipesReferences.getRecipePath(file, Paths.getRecipeRoot()) ??
214
+ RecipesReferences.resolveRecipePath(file, Paths.getRecipeRoot())
218
215
  );
219
216
  }
220
217
 
@@ -134,18 +134,33 @@ export function resolveInheritedDefaultReferences(
134
134
  inheritedDefaults: Record<string, unknown> | undefined,
135
135
  runtimeValues: Record<string, unknown> = {},
136
136
  ): Record<string, unknown> | undefined {
137
- if (!ownDefaults || !inheritedDefaults) return ownDefaults;
137
+ if (!ownDefaults) return ownDefaults;
138
138
  const resolved = { ...ownDefaults };
139
+ const values = { ...(inheritedDefaults ?? {}), ...runtimeValues };
139
140
  for (const [key, value] of Object.entries(ownDefaults)) {
140
141
  if (typeof value !== "string") continue;
141
142
  const exact = /^\{([A-Za-z_][A-Za-z0-9_-]*)\}$/.exec(value);
142
- if (
143
- !exact ||
144
- Object.hasOwn(runtimeValues, exact[1]) ||
145
- !Object.hasOwn(inheritedDefaults, exact[1])
146
- )
143
+ if (exact && Object.hasOwn(values, exact[1])) {
144
+ resolved[key] = values[exact[1]];
147
145
  continue;
148
- resolved[key] = inheritedDefaults[exact[1]];
146
+ }
147
+ const indexed = value.match(
148
+ /^\{([A-Za-z_][A-Za-z0-9_-]*)\[([A-Za-z_][A-Za-z0-9_-]*|\d+)\]\}$/,
149
+ );
150
+ if (!indexed) continue;
151
+ const source = values[indexed[1]];
152
+ const indexValue = /^\d+$/.test(indexed[2])
153
+ ? indexed[2]
154
+ : values[indexed[2]];
155
+ const index = Number(indexValue);
156
+ if (
157
+ Array.isArray(source) &&
158
+ Number.isInteger(index) &&
159
+ index >= 0 &&
160
+ index < source.length
161
+ ) {
162
+ resolved[key] = source[index] ?? "";
163
+ }
149
164
  }
150
165
  return resolved;
151
166
  }
package/lib/prompts.ts CHANGED
@@ -31,7 +31,7 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
31
31
  - Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid runtime/FIFO/outbox vocabulary in public guidance.
32
32
  - Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.
33
33
  - Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; packaged/ad hoc recipes are lower-priority components; offer to save successful recurring patterns only after confirmation.
34
- - Long fanout = parent async recipe wrapping template(parallel:true) and imports; packaged fanout recipes bubble branch completion messages; grow recurring multi-agent workflows as packaged recipes/pipelines, not ad hoc external scripts.
34
+ - Prefer maintained packaged recipes/pipelines with spawn file=<recipe> before ad hoc scripts/wrappers; long fanout = parent async recipe wrapping template(parallel:true) and imports.
35
35
  - For any non-trivial actor use or pi-actors change, read the bundled actors skill first; for deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.`;
36
36
 
37
37
  export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
@@ -22,14 +22,68 @@ function isPiCommand(command: string): boolean {
22
22
  return commandName(command) === "pi";
23
23
  }
24
24
 
25
+ const PI_PRINT_FLAGS = new Set(["-p", "--print"]);
26
+ const PI_VALUE_OPTIONS = new Set([
27
+ "--api-key",
28
+ "--append-system-prompt",
29
+ "--exclude-tools",
30
+ "--extension",
31
+ "--fork",
32
+ "--mode",
33
+ "--model",
34
+ "--models",
35
+ "--name",
36
+ "--prompt-template",
37
+ "--provider",
38
+ "--session",
39
+ "--session-dir",
40
+ "--skill",
41
+ "--system-prompt",
42
+ "--theme",
43
+ "--thinking",
44
+ "--tools",
45
+ ]);
46
+ const PI_SHORT_VALUE_OPTIONS = new Set(["-e", "-n", "-t", "-xt"]);
47
+
48
+ function isPiPrintFlag(arg: string): boolean {
49
+ return PI_PRINT_FLAGS.has(arg);
50
+ }
51
+
52
+ function isPiOption(arg: string): boolean {
53
+ return arg.startsWith("-") && arg !== "-";
54
+ }
55
+
56
+ function piOptionConsumesNextArg(arg: string): boolean {
57
+ if (arg.includes("=")) return false;
58
+ return PI_VALUE_OPTIONS.has(arg) || PI_SHORT_VALUE_OPTIONS.has(arg);
59
+ }
60
+
61
+ function isPiFileArgument(arg: string): boolean {
62
+ return arg.startsWith("@") && arg.length > 1;
63
+ }
64
+
25
65
  function findPrintPromptIndex(args: string[]): number | undefined {
66
+ let printMode = false;
67
+ let positionalOnly = false;
68
+ let promptIndex: number | undefined;
26
69
  for (let index = 0; index < args.length; index += 1) {
27
70
  const arg = args[index];
28
- if ((arg === "-p" || arg === "--print") && index + 1 < args.length) {
29
- return index + 1;
71
+ if (!positionalOnly && arg === "--") {
72
+ positionalOnly = true;
73
+ continue;
74
+ }
75
+ if (!positionalOnly && isPiPrintFlag(arg)) {
76
+ printMode = true;
77
+ continue;
78
+ }
79
+ if (!positionalOnly && isPiOption(arg)) {
80
+ if (piOptionConsumesNextArg(arg)) index += 1;
81
+ continue;
30
82
  }
83
+ if (!printMode || isPiFileArgument(arg)) continue;
84
+ promptIndex = index;
31
85
  }
32
- return undefined;
86
+ return promptIndex;
33
87
  }
34
88
 
35
89
  function matchesActorContext(