@llblab/pi-actors 0.30.1 → 0.31.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.
package/AGENTS.md CHANGED
@@ -129,6 +129,8 @@ Pi host
129
129
  - Preserve JSON envelope object shape across handoffs.
130
130
  - Keep locker state generic and thin; orchestration strategy belongs in the coordinator.
131
131
  - Graceful actor retirement is opt-in through recipe/run metadata and must not infer retirement for persistent services or backlog implementers.
132
+ - Script helpers that spawn long-lived child processes should keep those children inside the async run's owned process group unless they also provide an explicit termination bridge; `control.kill` must not leave detached playback/service descendants alive.
133
+ - True daemon recipes are allowed, but daemon ownership belongs to the recipe/script contract: persist a pid or service handle, verify ownership before signaling, expose status/stop semantics, and bridge `control.kill` to daemon cleanup instead of relying on the generic runner to discover detached services.
132
134
 
133
135
  ## Context And Planning Hygiene
134
136
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.31.0: Agent Adoption Ergonomics
6
+
7
+ - `[Adoption]` Added a compact actor-mode trigger rule to the injected prompt, actors skill, README, and async-run docs so models prefer `spawn → message → inspect` for long-lived, stateful, follow-up, artifact, service, fanout, and resumable work while keeping short foreground checks as ordinary tools.
8
+ - `[Skills]` Reframed the bundled actors skill as required practical guidance for non-trivial actor use or pi-actors changes, and made the injected prompt route agents to it before improvising actor workflows.
9
+ - `[Tools]` Strengthened `spawn`, `message`, and `inspect` descriptions with adoption cues that steer models away from ad hoc shell backgrounding, actor restarts, and polling loops.
10
+ - `[Spawn]` Added explicit next-action feedback to spawn results so newly created actors immediately suggest inspect/message chains instead of leaving agents to infer the next step.
11
+ - `[Docs]` Added a task-first “when to use actors” golden path to the README without adding public verbs or scheduler/service-manager concepts.
12
+
13
+ ## 0.30.2: Music Player Kill Hotfix
14
+
15
+ - `[Music Player]` Kept backend player processes inside the async run process group so `control.kill` can terminate an active music-player run without leaving detached `cvlc`/player children alive.
16
+ - `[Docs]` Updated durable project guidance, actor skill guidance, async-run ownership docs, and recipe-library music-player notes to preserve the run-owned process-tree invariant while still allowing true daemon recipes through explicit termination bridges.
17
+
5
18
  ## 0.30.1: Backlog Curation Hotfix
6
19
 
7
20
  - `[Backlog]` Added curation rules clarifying that completed work belongs only in the changelog, that cohesive ~1000-line domain files are acceptable, and that file splitting should follow real ownership boundaries rather than line count alone.
package/README.md CHANGED
@@ -29,7 +29,26 @@ inspect intentionally read state, logs, messages, contracts, or artifacts
29
29
 
30
30
  Everything else is an adapter until proven otherwise.
31
31
 
32
- Use `spawn` when work may outlive the current turn. Use `message` when the actor should be steered rather than restarted. Use `inspect` at decision points, after actor follow-ups, or during diagnosis. Do not build polling loops as the default coordination pattern.
32
+ Use `spawn` when work may outlive the current turn. Use `message` when the actor should be steered rather than restarted. Use `inspect` at decision points, after actor follow-ups, or during diagnosis. For non-trivial actor use, load the bundled `actors` skill before improvising. Do not build polling loops as the default coordination pattern.
33
+
34
+ ## When To Use Actors
35
+
36
+ Use actor-mode instead of ad hoc shell backgrounding when work is:
37
+
38
+ - Long-running or likely to outlive this agent turn.
39
+ - Stateful, resumable, or something you will need to inspect later.
40
+ - Expected to produce named artifacts or follow-up messages.
41
+ - A service, worker, media process, fanout, subagent, or pipeline.
42
+ - A repeatable local capability worth promoting into recipe memory.
43
+
44
+ Keep ordinary foreground tools for short checks such as `rg`, `ls`, quick tests, and one-shot transforms. The golden path is:
45
+
46
+ ```text
47
+ create actor -> spawn
48
+ steer actor -> message
49
+ read state/results -> inspect
50
+ repeatable pattern -> promote to recipe/tool memory
51
+ ```
33
52
 
34
53
  ## Install
