@llblab/pi-actors 0.46.1 → 0.48.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 (54) hide show
  1. package/AGENTS.md +7 -5
  2. package/BACKLOG.md +0 -582
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +8 -1
  5. package/banner.jpg +0 -0
  6. package/dist/lib/prompts.d.ts +6 -5
  7. package/dist/lib/prompts.js +22 -23
  8. package/dist/lib/recipes-discovery.d.ts +4 -0
  9. package/dist/lib/recipes-discovery.js +13 -1
  10. package/dist/lib/recipes-references.js +10 -6
  11. package/dist/lib/registry.d.ts +15 -11
  12. package/dist/lib/registry.js +195 -25
  13. package/dist/lib/runtime.js +37 -2
  14. package/dist/lib/tools-inspect.js +156 -12
  15. package/dist/lib/tools-register.js +2 -1
  16. package/dist/lib/tools-response.js +5 -1
  17. package/dist/scripts/conformance.mjs +1 -0
  18. package/dist/skills/actors/SKILL.md +87 -65
  19. package/dist/skills/actors/references/diagnostics.md +44 -0
  20. package/dist/skills/actors/references/persistent-tools.md +74 -0
  21. package/dist/skills/actors/references/recipes.md +51 -0
  22. package/dist/skills/actors/references/runs.md +39 -0
  23. package/dist/skills/artifacts/SKILL.md +24 -7
  24. package/dist/skills/media/SKILL.md +35 -7
  25. package/dist/skills/project-work/SKILL.md +28 -7
  26. package/dist/skills/recipe-memory/SKILL.md +27 -7
  27. package/dist/skills/swarm/SKILL.md +56 -437
  28. package/dist/skills/swarm/references/development-swarm.md +118 -525
  29. package/dist/skills/swarm/references/review-swarms.md +115 -0
  30. package/docs/README.md +5 -5
  31. package/docs/recipe-library.md +15 -10
  32. package/docs/tool-registry.md +10 -4
  33. package/lib/prompts.ts +24 -24
  34. package/lib/recipes-discovery.ts +22 -1
  35. package/lib/recipes-references.ts +14 -6
  36. package/lib/registry.ts +288 -51
  37. package/lib/runtime.ts +41 -2
  38. package/lib/tools-inspect.ts +202 -10
  39. package/lib/tools-register.ts +4 -3
  40. package/lib/tools-response.ts +5 -1
  41. package/package.json +1 -1
  42. package/scripts/conformance.mjs +1 -0
  43. package/skills/actors/SKILL.md +87 -65
  44. package/skills/actors/references/diagnostics.md +44 -0
  45. package/skills/actors/references/persistent-tools.md +74 -0
  46. package/skills/actors/references/recipes.md +51 -0
  47. package/skills/actors/references/runs.md +39 -0
  48. package/skills/artifacts/SKILL.md +24 -7
  49. package/skills/media/SKILL.md +35 -7
  50. package/skills/project-work/SKILL.md +28 -7
  51. package/skills/recipe-memory/SKILL.md +27 -7
  52. package/skills/swarm/SKILL.md +56 -437
  53. package/skills/swarm/references/development-swarm.md +118 -525
  54. package/skills/swarm/references/review-swarms.md +115 -0
@@ -4,7 +4,7 @@
4
4
  * Owns exact inspect target/view dispatch; source projection stays in domain modules.
5
5
  */
6
6
 
7
- import { statSync } from "node:fs";
7
+ import { existsSync, statSync } from "node:fs";
8
8
  import { isAbsolute, join, relative } from "node:path";
9
9
 
10
10
  import * as AsyncRuns from "./async-runs.ts";
@@ -208,8 +208,135 @@ function portableSkillRecipePath(
208
208
  : `<active-skill:${skill}>`;
209
209
  }
210
210
 
