@llblab/pi-actors 0.14.3 → 0.15.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 (67) hide show
  1. package/AGENTS.md +5 -1
  2. package/BACKLOG.md +18 -32
  3. package/CHANGELOG.md +20 -0
  4. package/README.md +24 -20
  5. package/docs/actor-messages.md +1 -1
  6. package/docs/async-runs.md +4 -4
  7. package/docs/command-templates.md +11 -11
  8. package/docs/recipe-library.md +7 -3
  9. package/docs/task-first-recipes.md +44 -43
  10. package/docs/template-recipes.md +7 -2
  11. package/docs/tool-registry.md +7 -5
  12. package/lib/actor-messages.ts +20 -7
  13. package/lib/async-runs.ts +25 -12
  14. package/lib/command-templates.ts +6 -1
  15. package/lib/config.ts +2 -2
  16. package/lib/execution.ts +9 -5
  17. package/lib/observability.ts +20 -10
  18. package/lib/prompts.ts +13 -20
  19. package/lib/tools.ts +196 -64
  20. package/package.json +17 -9
  21. package/recipes/coordinator-locker.json +46 -0
  22. package/recipes/music-player.json +16 -2
  23. package/recipes/pipeline-architect-coordinator.json +11 -3
  24. package/recipes/pipeline-artifact-bundle.json +12 -3
  25. package/recipes/pipeline-artifact-report.json +9 -3
  26. package/recipes/pipeline-artifact-write.json +9 -3
  27. package/recipes/pipeline-async-run-ops.json +18 -9
  28. package/recipes/pipeline-checkpoint-continuation.json +14 -3
  29. package/recipes/pipeline-development-tasking.json +12 -3
  30. package/recipes/pipeline-docs-maintenance.json +12 -3
  31. package/recipes/pipeline-media-library.json +12 -3
  32. package/recipes/pipeline-quorum-review.json +12 -9
  33. package/recipes/pipeline-release-readiness.json +27 -9
  34. package/recipes/pipeline-release-summary.json +89 -0
  35. package/recipes/pipeline-repo-health.json +12 -3
  36. package/recipes/pipeline-research-synthesis.json +11 -3
  37. package/recipes/pipeline-review-readiness.json +12 -6
  38. package/recipes/subagent-artifact.json +9 -3
  39. package/recipes/subagent-checkpoint.json +10 -3
  40. package/recipes/subagent-conflict-report.json +11 -3
  41. package/recipes/subagent-contradiction-map.json +11 -3
  42. package/recipes/subagent-critic.json +11 -3
  43. package/recipes/subagent-evidence-map.json +11 -3
  44. package/recipes/subagent-followup.json +10 -3
  45. package/recipes/subagent-judge.json +11 -3
  46. package/recipes/subagent-merge.json +11 -3
  47. package/recipes/subagent-message.json +8 -3
  48. package/recipes/subagent-normalize.json +11 -3
  49. package/recipes/subagent-plan.json +11 -3
  50. package/recipes/subagent-prompt.json +10 -3
  51. package/recipes/subagent-quorum.json +10 -7
  52. package/recipes/subagent-review-coordinator.json +14 -6
  53. package/recipes/subagent-review.json +11 -3
  54. package/recipes/subagent-task-card.json +11 -3
  55. package/recipes/subagent-tools.json +10 -3
  56. package/recipes/subagent-verify.json +11 -3
  57. package/recipes/subagents-prompts.json +10 -3
  58. package/recipes/utility-coordinator-lock-snapshot.json +14 -0
  59. package/recipes/utility-run-ops-snapshot.json +3 -3
  60. package/recipes/utility-skill-summary.json +14 -0
  61. package/scripts/coordinator-locker.mjs +272 -0
  62. package/scripts/music-player.mjs +2 -1
  63. package/scripts/recipe-utils.mjs +239 -81
  64. package/scripts/validate-recipe.mjs +28 -10
  65. package/skills/actors/SKILL.md +283 -0
  66. package/skills/swarm/SKILL.md +451 -0
  67. package/skills/swarm/references/development-swarm.md +596 -0
package/lib/tools.ts CHANGED
@@ -4,10 +4,10 @@
4
4
  * Owns generated runtime tool schemas and the register_tool management tool schema
5
5
  */
6
6
 
7
- import type { RegisteredTool } from "./config.ts";
8
7
  import * as ActorMessages from "./actor-messages.ts";