35
54
 
@@ -6,7 +6,7 @@
6
6
  export declare const REGISTER_TOOL_DESCRIPTION: string;
7
7
  export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent command templates as agent-callable tools";
8
8
  export declare const REGISTER_TOOL_GUIDELINES: string[];
9
- export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, when, timeout, delay, retry, failure, recover, repeat, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Use spawn/message/inspect for actor-level start/send/observe; avoid runtime/FIFO/outbox vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.\n- 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.\n- Foreground tools/templates fit short work; async recipes/runs fit subagents, services, fanout, media, and long pipelines.\n- 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.\n- For deeper pi-actors guidance, inspect installed extension sources/docs/recipes; README and docs are not automatically in context.";
9
+ export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync: string leaf, array sequence, object node; flags include args/defaults, parallel, when, timeout, delay, retry, failure, recover, repeat, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.\n- 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.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.\n- 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.\n- 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.\n- 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.";
10
10
  export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
11
11
  readonly name: "Tool name in snake_case (e.g., 'transcribe')";
12
12
  readonly description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.";
@@ -21,12 +21,12 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
21
21
  - ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
22
22
  - Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
23
23
  - Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
24
- - Use spawn/message/inspect for actor-level start/send/observe; avoid runtime/FIFO/outbox vocabulary in public guidance.
24
+ - Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
25
+ - 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.
25
26
  - Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.
26
27
  - 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.
27
- - Foreground tools/templates fit short work; async recipes/runs fit subagents, services, fanout, media, and long pipelines.
28
28
  - 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.
29
- - For deeper pi-actors guidance, inspect installed extension sources/docs/recipes; README and docs are not automatically in context.`;
29
+ - 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.`;
30
30
  export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
31
31
  name: "Tool name in snake_case (e.g., 'transcribe')",
32
32
  description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.",