211
+ function portableSkillRecipeDiagnosticReason(
212
+ diagnostic: RecipesReferences.SkillRecipeComponentDiagnostic,
213
+ namespaces: Record<string, string[]>,
214
+ ): string {
215
+ let reason = diagnostic.reason;
216
+ if (diagnostic.file) {
217
+ reason = reason.replaceAll(
218
+ diagnostic.file,
219
+ portableSkillRecipePath(
220
+ diagnostic.file,
221
+ diagnostic.skill,
222
+ namespaces,
223
+ ),
224
+ );
225
+ }
226
+ for (const root of namespaces[diagnostic.skill] ?? []) {
227
+ reason = reason.replaceAll(root, `<active-skill:${diagnostic.skill}>`);
228
+ }
229
+ return reason;
230
+ }
231
+
232
+ function inspectRecipeIdentity(
233
+ identity: string,
234
+ resolutionContext: RecipeResolution.RecipeResolutionContext | undefined,
235
+ namespaces: Record<string, string[]>,
236
+ inventory: RecipesReferences.SkillRecipeComponentInventory,
237
+ registryStatus: Runtime.RecipeRegistryStatus | undefined,
238
+ ): Record<string, unknown> {
239
+ const match = /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\/([a-z][a-z0-9]*(?:-[a-z0-9]+)*)$/u.exec(identity);
240
+ const skill = match?.[1] ?? "";
241
+ const roots = skill ? namespaces[skill] ?? [] : [];
242
+ const skillActive = roots.length === 1;
243
+ const matchingComponent = inventory.components.find(
244
+ (component) => component.identity === identity,
245
+ );
246
+ const matchingRejection = inventory.rejected.find(
247
+ (diagnostic) =>
248
+ diagnostic.skill === skill &&
249
+ (diagnostic.stem === undefined || diagnostic.stem === match?.[2]),
250
+ );
251
+ let sourceLocation: string | undefined;
252
+ let rejectedReason: string | undefined;
253
+ let resolvable = false;
254
+ if (!match) {
255
+ rejectedReason =
256
+ "Identity must use canonical <skill>/<recipe> syntax.";
257
+ } else if (roots.length === 0) {
258
+ rejectedReason = `Active Skill Recipe not found: ${identity}`;
259
+ } else if (roots.length > 1) {
260
+ rejectedReason = matchingRejection
261
+ ? portableSkillRecipeDiagnosticReason(matchingRejection, namespaces)
262
+ : `Duplicate active Skill identity ${skill}`;
263
+ } else if (matchingRejection) {
264
+ rejectedReason = portableSkillRecipeDiagnosticReason(
265
+ matchingRejection,
266
+ namespaces,
267
+ );
268
+ if (matchingRejection.file) {
269
+ sourceLocation = portableSkillRecipePath(
270
+ matchingRejection.file,
271
+ skill,
272
+ namespaces,
273
+ );
274
+ }
275
+ } else {
276
+ try {
277
+ const file = RecipesReferences.resolveRecipeReferencePath(
278
+ identity,
279
+ resolutionContext?.cwd,
280
+ resolutionContext?.activeSkills,
281
+ );
282
+ if (!file || !existsSync(file)) {
283
+ rejectedReason = `Skill Recipe component not found: ${identity}`;
284
+ } else {
285
+ sourceLocation = portableSkillRecipePath(file, skill, namespaces);
286
+ const recipe = RecipesReferences.readResolvedRecipeConfig(file, [], {
287
+ skillContext: resolutionContext?.activeSkills,
288
+ });
289
+ if (recipe) resolvable = true;
290
+ else {
291
+ rejectedReason =
292
+ RecipesReferences.diagnoseRawRecipeConfigFailure(file) ??
293
+ `Recipe could not be resolved: ${identity}`;
294
+ }
295
+ }
296
+ } catch (error) {
297
+ rejectedReason = error instanceof Error ? error.message : String(error);
298
+ }
299
+ }
300
+ const componentStatus = resolvable
301
+ ? "available"
302
+ : !match
303
+ ? "invalid_identity"
304
+ : roots.length === 0
305
+ ? "skill_inactive"
306
+ : roots.length > 1
307
+ ? "ambiguous_skill"
308
+ : matchingRejection
309
+ ? "rejected"
310
+ : matchingComponent
311
+ ? "unresolvable"
312
+ : "missing";
313
+ return {
314
+ identity,
315
+ skill_active: skillActive,
316
+ resolvable,
317
+ catalog_partial: inventory.partial,
318
+ component_status: componentStatus,
319
+ ...(sourceLocation ? { source_location: sourceLocation } : {}),
320
+ resolution_generation:
321
+ resolutionContext?.generation ?? registryStatus?.resolution_generation ?? "unavailable",
322
+ ...(rejectedReason ? { rejected_reason: rejectedReason } : {}),
323
+ next_actions: resolvable
324
+ ? [
325
+ `spawn recipe=${identity}`,
326
+ `register_tool name=<tool-name> from=${identity}`,
327
+ ]
328
+ : roots.length === 0 && skill
329
+ ? [
330
+ `activate Skill ${skill}`,
331
+ `inspect target=recipes view=doctor identity=${identity}`,
332
+ ]
333
+ : ["inspect target=recipes view=imports verbose=true"],
334
+ };
335
+ }
336
+
211
337
  function inspectRecipes(
212
338
  view: string,
339
+ input: Record<string, unknown>,
213
340
  deps: InspectToolDeps,
214
341
  context: unknown,
215
342
  ): Record<string, unknown> {
@@ -245,6 +372,21 @@ function inspectRecipes(
245
372
  );
246
373
  const skillInventory =
247
374
  RecipesReferences.inventoryActiveSkillRecipeComponents(activeSkillContext);
375
+ const registryStatus = deps.registryStatus?.();
376
+ const identity =
377
+ typeof input.identity === "string" ? input.identity.trim() : "";
378
+ if (identity && view !== "doctor") {
379
+ throw new Error("inspect recipes identity is supported only with view=doctor.");
380
+ }
381
+ if (view === "doctor" && identity) {
382
+ return inspectRecipeIdentity(
383
+ identity,
384
+ resolutionContext,
385
+ skillRecipeNamespaces,
386
+ skillInventory,
387
+ registryStatus,
388
+ );
389
+ }
248
390
  const skillRecipeComponents = skillInventory.components.map((component) => ({
249
391
  identity: component.identity,
250
392
  source_kind: "active_skill_component",
@@ -254,7 +396,10 @@ function inspectRecipes(
254
396
  }));
255
397
  const skillRecipeComponentDiagnostics = skillInventory.rejected.map(
256
398
  (diagnostic) => ({
257
- error: diagnostic.reason,
399
+ error: portableSkillRecipeDiagnosticReason(
400
+ diagnostic,
401
+ skillRecipeNamespaces,
402
+ ),
258
403
  ...(diagnostic.file
259
404
  ? {
260
405
  file: portableSkillRecipePath(
@@ -271,9 +416,9 @@ function inspectRecipes(
271
416
  const discoverySummary = RecipesDiscovery.summarizeDiscovery(discovered);
272
417
  const summary = {
273
418
  ...discoverySummary,
274
- ...(deps.registryStatus
419
+ ...(registryStatus
275
420
  ? {
276
- ...deps.registryStatus(),
421
+ ...registryStatus,
277
422
  watched_root: "~/.pi/agent/recipes",
278
423
  }
279
424
  : {}),
@@ -399,19 +544,63 @@ function inspectTool(
399
544
  throw new Error("inspect tool:<name> supports view=status or view=schema.");
400
545
  }
401
546
  const tool = deps.getTool?.(name);
402
- if (!tool) throw new Error(`registered tool not found: ${name}`);
547
+ const status = deps.getToolStatus?.(name);
548
+ if (!tool && !status) {
549
+ throw new Error(
550
+ `Registered tool not found: ${name}. Next: inspect target=recipes view=doctor; do not substitute spawn for tool invocation.`,
551
+ );
552
+ }
553
+ if (view === "schema" && !tool) {
554
+ throw new Error(
555
+ `Tool "${name}" is registered but its callable schema is unavailable; callable_now=${String(status?.callable_now ?? false)}, activation_boundary=${String(status?.activation_boundary ?? "host_registration")}. Next: inspect target=tool:${name} view=status.`,
556
+ );
557
+ }
403
558
  return {
404
559
  name,
405
- ...(view === "status" ? deps.getToolStatus?.(name) : {}),
406
- description: tool.description,
407
- parameters: tool.parameters,
408
- promptSnippet: tool.promptSnippet,
560
+ ...(view === "status" ? status : {}),
561
+ ...(tool
562
+ ? {
563
+ description: tool.description,
564
+ parameters: tool.parameters,
565
+ promptSnippet: tool.promptSnippet,
566
+ }
567
+ : {}),
409
568
  };
410
569
  }
411
570
 
412
571
  function compactResult(target: string, view: string, details: Record<string, unknown>): string {
413
572
  if (target === "runtime") return compactRuntime(view, details);
573
+ if (target === "recipes" && typeof details.identity === "string") {
574
+ const lines = [
575
+ `recipes doctor identity=${details.identity} skill_active=${String(details.skill_active)} resolvable=${String(details.resolvable)} catalog_partial=${String(details.catalog_partial)} component_status=${String(details.component_status)}`,
576
+ ...(details.source_location
577
+ ? [`source=${String(details.source_location)}`]
578
+ : []),
579
+ `resolution_generation=${String(details.resolution_generation)}`,
580
+ ...(details.rejected_reason
581
+ ? [`rejected_reason=${String(details.rejected_reason)}`]
582
+ : []),
583
+ ...((details.next_actions as unknown[] | undefined) ?? []).map(
584
+ (action) => `next=${String(action)}`,
585
+ ),
586
+ ];
587
+ return `\n${lines.join("\n")}`;
588
+ }
414
589
  if (target === "recipes") return ToolsResponse.compactRecipeRegistry(details);
590
+ if (target.startsWith("tool:") && view === "status") {
591
+ const required = Array.isArray(details.required_args)
592
+ ? details.required_args.map(String).join(",") || "none"
593
+ : "unknown";
594
+ const optional = Array.isArray(details.optional_args)
595
+ ? details.optional_args.map(String).join(",") || "none"
596
+ : "unknown";
597
+ const next = Array.isArray(details.next_actions) && details.next_actions.length > 0
598
+ ? String(details.next_actions[0])
599
+ : details.callable_now === true
600
+ ? `call tool ${target.slice(5)}`
601
+ : `register_tool name=${target.slice(5)} update=true`;
602
+ return `\ntool=${target.slice(5)} source=${String(details.source ?? "unknown")} callable_now=${String(details.callable_now ?? false)} activation_boundary=${String(details.activation_boundary ?? "unknown")} required=${required} optional=${optional} launch_kind=${String(details.launch_kind ?? "none")} spawn_calls=${Number(details.spawn_calls ?? 0)} tool_calls=${Number(details.tool_calls ?? 0)} next=${next}`;
603
+ }
415
604
  if (target.startsWith("tool:")) return `\ntool=${target.slice(5)} view=${view}`;
416
605
  return `\nrun=${target.slice(4)} view=${view}`;
417
606
  }
@@ -426,6 +615,9 @@ export function createInspectToolDefinition<TContext = unknown>(
426
615
  "Inspect a Run's Recipe, Trace, or Control evidence, or runtime/recipe/tool diagnostics.",
427
616
  parameters: Schema.objectSchema(
428
617
  {
618
+ identity: Schema.stringSchema(
619
+ "Optional canonical <skill>/<recipe> identity for focused Recipe doctor diagnosis.",
620
+ ),
429
621
  lines: Schema.stringSchema("Bounded item count for Trace or recent Controls."),
430
622
  source: Schema.stringSchema(
431
623
  "Optional Trace source: all, lifecycle, control, process, agent, artifact, or runtime.",
@@ -453,7 +645,7 @@ export function createInspectToolDefinition<TContext = unknown>(
453
645
  if (target === "runtime") {
454
646
  details = inspectRuntime(view, input, ctx, deps);
455
647
  } else if (target === "recipes") {
456
- details = inspectRecipes(view, deps, ctx);
648
+ details = inspectRecipes(view, input, deps, ctx);
457
649
  } else {
458
650
  const run = parseRunTarget(target);
459
651
  if (run) details = inspectRun(run, view, input, ctx, deps);
@@ -36,7 +36,11 @@ export function createRegisterToolDefinition<TContext>(
36
36
  description: stringSchema(
37
37
  Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description,
38
38
  ),
39
+ defaults: looseObjectSchema(
40
+ Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.defaults,
41
+ ),
39
42
  draft: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.draft),
43
+ from: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.from),
40
44
  name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
41
45
  template: unionSchema([
42
46
  stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
@@ -45,9 +49,6 @@ export function createRegisterToolDefinition<TContext>(
45
49
  nullSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateNull),
46
50
  ]),
47
51
  update: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.update),
48
- values: looseObjectSchema(
49
- Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.values,
50
- ),
51
52
  },
52
53
  [],
53
54
  ),
@@ -240,6 +240,9 @@ export function recipeRegistryNextActions(
240
240
  if (view === "doctor" && typeof topAction.action === "string") {
241
241
  actions.push(String(topAction.action));
242
242
  }
243
+ if (summary.skill_recipe_catalog_partial === true) {
244
+ actions.push("inspect target=recipes view=imports verbose=true");
245
+ }
243
246
  if (drafts.length > 0) {
244
247
  actions.push("inspect target=recipes view=summary verbose=true");
245
248
  const firstPath =
@@ -267,6 +270,7 @@ export function compactRecipeRegistry(
267
270
  const recommendations = Array.isArray(summary.recommendations)
268
271
  ? summary.recommendations.length
269
272
  : 0;
273
+ const skillCatalogPartial = summary.skill_recipe_catalog_partial === true;
270
274
  const currentPolicy = Array.isArray(summary.active)
271
275
  ? (summary.active as Array<Record<string, unknown>>).filter(
272
276
  (entry) => entry.current_policy,
@@ -275,7 +279,7 @@ export function compactRecipeRegistry(
275
279
  const nextActions = Array.isArray(summary.next_actions)
276
280
  ? (summary.next_actions as string[])
277
281
  : [];
278
- return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} current_policy=${currentPolicy} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
282
+ return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} current_policy=${currentPolicy} skill_catalog_partial=${skillCatalogPartial} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
279
283
  }
280
284
 
281
285
  export const DEFAULT_INSPECT_LINES = Limits.DEFAULT_INSPECT_LINES;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.46.1",
3
+ "version": "0.48.0",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -12,6 +12,7 @@ import { dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
 
14
14
  const conformanceSuites = [
15
+ "tests/agent-journeys.test.ts",
15
16
  "tests/control.test.ts",
16
17
  "tests/runs-controls.test.ts",
17
18
  "tests/runs-trace.test.ts",
@@ -1,104 +1,126 @@
1
1
  ---
2
2
  name: actors
3
- description: Required practical guide for non-trivial pi-actors use and Run-kernel work. Read before using or changing spawn, message, inspect, Runs, tools, Recipes, command templates, Control, Trace, artifacts, or lifecycle mechanics.
3
+ description: Use for any non-trivial pi-actors operation, diagnosis, or development involving Recipes, persistent tools, Runs, spawn, message, inspect, Trace, Control, capability specialization, or activation.
4
4
  ---
5
5
 
6
- # Actors (pi-actors)
6
+ # Actors
7
7
 
8
- `pi-actors` treats any runnable local capability—a script, tool, service, pipeline, or subagent—as an actor. A Recipe is its reusable executable definition; a Run is one concrete actor instance:
8
+ ## Choose the operation
9
+
10
+ Start from the intended outcome:
9
11
 
10
12
  ```text
11
- Recipe --spawn--> Run
12
- Run = Recipe + Trace + Control
13
- ```
13
+ Run a maintained capability once
14
+ → use spawn recipe=<skill>/<recipe>
14
15
 
15
- Use the swarm skill separately for decomposition, quorum design, reviewer lenses, and consensus methodology.
16
+ Make a maintained capability a persistent agent-callable tool
17
+ → use register_tool from=<skill>/<recipe>
16
18
 
17
- ## Public Verbs
19
+ Keep the same capability but narrow caller defaults
20
+ → use register_tool from=<skill>/<recipe> defaults={...}
18
21
 
19
- - `spawn`: create one Run from a Recipe or inline command template.
20
- - `message`: send one actor-local Control to `run:<id>`, or the reserved review actions to `runtime`.
21
- - `inspect`: inspect `run:<id>`, `runtime`, `recipes`, or `tool:<name>`.
22
- - `register_tool`: persist a trusted capability; it does not address a running actor. Trust current-session callability only when its result says `callable_now: true`.
22
+ Register a trusted command directly
23
+ → use register_tool template="..."
23
24
 
24
- A Run target exposes exactly three inspect views: `recipe`, `trace`, and `control`. Spawning a user Recipe is still a Recipe launch (`launch_kind: "spawn"`), not evidence that its registered tool was exposed or invoked; registered-tool execution reports `launch_kind: "tool"`, and `inspect target=tool:<name> view=status` separates both usage counts.
25
+ Build a reusable multi-node execution graph
26
+ → author a Recipe with named imports
25
27
 
26
- ## Recipe
28
+ Run a long-lived controlled process
29
+ → spawn its async Recipe, then use message and inspect
27
30
 
28
- A Recipe defines execution. It may declare named typed args, inline fallbacks, configuration `defaults`, composition `values`, imports, artifacts, command-template flags, and `control: ["action"]` when a long-lived service actually consumes actor-local inputs.
31
+ Coordinate several independent actors or subagents
32
+ → also read the swarm Skill
29
33
 
30
- Do not declare Control for ordinary one-shot work. Runtime lifecycle actions such as `kill` stay runtime-owned and must not appear in Recipe Control declarations. Imported Recipes act as local definitions inside one Run; they do not create nested Runs unless execution explicitly spawns them.
34
+ Choose capability-specific behavior
35
+ → read the owning capability Skill
31
36
 
32
- Prefer maintained Skill-owned Recipes over ad hoc wrappers. Skill Recipe identity is `<active Skill name>/<Recipe filename stem>`; Recipe files have no top-level `name`. `SKILL.md` `name` is Pi host metadata matching its directory, not an additional pi-actors identity field. Use `<skill>/<recipe>` for an exact direct component or an explicit `.json` / `.md` path. Entry paths resolve from invocation `cwd`; relative imports resolve from the importing Recipe directory. File-backed Recipes own `{recipe_dir}` and Skill Recipes own `{skill_dir}`; callers never pass or override these origins. Skill Recipes are components, not automatic tools. A rejected component makes catalog inspection partial without blocking exact resolution of unrelated valid components in the same live session context. Keep model, thinking, mission, concurrency, quorum, and timeout choices caller-owned unless a Recipe documents a stable policy.
37
+ Diagnose resolution, registration, or activation
38
+ → use Inspect status/doctor flows and stop on contradictory evidence
39
+ ```
33
40
 
34
- ## Trace
41
+ Use [persistent tools](./references/persistent-tools.md), [Recipes](./references/recipes.md), [Runs](./references/runs.md), or [diagnostics](./references/diagnostics.md) only when the selected operation needs that detail.
35
42
 
36
- Trace records bounded structured observations in `trace.jsonl`:
43
+ ## Core distinctions
37
44
 
38
- ```json
39
- {
40
- "id": "…",
41
- "ts": "…",
42
- "kind": "progress.update",
43
- "summary": "…",
44
- "data": {},
45
- "level": "info",
46
- "attention": "notify"
47
- }
45
+ Keep these boundaries explicit:
46
+
47
+ ```text
48
+ Skill Recipe ≠ registered tool
49
+ spawn ≠ registered-tool invocation
50
+ persisted ≠ callable
51
+ direct delegation ≠ named import composition
52
+ Run Control ≠ actor chat
48
53
  ```
49
54
 
50
- Trace never carries sender, recipient, route, reply, or message-envelope fields. It is a bounded retained suffix: the canonical lock appends within 2,048 events and 4 MiB or atomically keeps the newest suffix plus one warning-only `runtime.trace_compacted` marker. That marker means older history was discarded; terminal/result/execution/artifact evidence stays independently authoritative. `inspect view=trace` reports completeness. Equal timestamps use same-source physical order, fixed source rank, then stable id without exposing an ordinal or claiming cross-source causality. Attention is a wake hint, not a queue: persist durable state or an artifact first, use `notify` for visible status, and reserve `followup` for needed coordinator context. Compaction may discard old hints.
55
+ A Skill Recipe is a maintained component addressed by `<skill>/<recipe>`. `spawn` creates a Run from a Recipe. `register_tool` creates or updates a persistent user tool. A tool is callable in the current session only when activation evidence says `callable_now: true`.
51
56
 
52
- ## Control
57
+ `actors` owns generic Recipe/tool/Run mechanics. The owning capability Skill owns capability-specific selection and constraints. `swarm` owns multi-actor decomposition and integration methodology.
53
58
 
54
- The public Control request is exact:
59
+ ## Persistent capability workflow
55
60
 
56
- ```json
57
- { "target": "run:<id>", "action": "pause", "input": {}, "verbose": false }
61
+ To make `media/player` callable as `music_player` with a default source:
62
+
63
+ ```text
64
+ register_tool
65
+ name=music_player
66
+ from=media/player
67
+ defaults={"source":"~/Music/1MIX"}
58
68
  ```
59
69
 
60
- Valid Controls persist in `controls.jsonl` before delivery; invalid envelopes remain outside it. One token-owned lock rejects a 65th pending Control or 1 MiB rewrite before admission, fails closed on malformed or stale-generation evidence, and atomically admits one queued record. Exact-id claims/finalization preserve a 128-terminal tail, expected-state fencing, and 4 KiB errors. Admitted nonterminal Controls never expire automatically. `inspect view=control` reports capacity, saturation, stale work, bytes, and diagnostics. Endpoints carry immutable startup `run_instance_id`; FIFO and named pipe share limits of 64 action characters, 380 serialized input bytes, and 512 newline-terminated wire bytes. Partial writes fail. Put larger data in an artifact and send only its reference. Delivery revalidates owner, generation, state, and process identity.
70
+ Then:
61
71
 
62
- `kill` remains the runtime recovery path for a stuck saturated Run: it bypasses actor-local Control capacity and adds no synthetic Control. Use actor-local `stop` only when declared and implemented. Restart clears generation-local evidence; archive preserves the bounded terminal tree, while prune preserves only requested artifacts.
72
+ 1. Require registration to report successful resolution, validation, persistence, registry admission, host registration, activation, and `callable_now: true`.
73
+ 2. Call the actual `music_player` tool. Do not call `spawn` and describe that as tool invocation.
74
+ 3. Verify agent-facing evidence reports `launch_kind: "tool"`; use `inspect target=tool:music_player view=status` when usage or activation needs confirmation.
75
+ 4. If callability is false, stop at the reported activation boundary. Preserve the logical source and diagnose it; do not substitute a Recipe spawn as proof.
63
76
 
64
- ## Run State and Safety
77
+ Use direct delegation for the same maintained capability under a persistent name or narrower defaults. Use named imports only when one Recipe graph contains reusable child nodes. See [persistent tools](./references/persistent-tools.md) and [Recipes](./references/recipes.md).
65
78
 
66
- Run state lives under `~/.pi/agent/tmp/pi-actors/runs/<run>/`. Important evidence includes:
79
+ ## Local coordinator topology
67
80
 
68
- - `run.json`: captured Run identity, Recipe, owner, generation, process identity, and policy.
69
- - `trace.jsonl`: structured observations.
70
- - `controls.jsonl`: durable actor-local inputs and outcomes.
71
- - `control-endpoint.json`: generation-fenced service readiness.
72
- - `execution.json`: command/session provenance and bounded complete-capture references.
73
- - `result.json`, logs, and declared artifacts.
81
+ There are two distinct multi-instance shapes:
74
82
 
75
- Trace/Control quotas do not constrain user-declared artifacts, repositories, media sources, complete captures, or actor-owned workload state. No public noun, tool, target, or view is added by bounded retention.
83
+ - A gateway-centric system owns ingress, agent-instance creation, routing, and lifecycle outside the agents.
84
+ - A host-coordinator system keeps the current Pi instance as the control plane; companion extensions such as Telegram provide presence, while pi-actors creates explicit local Runs for delegated work.
85
+
86
+ In host-coordinator mode, the top-level agent receives declarative outcomes, preserves user authority and global context, delegates bounded concrete execution, and owns integration plus final validation. It is not merely another worker after delegation begins. One bounded implementation worker normally runs with reasoning off; consequential output receives a separate reasoning-enabled review. Several independent participants or reviewers additionally use `swarm`.
87
+
88
+ Delegation is not mandatory for every prompt. Work inline when one short bounded act has one natural validation boundary and spawning would add more coordination than isolation, latency hiding, clean context, or continued coordinator availability can repay. For admitted delegation, prefer terminal follow-up and durable Trace/artifacts; inspect on meaningful attention, operator request, or an evidence-based overdue timer rather than busy polling.
89
+
90
+ ## Run workflow
91
+
92
+ A Run is one concrete execution of a Recipe:
93
+
94
+ ```text
95
+ Recipe --spawn--> Run
96
+ Run = Recipe + Trace + Control
97
+ ```
76
98
 
77
- Never bypass owner filtering, immutable generation fencing, process-identity verification, path containment, redaction, terminal reconciliation, or shutdown kill behavior. Do not edit active Run state to force a result.
99
+ 1. Spawn with the exact logical Recipe identity and caller-owned values.
100
+ 2. Retain the returned `run:<id>` and normally wait for terminal follow-up instead of polling.
101
+ 3. Inspect `view=trace` when retained observations or attention matter.
102
+ 4. Inspect `view=control` before diagnosing service readiness, stale work, or saturation.
103
+ 5. Send `message` only for an action declared and consumed by that controlled Recipe.
104
+ 6. Use terminal state, result, declared artifacts, and execution evidence to prove completion.
78
105
 
79
- ## Operating Pattern
106
+ A Run exposes only `recipe`, `trace`, and `control` views. Control is bounded actor-local input, not peer messaging or chat. See [Runs](./references/runs.md).
80
107
 
81
- 1. Inspect the Recipe before launch when its contract or policy matters.
82
- 2. Spawn with explicit values and retain the returned `run:<id>`.
83
- 3. Let short Runs finish; avoid polling.
84
- 4. Inspect Trace when evidence or attention requires it; its summary states whether retained history is complete.
85
- 5. Inspect Control capacity before diagnosing stale work or saturation, then send only declared actor-local Controls.
86
- 6. Use runtime kill/cancel behavior for lifecycle termination.
87
- 7. Inspect artifacts and execution evidence for final validation.
108
+ ## Diagnosis and stop rules
88
109
 
89
- If work may outlive the current turn, needs steering, produces artifacts, fans out, or must remain inspectable, use a Run rather than shell backgrounding.
110
+ When a pi-actors operation fails:
90
111
 
91
- ## Top Recipes
112
+ 1. Keep the intended logical Recipe or tool identity.
113
+ 2. Inspect the existing `recipes`, `tool:<name>`, `runtime`, or `run:<id>` surface that owns the failure.
114
+ 3. Report resolver, registry, activation, Run, Trace, or Control truth exactly.
115
+ 4. Retry only after the owning state is healthy.
92
116
 
93
- - [Repository health](../project-work/recipes/repo-health.json)
94
- - [Quorum review](../swarm/recipes/quorum-review.json)
95
- - [Artifact bundle](../artifacts/recipes/bundle.json)
96
- - [Music player service](../media/recipes/player.json)
97
- - [Resource locker service](./recipes/resource-locker.json)
117
+ Stop if spawn and registry resolve the same Recipe differently. Stop if registration persists but is not callable. Stop if an operation cannot be proven through pi-actors surfaces.
98
118
 
99
- ## Deep References
119
+ Never recover by copying maintained Recipe args, defaults, Control, artifacts, or helper commands. Never hard-code a `{skill_dir}` replacement path. Never introduce `bash -lc`, `eval`, direct bundled-helper execution, or shell backgrounding to bypass resolution. Never call `spawn` and claim a tool call. Use [diagnostics](./references/diagnostics.md) for the safe next action.
100
120
 
101
- - [Recipe library](../../docs/recipe-library.md)
102
- - [Async Runs](../../docs/async-runs.md)
121
+ ## When to read another Skill
103
122
 
104
- Read repository source and tests for exact contracts when changing pi-actors itself. Update this skill whenever durable Run mechanics change.
123
+ - Read the owning capability Skill when choosing or operating that capability pack.
124
+ - Read `swarm` in addition to `actors` for multiple actors/subagents, parallel scopes, reviewer lenses, quorum, conflict handling, or integration.
125
+ - For generic mechanics, this Skill outranks capability Skills and `swarm`. Report a stale Skill if it contradicts Recipe identity, registration, activation, spawn, Inspect, Trace, or Control semantics here.
126
+ - When changing the extension implementation itself, apply project implementation instructions after this operating protocol.
@@ -0,0 +1,44 @@
1
+ # Diagnostics
2
+
3
+ Preserve the intended logical identity and diagnose through public pi-actors surfaces. Do not inspect raw registry files or implementation source as the normal first response.
4
+
5
+ ## Recipe resolution or catalog failure
6
+
7
+ ```text
8
+ inspect target=recipes view=status
9
+ inspect target=recipes view=doctor identity=<skill>/<recipe>
10
+ ```
11
+
12
+ Use the focused doctor form for one intended identity. It reports active-Skill ownership, exact resolvability, partial-catalog state, component status, portable source location, resolution generation, any rejection, and bounded next actions. Use the unfiltered doctor only for catalog-wide diagnosis. A partial catalog does not imply every exact component is unavailable. If the owning Skill is inactive, report that blocker rather than locating and running its helper manually.
13
+
14
+ ## Persistent tool failure
15
+
16
+ ```text
17
+ inspect target=tool:<name> view=status
18
+ inspect target=tool:<name> view=schema
19
+ ```
20
+
21
+ Distinguish persistence, registry admission, host registration, active-tool membership, and `callable_now`. Confirm the source identity, effective caller schema, separate tool/spawn usage, and last launch kind when exposed.
22
+
23
+ If persistence succeeded but callability is false, do not use `spawn` and claim the tool worked. Follow the reported activation boundary or stop.
24
+
25
+ ## Run failure
26
+
27
+ ```text
28
+ inspect target=run:<id> view=recipe
29
+ inspect target=run:<id> view=trace
30
+ inspect target=run:<id> view=control
31
+ inspect target=runtime view=status
32
+ ```
33
+
34
+ Use Recipe view for captured identity/launch evidence, Trace for bounded observations, Control for readiness/capacity/stale work, and runtime status for kernel-level health. Treat retained-history completeness honestly.
35
+
36
+ ## Safe failure protocol
37
+
38
+ 1. Keep the exact intended Recipe/tool/Run identity.
39
+ 2. Identify the owning public surface.
40
+ 3. Record exact resolver, registry, activation, or Run truth.
41
+ 4. Apply only the bounded next action returned by that owner.
42
+ 5. Retry only after the owning state is healthy.
43
+
44
+ Stop if evidence remains contradictory or the requested operation cannot be proven. Never recover by copying maintained contracts, hard-coding installation paths, directly executing bundled helpers, adding `bash -lc` or `eval`, shell-backgrounding work, editing unrelated Skills, or relabeling a Recipe spawn as a tool call.