9
8
  import * as AsyncRuns from "./async-runs.ts";
10
9
  import * as CommandTemplates from "./command-templates.ts";
10
+ import type { RegisteredTool } from "./config.ts";
11
11
  import * as Execution from "./execution.ts";
12
12
  import * as Prompts from "./prompts.ts";
13
13
  import * as RecipeReferences from "./recipe-references.ts";
@@ -86,8 +86,7 @@ function sampleValueForArg(
86
86
  function shouldAddRuntimeToolUsageHint(error: unknown): boolean {
87
87
  const message = error instanceof Error ? error.message : String(error);
88
88
  return (
89
- /^Argument \S+ must /.test(message) ||
90
- /^Missing .* value: /.test(message)
89
+ /^Argument \S+ must /.test(message) || /^Missing .* value: /.test(message)
91
90
  );
92
91
  }
93
92
 
@@ -176,16 +175,15 @@ function compactAsyncRunStatus(value: unknown): string {
176
175
  return `\n${tokens.join(" ")}`;
177
176
  }
178
177
 
179
- function compactRunEvents(events: AsyncRuns.RunOutboxEvent[]): string {
180
- if (events.length === 0) return "\n(no run events)";
181
- return `\n${events
182
- .map((event) =>
178
+ function compactRunMessages(messages: AsyncRuns.RunOutboxEvent[]): string {
179
+ if (messages.length === 0) return "\n(no actor messages)";
180
+ return `\n${messages
181
+ .map((message) =>
183
182
  [
184
- `run=${event.run}`,
185
- `event=${event.event}`,
186
- `level=${event.level}`,
187
- `delivery=${event.delivery}`,
188
- `summary=${event.summary.replaceAll(/\s+/g, "_")}`,
183
+ `run=${message.run}`,
184
+ `type=${message.event}`,
185
+ `level=${message.level}`,
186
+ `summary=${message.summary.replaceAll(/\s+/g, "_")}`,
189
187
  ].join(" "),
190
188
  )
191
189
  .join("\n")}`;
@@ -209,10 +207,16 @@ function compactActorFiles(status: Record<string, unknown>): string {
209
207
  return `\nrun=${run}${artifactText}${files.length ? ` files=${files.join(",")}` : ""}`;
210
208
  }
211
209
 
212
- function compactSessionRuns(session: string, runs: Array<Record<string, unknown>>): string {
210
+ function compactSessionRuns(
211
+ session: string,
212
+ runs: Array<Record<string, unknown>>,
213
+ ): string {
213
214
  if (runs.length === 0) return `\nsession=${session} runs=0`;
214
215
  return `\nsession=${session} runs=${runs.length}\n${runs
215
- .map((run) => `run=${String(run.run ?? "")} status=${String(run.status ?? "")}${run.recipe ? ` recipe=${String(run.recipe)}` : ""}`)
216
+ .map(
217
+ (run) =>
218
+ `run=${String(run.run ?? "")} status=${String(run.status ?? "")}${run.recipe ? ` recipe=${String(run.recipe)}` : ""}`,
219
+ )
216
220
  .join("\n")}`;
217
221
  }
218
222
 
@@ -236,7 +240,7 @@ function compactActorMessageResult(
236
240
  ];
237
241
  if (result.bytes !== undefined) tokens.push(`bytes=${String(result.bytes)}`);
238
242
  if (result.control) tokens.push(`control=${String(result.control)}`);
239
- if (result.outbox) tokens.push(`outbox=${String(result.outbox)}`);
243
+ if (result.outbox) tokens.push(`messages=${String(result.outbox)}`);
240
244
  if (result.tool) tokens.push(`tool=${String(result.tool)}`);
241
245
  if (result.stopped === true) tokens.push("stopped=true");
242
246
  if (result.signal) tokens.push(`signal=${String(result.signal)}`);
@@ -309,15 +313,23 @@ function messageBodyToRunLine(message: ActorMessages.ActorMessage): string {
309
313
  return JSON.stringify(message.body);
310
314
  }
311
315
 
312
- function messageBodyToToolParams(message: ActorMessages.ActorMessage): Record<string, unknown> {
313
- if (message.body && typeof message.body === "object" && !Array.isArray(message.body)) {
316
+ function messageBodyToToolParams(
317
+ message: ActorMessages.ActorMessage,
318
+ ): Record<string, unknown> {
319
+ if (
320
+ message.body &&
321
+ typeof message.body === "object" &&
322
+ !Array.isArray(message.body)
323
+ ) {
314
324
  return message.body as Record<string, unknown>;
315
325
  }
316
326
  if (message.body === undefined) return {};
317
327
  return { input: message.body };
318
328
  }
319
329
 
320
- function runIdFromActorAddress(address: string | undefined): string | undefined {
330
+ function runIdFromActorAddress(
331
+ address: string | undefined,
332
+ ): string | undefined {
321
333
  if (!address) return undefined;
322
334
  const parsed = ActorMessages.parseActorAddress(address);
323
335
  if (parsed.kind !== "run" || !parsed.value) {
@@ -336,10 +348,18 @@ export function createSpawnToolDefinition<
336
348
  "Create an addressable actor from a recipe file or inline command template. Currently spawns run:<id> actors backed by async runs.",
337
349
  parameters: objectSchema(
338
350
  {
339
- artifacts: looseObjectSchema("Optional named artifact paths for the spawned actor."),
340
- as: stringSchema("Optional actor address for the spawned run, e.g. run:<id>."),
341
- file: stringSchema("Optional template recipe JSON file. Bare names resolve under ~/.pi/agent/recipes."),
342
- recipe: stringSchema("Alias for file; template recipe JSON file/name to spawn."),
351
+ artifacts: looseObjectSchema(
352
+ "Optional named artifact paths for the spawned actor.",
353
+ ),
354
+ as: stringSchema(
355
+ "Optional actor address for the spawned run, e.g. run:<id>.",
356
+ ),
357
+ file: stringSchema(
358
+ "Optional template recipe JSON file. Bare names resolve under ~/.pi/agent/recipes.",
359
+ ),
360
+ recipe: stringSchema(
361
+ "Alias for file; template recipe JSON file/name to spawn.",
362
+ ),
343
363
  state_dir: stringSchema("Optional explicit run state directory."),
344
364
  template: unionSchema([
345
365
  stringSchema("Inline command template string"),
@@ -348,7 +368,9 @@ export function createSpawnToolDefinition<
348
368
  "Inline command-template object with flags such as parallel, repeat, retry, failure, and nested template.",
349
369
  ),
350
370
  ]),
351
- values: looseObjectSchema("Runtime placeholder values passed to the actor."),
371
+ values: looseObjectSchema(
372
+ "Runtime placeholder values passed to the actor.",
373
+ ),
352
374
  verbose: booleanSchema("Return full JSON instead of compact text."),
353
375
  },
354
376
  [],
@@ -366,13 +388,21 @@ export function createSpawnToolDefinition<
366
388
  );
367
389
  const meta = AsyncRuns.startRun(
368
390
  {
369
- file: typeof input.file === "string" ? input.file : typeof input.recipe === "string" ? input.recipe : undefined,
391
+ file:
392
+ typeof input.file === "string"
393
+ ? input.file
394
+ : typeof input.recipe === "string"
395
+ ? input.recipe
396
+ : undefined,
370
397
  ownerId: getRunOwnerId(ctx),
371
398
  run_id: runId,
372
- state_dir: typeof input.state_dir === "string" ? input.state_dir : undefined,
399
+ state_dir:
400
+ typeof input.state_dir === "string" ? input.state_dir : undefined,
373
401
  template: input.template as AsyncRuns.AsyncRunStartParams["template"],
374
402
  values: asRecord(input.values),
375
- ...(input.artifacts && typeof input.artifacts === "object" && !Array.isArray(input.artifacts)
403
+ ...(input.artifacts &&
404
+ typeof input.artifacts === "object" &&
405
+ !Array.isArray(input.artifacts)
376
406
  ? { artifacts: input.artifacts as Record<string, string> }
377
407
  : {}),
378
408
  },
@@ -382,7 +412,11 @@ export function createSpawnToolDefinition<
382
412
  content: [
383
413
  {
384
414
  type: "text" as const,
385
- text: maybeJsonText(meta, input.verbose === true, compactAsyncRunStatus(meta)),
415
+ text: maybeJsonText(
416
+ meta,
417
+ input.verbose === true,
418
+ compactAsyncRunStatus(meta),
419
+ ),
386
420
  },
387
421
  ],
388
422
  details: meta,
@@ -396,22 +430,31 @@ export interface InspectToolDeps<TContext = unknown> {
396
430
  }
397
431
 
398
432
  function getContextSessionId(ctx: unknown): string | undefined {
399
- return (ctx as AsyncRunToolContext | undefined)?.sessionManager?.getSessionId?.();
433
+ return (
434
+ ctx as AsyncRunToolContext | undefined
435
+ )?.sessionManager?.getSessionId?.();
400
436
  }
401
437
 
402
438
  function requireContextSessionId(ctx: unknown, actor: string): string {
403
439
  const sessionId = getContextSessionId(ctx);
404
440
  if (!sessionId) {
405
- throw new Error(`${actor} requires a current coordinator session; use session:<id> or session:all for explicit session inventory.`);
441
+ throw new Error(
442
+ `${actor} requires a current coordinator session; use session:<id> or session:all for explicit session inventory.`,
443
+ );
406
444
  }
407
445
  return sessionId;
408
446
  }
409
447
 
410
- function assertRunAccessibleToContext(runId: string, ctx: unknown): Record<string, unknown> {
448
+ function assertRunAccessibleToContext(
449
+ runId: string,
450
+ ctx: unknown,
451
+ ): Record<string, unknown> {
411
452
  const status = AsyncRuns.getRunStatus(runId);
412
453
  const sessionId = getContextSessionId(ctx);
413
454
  if (sessionId && status.ownerId && status.ownerId !== sessionId) {
414
- throw new Error(`run:${runId} is owned by session:${status.ownerId}; current session is ${sessionId}.`);
455
+ throw new Error(
456
+ `run:${runId} is owned by session:${status.ownerId}; current session is ${sessionId}.`,
457
+ );
415
458
  }
416
459
  return status;
417
460
  }
@@ -427,10 +470,18 @@ export function createInspectToolDefinition<TContext = unknown>(
427
470
  parameters: objectSchema(
428
471
  {
429
472
  lines: stringSchema("Line count for tail/messages views. Default 40."),
430
- status: stringSchema("Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited."),
431
- target: stringSchema("Actor address to inspect, e.g. run:<id>, coordinator, session:<id>, session:all, or tool:<name>."),
432
- verbose: booleanSchema("Return full JSON instead of compact text where available."),
433
- view: stringSchema("Inspection view: status, tail, messages, artifacts, files, or mailbox."),
473
+ status: stringSchema(
474
+ "Optional session run filter: all, running, active, terminal, done, failed, cancelled, killed, or exited.",
475
+ ),
476
+ target: stringSchema(
477
+ "Actor address to inspect, e.g. run:<id>, coordinator, session:<id>, session:all, or tool:<name>.",
478
+ ),
479
+ verbose: booleanSchema(
480
+ "Return full JSON instead of compact text where available.",
481
+ ),
482
+ view: stringSchema(
483
+ "Inspection view: status, tail, messages, artifacts, files, or mailbox.",
484
+ ),
434
485
  },
435
486
  ["target", "view"],
436
487
  ),
@@ -447,17 +498,26 @@ export function createInspectToolDefinition<TContext = unknown>(
447
498
  const view = String(input.view ?? "");
448
499
  if (address.kind === "coordinator") {
449
500
  if (view !== "status" && view !== "runs") {
450
- throw new Error("inspect coordinator supports view=status or view=runs.");
501
+ throw new Error(
502
+ "inspect coordinator supports view=status or view=runs.",
503
+ );
451
504
  }
452
505
  const session = requireContextSessionId(ctx, "inspect coordinator");
453
- const runs = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined)
506
+ const runs = AsyncRuns.listRuns(
507
+ undefined,
508
+ typeof input.status === "string" ? input.status : undefined,
509
+ )
454
510
  .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
455
511
  .filter((run) => run.ownerId === session);
456
512
  return {
457
513
  content: [
458
514
  {
459
515
  type: "text" as const,
460
- text: maybeJsonText({ session, runs }, input.verbose === true, compactSessionRuns(session, runs)),
516
+ text: maybeJsonText(
517
+ { session, runs },
518
+ input.verbose === true,
519
+ compactSessionRuns(session, runs),
520
+ ),
461
521
  },
462
522
  ],
463
523
  details: { session, runs },
@@ -465,16 +525,27 @@ export function createInspectToolDefinition<TContext = unknown>(
465
525
  }
466
526
  if (address.kind === "session") {
467
527
  if (view !== "status" && view !== "runs") {
468
- throw new Error("inspect session:<id> supports view=status or view=runs.");
528
+ throw new Error(
529
+ "inspect session:<id> supports view=status or view=runs.",
530
+ );
469
531
  }
470
- const runs = AsyncRuns.listRuns(undefined, typeof input.status === "string" ? input.status : undefined)
532
+ const runs = AsyncRuns.listRuns(
533
+ undefined,
534
+ typeof input.status === "string" ? input.status : undefined,
535
+ )
471
536
  .map((run) => AsyncRuns.getRunStatus(String(run.state_dir)))
472
- .filter((run) => address.value === "all" || run.ownerId === address.value);
537
+ .filter(
538
+ (run) => address.value === "all" || run.ownerId === address.value,
539
+ );
473
540
  return {
474
541
  content: [
475
542
  {
476
543
  type: "text" as const,
477
- text: maybeJsonText({ session: address.value, runs }, input.verbose === true, compactSessionRuns(address.value || "", runs)),
544
+ text: maybeJsonText(
545
+ { session: address.value, runs },
546
+ input.verbose === true,
547
+ compactSessionRuns(address.value || "", runs),
548
+ ),
478
549
  },
479
550
  ],
480
551
  details: { session: address.value, runs },
@@ -482,7 +553,9 @@ export function createInspectToolDefinition<TContext = unknown>(
482
553
  }
483
554
  if (address.kind === "tool" && address.value) {
484
555
  if (view !== "status" && view !== "schema") {
485
- throw new Error("inspect tool:<name> supports view=status or view=schema.");
556
+ throw new Error(
557
+ "inspect tool:<name> supports view=status or view=schema.",
558
+ );
486
559
  }
487
560
  const tool = deps.getTool?.(address.value);
488
561
  if (!tool) throw new Error(`tool actor not found: ${address.value}`);
@@ -496,14 +569,21 @@ export function createInspectToolDefinition<TContext = unknown>(
496
569
  content: [
497
570
  {
498
571
  type: "text" as const,
499
- text: maybeJsonText(details, input.verbose === true || view === "schema", compactToolActor(address.value, details)),
572
+ text: maybeJsonText(
573
+ details,
574
+ input.verbose === true || view === "schema",
575
+ compactToolActor(address.value, details),
576
+ ),
500
577
  },
501
578
  ],
502
579
  details,
503
580
  };
504
581
  }
505
582
  const runId = address.kind === "run" ? address.value : undefined;
506
- if (!runId) throw new Error("inspect target must be run:<id>, coordinator, session:<id>, or tool:<name>.");
583
+ if (!runId)
584
+ throw new Error(
585
+ "inspect target must be run:<id>, coordinator, session:<id>, or tool:<name>.",
586
+ );
507
587
  switch (view) {
508
588
  case "status": {
509
589
  const status = assertRunAccessibleToContext(runId, ctx);
@@ -511,7 +591,11 @@ export function createInspectToolDefinition<TContext = unknown>(
511
591
  content: [
512
592
  {
513
593
  type: "text" as const,
514
- text: maybeJsonText(status, input.verbose === true, compactAsyncRunStatus(status)),
594
+ text: maybeJsonText(
595
+ status,
596
+ input.verbose === true,
597
+ compactAsyncRunStatus(status),
598
+ ),
515
599
  },
516
600
  ],
517
601
  details: status,
@@ -520,16 +604,26 @@ export function createInspectToolDefinition<TContext = unknown>(
520
604
  case "tail": {
521
605
  assertRunAccessibleToContext(runId, ctx);
522
606
  const text = AsyncRuns.tailRun(runId, Number(input.lines || 40));
523
- return { content: [{ type: "text" as const, text: `\n${text}` }], details: {} };
607
+ return {
608
+ content: [{ type: "text" as const, text: `\n${text}` }],
609
+ details: {},
610
+ };
524
611
  }
525
612
  case "messages": {
526
613
  assertRunAccessibleToContext(runId, ctx);
527
- const messages = AsyncRuns.readRunEvents(runId, Number(input.lines || 40));
614
+ const messages = AsyncRuns.readRunEvents(
615
+ runId,
616
+ Number(input.lines || 40),
617
+ );
528
618
  return {
529
619
  content: [
530
620
  {
531
621
  type: "text" as const,
532
- text: maybeJsonText(messages, input.verbose === true, compactRunEvents(messages)),
622
+ text: maybeJsonText(
623
+ messages,
624
+ input.verbose === true,
625
+ compactRunMessages(messages),
626
+ ),
533
627
  },
534
628
  ],
535
629
  details: { messages },
@@ -542,7 +636,11 @@ export function createInspectToolDefinition<TContext = unknown>(
542
636
  content: [
543
637
  {
544
638
  type: "text" as const,
545
- text: maybeJsonText(status, input.verbose === true, compactActorFiles(status)),
639
+ text: maybeJsonText(
640
+ status,
641
+ input.verbose === true,
642
+ compactActorFiles(status),
643
+ ),
546
644
  },
547
645
  ],
548
646
  details: status,
@@ -555,14 +653,20 @@ export function createInspectToolDefinition<TContext = unknown>(
555
653
  content: [
556
654
  {
557
655
  type: "text" as const,
558
- text: maybeJsonText(mailbox, input.verbose === true, `\nrun=${String(status.run ?? runId)} accepts=${Array.isArray(mailbox.accepts) ? mailbox.accepts.join(",") : ""} emits=${Array.isArray(mailbox.emits) ? mailbox.emits.join(",") : ""}`),
656
+ text: maybeJsonText(
657
+ mailbox,
658
+ input.verbose === true,
659
+ `\nrun=${String(status.run ?? runId)} accepts=${Array.isArray(mailbox.accepts) ? mailbox.accepts.join(",") : ""} emits=${Array.isArray(mailbox.emits) ? mailbox.emits.join(",") : ""}`,
660
+ ),
559
661
  },
560
662
  ],
561
663
  details: { mailbox },
562
664
  };
563
665
  }
564
666
  default:
565
- throw new Error("inspect view must be one of: status, tail, messages, artifacts, files, mailbox.");
667
+ throw new Error(
668
+ "inspect view must be one of: status, tail, messages, artifacts, files, mailbox.",
669
+ );
566
670
  }
567
671
  },
568
672
  };
@@ -583,17 +687,29 @@ export function createActorMessageToolDefinition<TContext = unknown>(
583
687
  parameters: objectSchema(
584
688
  {
585
689
  body: unionSchema([
586
- stringSchema("Message body. For run:<id>, this is the run-local command line."),
690
+ stringSchema(
691
+ "Message body. For run:<id>, this is the run-local command line.",
692
+ ),
587
693
  looseObjectSchema("Structured JSON message body."),
588
694
  arraySchema("Structured JSON message body array."),
589
695
  ]),
590
- correlation_id: stringSchema("Optional correlation id for workflow/task linkage."),
591
- from: stringSchema("Optional sender address, such as coordinator or run:<id>."),
592
- metadata: looseObjectSchema("Optional structured metadata for routing or domain hints."),
696
+ correlation_id: stringSchema(
697
+ "Optional correlation id for workflow/task linkage.",
698
+ ),
699
+ from: stringSchema(
700
+ "Optional sender address, such as coordinator or run:<id>.",
701
+ ),
702
+ metadata: looseObjectSchema(
703
+ "Optional structured metadata for routing or domain hints.",
704
+ ),
593
705
  reply_to: stringSchema("Optional message id this message replies to."),
594
706
  summary: stringSchema("Optional short human-facing summary."),
595
- to: stringSchema("Destination actor address, e.g. run:<id>, branch:<run>/<branch>, coordinator, session:<id>, or tool:<name>."),
596
- type: stringSchema("Semantic message type, e.g. control.approve or checkpoint.needs_scope."),
707
+ to: stringSchema(
708
+ "Destination actor address, e.g. run:<id>, branch:<run>/<branch>, coordinator, session:<id>, or tool:<name>.",
709
+ ),
710
+ type: stringSchema(
711
+ "Semantic message type, e.g. control.approve or checkpoint.needs_scope.",
712
+ ),
597
713
  verbose: booleanSchema("Return full JSON instead of compact text."),
598
714
  },
599
715
  ["to", "type"],
@@ -611,7 +727,10 @@ export function createActorMessageToolDefinition<TContext = unknown>(
611
727
  let result: Record<string, unknown>;
612
728
  if (address.kind === "run" && address.value) {
613
729
  assertRunAccessibleToContext(address.value, ctx);
614
- if (message.type === "control.stop" || message.type === "control.cancel") {
730
+ if (
731
+ message.type === "control.stop" ||
732
+ message.type === "control.cancel"
733
+ ) {
615
734
  result = AsyncRuns.cancelRun(address.value);
616
735
  } else if (message.type === "control.kill") {
617
736
  result = AsyncRuns.killRun(address.value);
@@ -630,7 +749,9 @@ export function createActorMessageToolDefinition<TContext = unknown>(
630
749
  } else if (address.kind === "tool" && address.value) {
631
750
  const tool = deps.getTool?.(address.value);
632
751
  if (!tool || typeof tool.execute !== "function") {
633
- throw new Error(`tool actor not found or not executable: ${address.value}`);
752
+ throw new Error(
753
+ `tool actor not found or not executable: ${address.value}`,
754
+ );
634
755
  }
635
756
  const toolResult = await tool.execute(
636
757
  `message:${message.type}`,
@@ -651,15 +772,21 @@ export function createActorMessageToolDefinition<TContext = unknown>(
651
772
  }
652
773
  const sender = ActorMessages.parseActorAddress(message.from);
653
774
  if (sender.kind !== "run" || !sender.value) {
654
- throw new Error(`message to ${address.kind} currently requires from=run:<id>.`);
775
+ throw new Error(
776
+ `message to ${address.kind} currently requires from=run:<id>.`,
777
+ );
655
778
  }
656
779
  const senderStatus = assertRunAccessibleToContext(sender.value, ctx);
657
780
  if (address.kind === "session") {
658
781
  if (!senderStatus.ownerId) {
659
- throw new Error(`message to session:${address.value} requires sender run owner ${address.value}; got no owner.`);
782
+ throw new Error(
783
+ `message to session:${address.value} requires sender run owner ${address.value}; got no owner.`,
784
+ );
660
785
  }
661
786
  if (senderStatus.ownerId !== address.value) {
662
- throw new Error(`message to session:${address.value} requires sender run owner ${address.value}; got ${senderStatus.ownerId}.`);
787
+ throw new Error(
788
+ `message to session:${address.value} requires sender run owner ${address.value}; got ${senderStatus.ownerId}.`,
789
+ );
663
790
  }
664
791
  }
665
792
  result = AsyncRuns.appendRunOutboxEvent(sender.value, {
@@ -807,7 +934,12 @@ export function createRuntimeToolDefinition(
807
934
  signal,
808
935
  );
809
936
  } catch (error) {
810
- throw formatRuntimeToolArgumentError(cfg, error, required, isAsyncRecipe);
937
+ throw formatRuntimeToolArgumentError(
938
+ cfg,
939
+ error,
940
+ required,
941
+ isAsyncRecipe,
942
+ );
811
943
  }
812
944
  },
813
945
  };
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.14.3",
3
+ "version": "0.15.0",
4
4
  "private": false,
5
5
  "description": "Actor runtime and orchestrator for agent-managed local processes",
6
+ "keywords": [
7
+ "pi-package",
8
+ "pi-extension",
9
+ "actors",
10
+ "orchestration",
11
+ "automation",
12
+ "tools"
13
+ ],
6
14
  "type": "module",
7
15
  "license": "MIT",
8
16
  "repository": {
@@ -13,14 +21,9 @@
13
21
  "bugs": {
14
22
  "url": "https://github.com/llblab/pi-actors/issues"
15
23
  },
16
- "keywords": [
17
- "pi-package",
18
- "pi-extension",
19
- "actors",
20
- "orchestration",
21
- "automation",
22
- "tools"
23
- ],
24
+ "engines": {
25
+ "node": ">=22.19.0"
26
+ },
24
27
  "scripts": {
25
28
  "check": "node --experimental-strip-types -e \"await import('./index.ts'); console.log('pi-actors: extension import ok')\"",
26
29
  "test": "node --experimental-strip-types --test tests/*.test.ts",
@@ -32,6 +35,7 @@
32
35
  "lib",
33
36
  "scripts",
34
37
  "recipes",
38
+ "skills",
35
39
  "README.md",
36
40
  "AGENTS.md",
37
41
  "BACKLOG.md",
@@ -41,6 +45,10 @@
41
45
  "pi": {
42
46
  "extensions": [
43
47
  "./index.ts"
48
+ ],
49
+ "skills": [
50
+ "./skills/actors/SKILL.md",
51
+ "./skills/swarm/SKILL.md"
44
52
  ]
45
53
  },
46
54
  "peerDependencies": {
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "coordinator-locker",
3
+ "async": true,
4
+ "args": [
5
+ "repo:path",
6
+ "lease_ms:int"
7
+ ],
8
+ "defaults": {
9
+ "repo": "~/.pi/agent/extensions/pi-actors",
10
+ "lease_ms": "600000"
11
+ },
12
+ "mailbox": {
13
+ "accepts": [
14
+ "coord.enqueue",
15
+ "coord.claim",
16
+ "coord.complete",
17
+ "coord.fail",
18
+ "lock.acquire",
19
+ "lock.renew",
20
+ "lock.release",
21
+ "control.stop",
22
+ "control.cancel",
23
+ "control.kill"
24
+ ],
25
+ "emits": [
26
+ "coord.started",
27
+ "coord.enqueued",
28
+ "coord.assigned",
29
+ "coord.empty",
30
+ "coord.completed",
31
+ "coord.failed",
32
+ "lock.granted",
33
+ "lock.denied",
34
+ "lock.renewed",
35
+ "lock.released",
36
+ "run.done",
37
+ "run.failed"
38
+ ]
39
+ },
40
+ "artifacts": {
41
+ "queue": "{state_dir}/queue.json",
42
+ "locks": "{state_dir}/locks.json",
43
+ "journal": "{state_dir}/journal.jsonl"
44
+ },
45
+ "template": "{repo}/scripts/coordinator-locker.mjs serve --state-dir {state_dir} --lease-ms {lease_ms}"
46
+ }
@@ -18,8 +18,22 @@
18
18
  "player": "auto"
19
19
  },
20
20
  "mailbox": {
21
- "accepts": ["control.stop", "control.cancel", "control.kill", "player.play", "player.pause", "player.resume", "player.toggle", "player.next", "player.previous", "player.stop", "player.status"],
22
- "emits": ["player.track"]
21
+ "accepts": [
22
+ "control.stop",
23
+ "control.cancel",
24
+ "control.kill",
25
+ "player.play",
26
+ "player.pause",
27
+ "player.resume",
28
+ "player.toggle",
29
+ "player.next",
30
+ "player.previous",
31
+ "player.stop",
32
+ "player.status"
33
+ ],
34
+ "emits": [
35
+ "player.track"
36
+ ]
23
37
  },
24
38
  "template": "{repo}/scripts/music-player.mjs {command} {source} {loop} {volume} {player} {state_dir}"
25
39
  }
@@ -23,12 +23,20 @@
23
23
  "failure modes"
24
24
  ],
25
25
  "constraints": "Prefer small composable pieces, explicit contracts, and no broad workflow DSL.",
26
- "model": "openai-codex/gpt-5.5",
27
26
  "tools": ""
28
27
  },
29
28
  "mailbox": {
30
- "accepts": ["control.stop", "control.cancel", "control.kill"],
31
- "emits": ["pipeline.completed", "command.done", "run.done", "run.failed"]
29
+ "accepts": [
30
+ "control.stop",
31
+ "control.cancel",
32
+ "control.kill"
33
+ ],
34
+ "emits": [
35
+ "pipeline.completed",
36
+ "command.done",
37
+ "run.done",
38
+ "run.failed"
39
+ ]
32
40
  },
33
41
  "template": [
34
42
  {
@@ -32,12 +32,21 @@
32
32
  "validation_command": "true",
33
33
  "validation_scope": ".",
34
34
  "validation_timeout_ms": "300000",
35
- "model": "openai-codex/gpt-5.5",
36
35
  "tools": ""
37
36
  },
38
37
  "mailbox": {
39
- "accepts": ["control.stop", "control.cancel", "control.kill"],
40
- "emits": ["artifact.bundle_ready", "artifact.written", "artifact.blocked", "run.done", "run.failed"]
38
+ "accepts": [
39
+ "control.stop",
40
+ "control.cancel",
41
+ "control.kill"
42
+ ],
43
+ "emits": [
44
+ "artifact.bundle_ready",
45
+ "artifact.written",
46
+ "artifact.blocked",
47
+ "run.done",
48
+ "run.failed"
49
+ ]
41
50
  },
42
51
  "artifacts": {
43
52
  "artifact": "{artifact_path}",