package/dist/lib/tools.js CHANGED
@@ -128,12 +128,23 @@ function asRecord(value) {
128
128
  function formatFailureCount(value) {
129
129
  return Array.isArray(value) ? value.length : undefined;
130
130
  }
131
+ function actorRunNextActions(run) {
132
+ const id = String(run ?? "").trim();
133
+ if (!id)
134
+ return [];
135
+ return [
136
+ `inspect target=run:${id} view=status`,
137
+ `inspect target=run:${id} view=messages`,
138
+ `message to=run:${id} type=<actor.action>`,
139
+ ];
140
+ }
131
141
  function compactAsyncRunStatus(value) {
132
142
  const status = asRecord(value);
133
143
  const progress = asRecord(status.progress);
134
144
  const result = asRecord(status.result);
145
+ const run = String(status.run ?? "<unknown>");
135
146
  const tokens = [
136
- `run=${String(status.run ?? "<unknown>")}`,
147
+ `run=${run}`,
137
148
  `status=${String(status.status ?? "unknown")}`,
138
149
  ];
139
150
  if (status.tool)
@@ -159,6 +170,9 @@ function compactAsyncRunStatus(value) {
159
170
  tokens.push("killed=true");
160
171
  if (status.candidate_recipe)
161
172
  tokens.push(`candidate_recipe=${String(status.candidate_recipe)}`);
173
+ const nextActions = actorRunNextActions(run);
174
+ if (nextActions.length > 0)
175
+ tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
162
176
  return `\n${tokens.join(" ")}`;
163
177
  }
164
178
  function compactRunMessages(messages) {
@@ -720,7 +734,7 @@ export function createSpawnToolDefinition() {
720
734
  return {
721
735
  name: "spawn",
722
736
  label: "Spawn",
723
- description: "Create an addressable actor from a recipe file or inline command template. Currently spawns run:<id> actors backed by async runs.",
737
+ description: "Create an addressable actor from a recipe file or inline command template. Use instead of ad hoc shell backgrounding for work that may outlive this turn, needs steering/follow-up/artifacts, runs as a service, fans out, or should be inspected later. Currently spawns run:<id> actors backed by async runs.",
724
738
  parameters: objectSchema({
725
739
  artifacts: looseObjectSchema("Optional named artifact paths for the spawned actor."),
726
740
  as: stringSchema("Optional actor address for the spawned run, e.g. run:<id>."),
@@ -770,9 +784,12 @@ export function createSpawnToolDefinition() {
770
784
  throw enhanceSpawnRecipeError(error, recipe);
771
785
  }
772
786
  const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
773
- const details = candidateRecipe
774
- ? { ...meta, candidate_recipe: candidateRecipe }
775
- : meta;
787
+ const nextActions = actorRunNextActions(meta.run);
788
+ const details = {
789
+ ...meta,
790
+ ...(candidateRecipe ? { candidate_recipe: candidateRecipe } : {}),
791
+ next_actions: nextActions,
792
+ };
776
793
  ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
777
794
  ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
778
795
  return {
@@ -818,7 +835,7 @@ export function createInspectToolDefinition(deps = {}) {
818
835
  return {
819
836
  name: "inspect",
820
837
  label: "Inspect",
821
- description: "Intentionally inspect an actor. Supports run:<id> views: status, tail, messages, artifacts, files, mailbox, communication; room:<run> status/messages/previews/roster/contacts; coordinator/session status; and tool:<name> status/schema.",
838
+ description: "Intentionally inspect an actor at decision points, after follow-ups, or during diagnosis instead of polling. Supports run:<id> views: status, tail, messages, artifacts, files, mailbox, communication; room:<run> status/messages/previews/roster/contacts; coordinator/session status; and tool:<name> status/schema.",
822
839
  parameters: objectSchema({
823
840
  lines: stringSchema("Line count for tail/messages views. Default 40."),
824
841
  status: stringSchema("Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited."),
@@ -1122,7 +1139,7 @@ export function createActorMessageToolDefinition(deps = {}) {
1122
1139
  return {
1123
1140
  name: "message",
1124
1141
  label: "Message",
1125
- description: "Send one typed addressed message. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, room:<run> timelines/rosters, tool:<name> calls, and coordinator/session-bound run messages.",
1142
+ description: "Send one typed addressed message to steer an existing actor instead of restarting it. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, room:<run> timelines/rosters, tool:<name> calls, and coordinator/session-bound run messages.",
1126
1143
  parameters: objectSchema({
1127
1144
  body: unionSchema([
1128
1145
  stringSchema("Message body. For run:<id>, this is the run-local command line."),
@@ -804,7 +804,6 @@ function playOne(ctx, player, volume, track, index, count) {
804
804
  const [command, args] = playerCommand(ctx, player, volume, track);
805
805
  writeStatus(ctx, "playing", index, count, track, player, "");
806
806
  const child = spawn(command, args, {
807
- detached: process.platform !== "win32",
808
807
  stdio: ["ignore", "inherit", "inherit"],
809
808
  });
810
809
  ctx.child = child;
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: actors
3
- description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
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.30.1
5
+ version: 0.31.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
9
9
 
10
- `pi-actors` turns trusted local capabilities into addressable actors. This skill is the compact operator/reference layer for the extension itself: tools, nouns, lifecycle, message protocol, recipes, and common edge cases. It is not a multi-agent strategy guide; use a swarm skill for decomposition, quorum design, reviewer lenses, and consensus methodology.
10
+ `pi-actors` turns trusted local capabilities into addressable actors. This skill is the required practical layer for any non-trivial pi-actors use or repo change: tools, nouns, lifecycle, message protocol, recipes, and common edge cases. It is not a multi-agent strategy guide; use a swarm skill for decomposition, quorum design, reviewer lenses, and consensus methodology.
11
11
 
12
12
  Maintain this skill as the extension's agent-facing manual. When implementation changes reveal new durable mechanics, invariants, warnings, or safer operating patterns, update this skill alongside code/docs so future agents learn the current actor model instead of rediscovering it.
13
13
 
@@ -48,6 +48,8 @@ Trusted local capability
48
48
 
49
49
  ## Three Verbs
50
50
 
51
+ Actor-mode trigger: if work may outlive this turn, needs steering/follow-up/artifacts, runs as a service, fans out, or should be resumed/inspected later, use `spawn → message → inspect` instead of ad hoc shell backgrounding. Keep short foreground checks as normal tools.
52
+
51
53
  ### `spawn` — create a run actor
52
54
 
53
55
  Use for long work, background services, subagents, fanout, pipelines, and reusable recipes.
@@ -87,6 +89,7 @@ Envelope fields:
87
89
  - Addresses: `run:<id>`, `branch:<run>/<branch>`, `room:<run>`, `tool:<name>`, `coordinator`, `session:<id>`.
88
90
  - Room posts require `from` from the same run (`run:<run>` or `branch:<run>/<branch>`).
89
91
  - Runtime termination message: `control.kill` is the only documented actor message that kills a run. `control.stop` and `control.cancel` are actor-local mailbox vocabulary only when a recipe declares and handles them. Terminal retention messages: `control.archive`, `control.prune`.
92
+ - Long-lived child processes should remain in the run-owned process group unless the recipe implements an explicit daemon termination bridge; do not leave unowned detached services behind `control.kill`.
90
93
 
91
94
  Check `inspect view=mailbox` before domain-specific messages.
92
95
 
@@ -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.30.1
5
+ version: 0.31.0
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -6,6 +6,8 @@ Async runs are detached executions of a template recipe or inline command templa
6
6
 
7
7
  **Scope:** run id, state path, runner pid, process-group cancellation, logs, status, tail, list, script-authored actor messages, run-local control messages, 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
8
 
9
+ Actor-mode trigger: choose an async run when work may outlive the current turn, needs later steering or inspection, produces artifacts/follow-ups, runs as a service, fans out, or should become repeatable recipe memory. Keep short foreground checks in ordinary tools/templates.
10
+
9
11
  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, control messages, actor messages, cancel, and kill belong to the async-run layer.
10
12
 
11
13
  ---
@@ -233,7 +235,7 @@ Use coordinator/session-bound messages for completion and decision points, not f
233
235
 
234
236
  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.
235
237
 
236
- On Unix-like systems, `control.kill` signals the runner process group when available, then falls back to the runner pid. On native Windows, `control.kill` uses Windows process-tree termination through the platform adapter. The runner starts command-template children in the owned process tree, so long-running descendants such as audio players should stop with the run instead of becoming orphaned background processes. After the process exits, status reflects the operator action as `killed` instead of a generic `exited`. Programmatic `cancelRun()` remains an internal lifecycle helper for retirement and tests, but it is not a documented actor-message action.
238
+ On Unix-like systems, `control.kill` signals the runner process group when available, then falls back to the runner pid. On native Windows, `control.kill` uses Windows process-tree termination through the platform adapter. The runner starts command-template children in the owned process tree, so long-running descendants such as audio players should stop with the run instead of becoming orphaned background processes. A recipe may manage a true detached daemon, but then daemon ownership is recipe-local: the script must persist and verify a pid or service handle, expose status/stop behavior, and bridge `control.kill` to daemon cleanup. The generic runner does not scan for or guess detached services. After the process exits, status reflects the operator action as `killed` instead of a generic `exited`. Programmatic `cancelRun()` remains an internal lifecycle helper for retirement and tests, but it is not a documented actor-message action.
237
239
 
238
240
  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 the generic run-local message adapter and runtime attention policy; command and message vocabularies belong to the recipe/script.
239
241
 
@@ -185,7 +185,7 @@ The wrapper also accepts control commands directly when a caller already has the
185
185
  scripts/music-player.mjs next ~/.pi/agent/tmp/pi-actors/runs/music
186
186
  ```
187
187
 
188
- Message body is queued in the recipe's run-local mailbox and reconciled by the player loop. The loop treats `wake.jsonl` and `fs.watch` as advisory signals, then verifies the durable inbox signature before taking the inbox lock so unchanged mailboxes are not reread on every tick. On Unix-like hosts, child players run in their own process group so pause/resume/next/stop controls can signal the playback subtree directly. The script writes `status.txt`, `player.json`, and track-change actor messages in the same state dir. Track-change messages stay diagnostic by default; interactive recipes should define a small command vocabulary for addressed messages, emit semantic actor messages for decision points, and let the coordinator react to messages rather than sleep-polling state.
188
+ Message body is queued in the recipe's run-local mailbox and reconciled by the player loop. The loop treats `wake.jsonl` and `fs.watch` as advisory signals, then verifies the durable inbox signature before taking the inbox lock so unchanged mailboxes are not reread on every tick. Backend players stay inside the async run process group so `control.kill` terminates active playback with the run instead of leaving detached player children alive; player-local pause/resume/next/stop controls still signal the current backend pid or process group when available. The script writes `status.txt`, `player.json`, and track-change actor messages in the same state dir. Track-change messages stay diagnostic by default; interactive recipes should define a small command vocabulary for addressed messages, emit semantic actor messages for decision points, and let the coordinator react to messages rather than sleep-polling state.
189
189
 
190
190
  Cross-platform smoke checklist:
191
191
 
package/lib/prompts.ts CHANGED
@@ -27,12 +27,12 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
27
27
  - ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
28
28
  - Recipes own template directly and may declare metadata/defaults/imports/mailbox/artifacts; files >1 MiB or import depth >32 fail closed.
29
29
  - Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
30
- - Use spawn/message/inspect for actor-level start/send/observe; avoid runtime/FIFO/outbox vocabulary in public guidance.
30
+ - Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
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.
31
32
  - Run state lives under ~/.pi/agent/tmp/pi-actors/runs; inspect status/tail/messages/mailbox/files/artifacts intentionally and avoid busy-polling.
32
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.
33
- - Foreground tools/templates fit short work; async recipes/runs fit subagents, services, fanout, media, and long pipelines.
34
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.
35
- - For deeper pi-actors guidance, inspect installed extension sources/docs/recipes; README and docs are not automatically in context.`;
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 = {
38
38
  name: "Tool name in snake_case (e.g., 'transcribe')",
package/lib/tools.ts CHANGED
@@ -187,12 +187,23 @@ function formatFailureCount(value: unknown): number | undefined {
187
187
  return Array.isArray(value) ? value.length : undefined;
188
188
  }
189
189
 
190
+ function actorRunNextActions(run: unknown): string[] {
191
+ const id = String(run ?? "").trim();
192
+ if (!id) return [];
193
+ return [
194
+ `inspect target=run:${id} view=status`,
195
+ `inspect target=run:${id} view=messages`,
196
+ `message to=run:${id} type=<actor.action>`,
197
+ ];
198
+ }
199
+
190
200
  function compactAsyncRunStatus(value: unknown): string {
191
201
  const status = asRecord(value);
192
202
  const progress = asRecord(status.progress);
193
203
  const result = asRecord(status.result);
204
+ const run = String(status.run ?? "<unknown>");
194
205
  const tokens = [
195
- `run=${String(status.run ?? "<unknown>")}`,
206
+ `run=${run}`,
196
207
  `status=${String(status.status ?? "unknown")}`,
197
208
  ];
198
209
  if (status.tool) tokens.push(`tool=${String(status.tool)}`);
@@ -213,6 +224,9 @@ function compactAsyncRunStatus(value: unknown): string {
213
224
  if (result.killed === true) tokens.push("killed=true");
214
225
  if (status.candidate_recipe)
215
226
  tokens.push(`candidate_recipe=${String(status.candidate_recipe)}`);
227
+ const nextActions = actorRunNextActions(run);
228
+ if (nextActions.length > 0)
229
+ tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
216
230
  return `\n${tokens.join(" ")}`;
217
231
  }
218
232
 
@@ -942,7 +956,7 @@ export function createSpawnToolDefinition<
942
956
  name: "spawn",
943
957
  label: "Spawn",
944
958
  description:
945
- "Create an addressable actor from a recipe file or inline command template. Currently spawns run:<id> actors backed by async runs.",
959
+ "Create an addressable actor from a recipe file or inline command template. Use instead of ad hoc shell backgrounding for work that may outlive this turn, needs steering/follow-up/artifacts, runs as a service, fans out, or should be inspected later. Currently spawns run:<id> actors backed by async runs.",
946
960
  parameters: objectSchema(
947
961
  {
948
962
  artifacts: looseObjectSchema(
@@ -1023,9 +1037,12 @@ export function createSpawnToolDefinition<
1023
1037
  throw enhanceSpawnRecipeError(error, recipe);
1024
1038
  }
1025
1039
  const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
1026
- const details = candidateRecipe
1027
- ? { ...meta, candidate_recipe: candidateRecipe }
1028
- : meta;
1040
+ const nextActions = actorRunNextActions(meta.run);
1041
+ const details = {
1042
+ ...meta,
1043
+ ...(candidateRecipe ? { candidate_recipe: candidateRecipe } : {}),
1044
+ next_actions: nextActions,
1045
+ };
1029
1046
  ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
1030
1047
  ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
1031
1048
  return {
@@ -1103,7 +1120,7 @@ export function createInspectToolDefinition<TContext = unknown>(
1103
1120
  name: "inspect",
1104
1121
  label: "Inspect",
1105
1122
  description:
1106
- "Intentionally inspect an actor. Supports run:<id> views: status, tail, messages, artifacts, files, mailbox, communication; room:<run> status/messages/previews/roster/contacts; coordinator/session status; and tool:<name> status/schema.",
1123
+ "Intentionally inspect an actor at decision points, after follow-ups, or during diagnosis instead of polling. Supports run:<id> views: status, tail, messages, artifacts, files, mailbox, communication; room:<run> status/messages/previews/roster/contacts; coordinator/session status; and tool:<name> status/schema.",
1107
1124
  parameters: objectSchema(
1108
1125
  {
1109
1126
  lines: stringSchema("Line count for tail/messages views. Default 40."),
@@ -1557,7 +1574,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
1557
1574
  name: "message",
1558
1575
  label: "Message",
1559
1576
  description:
1560
- "Send one typed addressed message. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, room:<run> timelines/rosters, tool:<name> calls, and coordinator/session-bound run messages.",
1577
+ "Send one typed addressed message to steer an existing actor instead of restarting it. Routes to run:<id> mailboxes, branch:<run>/<branch> mailboxes, room:<run> timelines/rosters, tool:<name> calls, and coordinator/session-bound run messages.",
1561
1578
  parameters: objectSchema(
1562
1579
  {
1563
1580
  body: unionSchema([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.30.1",
3
+ "version": "0.31.0",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -804,7 +804,6 @@ function playOne(ctx, player, volume, track, index, count) {
804
804
  const [command, args] = playerCommand(ctx, player, volume, track);
805
805
  writeStatus(ctx, "playing", index, count, track, player, "");
806
806
  const child = spawn(command, args, {
807
- detached: process.platform !== "win32",
808
807
  stdio: ["ignore", "inherit", "inherit"],
809
808
  });
810
809
  ctx.child = child;
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: actors
3
- description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
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.30.1
5
+ version: 0.31.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
9
9
 
10
- `pi-actors` turns trusted local capabilities into addressable actors. This skill is the compact operator/reference layer for the extension itself: tools, nouns, lifecycle, message protocol, recipes, and common edge cases. It is not a multi-agent strategy guide; use a swarm skill for decomposition, quorum design, reviewer lenses, and consensus methodology.
10
+ `pi-actors` turns trusted local capabilities into addressable actors. This skill is the required practical layer for any non-trivial pi-actors use or repo change: tools, nouns, lifecycle, message protocol, recipes, and common edge cases. It is not a multi-agent strategy guide; use a swarm skill for decomposition, quorum design, reviewer lenses, and consensus methodology.
11
11
 
12
12
  Maintain this skill as the extension's agent-facing manual. When implementation changes reveal new durable mechanics, invariants, warnings, or safer operating patterns, update this skill alongside code/docs so future agents learn the current actor model instead of rediscovering it.
13
13
 
@@ -48,6 +48,8 @@ Trusted local capability
48
48
 
49
49
  ## Three Verbs
50
50
 
51
+ Actor-mode trigger: if work may outlive this turn, needs steering/follow-up/artifacts, runs as a service, fans out, or should be resumed/inspected later, use `spawn → message → inspect` instead of ad hoc shell backgrounding. Keep short foreground checks as normal tools.
52
+
51
53
  ### `spawn` — create a run actor
52
54
 
53
55
  Use for long work, background services, subagents, fanout, pipelines, and reusable recipes.
@@ -87,6 +89,7 @@ Envelope fields:
87
89
  - Addresses: `run:<id>`, `branch:<run>/<branch>`, `room:<run>`, `tool:<name>`, `coordinator`, `session:<id>`.
88
90
  - Room posts require `from` from the same run (`run:<run>` or `branch:<run>/<branch>`).
89
91
  - Runtime termination message: `control.kill` is the only documented actor message that kills a run. `control.stop` and `control.cancel` are actor-local mailbox vocabulary only when a recipe declares and handles them. Terminal retention messages: `control.archive`, `control.prune`.
92
+ - Long-lived child processes should remain in the run-owned process group unless the recipe implements an explicit daemon termination bridge; do not leave unowned detached services behind `control.kill`.
90
93
 
91
94
  Check `inspect view=mailbox` before domain-specific messages.
92
95
 
@@ -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.30.1
5
+ version: 0.31.0
6
6
  ---
7
7
 
8
8
  # Swarm