@llblab/pi-actors 0.32.0 → 0.33.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
@@ -20,7 +20,7 @@ Pi host
20
20
  -> index.ts composition root
21
21
  -> lib/tools.ts / prompts.ts public tool + injected prompt surface
22
22
  -> lib/runtime.ts / registry.ts active user recipe tools
23
- -> lib/recipe-*.ts packaged/user/candidate recipe discovery
23
+ -> lib/recipe-*.ts packaged/user/draft recipe discovery
24
24
  -> lib/async-runs.ts spawn lifecycle and run state
25
25
  -> lib/actor-rooms.ts room, roster, mailbox, communication log
26
26
  -> scripts/*.mjs thin process entrypoints
@@ -45,8 +45,8 @@ Pi host
45
45
 
46
46
  ## Repo Surfaces
47
47
 
48
- - `/scripts/*.mjs`: Stable executable shims for detached/helper processes.
49
- - `/lib/*.ts`: Compiled domain and script-entrypoint logic. Keep `scripts/*.mjs` lightweight and move substantive behavior into named domain modules so `dist/lib` is the JS-only runtime surface. This intentionally grows a standard library: script-born behavior should gain a clear domain name when reuse is plausible. Exception: self-contained application/build scripts with no expected second consumer, such as `music-player.mjs` or `build-dist.mjs`, may remain standalone `.mjs` files.
48
+ - `/scripts/*.mjs`: Stable executables for detached/helper processes. Keep script-only release/build/glue behavior standalone when it has no plausible second consumer.
49
+ - `/lib/*.ts`: Compiled domains and shared script-entrypoint logic. Move substantive script behavior into named domain modules only when reuse, tests, packaged JS-only execution, or clear ownership justifies it. Do not create a lib domain solely to keep a tiny script shim thin.
50
50
  - `/recipes/*.json`: Packaged standard recipe library. Keep recipes optional, composable, policy-light, and caller-configurable.
51
51
  - `/skills/actors/SKILL.md`: Dense practical reference for operating pi-actors itself.
52
52
  - `/skills/swarm/SKILL.md`: Bundled methodology skill for multi-agent standards, strategies, and portable examples.
@@ -62,6 +62,7 @@ Pi host
62
62
  - Keep published documentation portable: use `~`, `<repo>`, or relative paths instead of machine-local absolute paths.
63
63
  - Preserve runtime output discipline because tool output flows directly into agent context.
64
64
  - Optimize every actor-facing surface for signal over volume: prefer compact state-backed hints and fewer concepts over broad explanatory prose or speculative guidance.
65
+ - Until a stable release greater than `1.x.x`, favor context compression over compatibility shims: do not preserve legacy actor-facing names, aliases, fields, env vars, paths, or docs solely for backward compatibility when a clearer current term exists. Remove compatibility layers in the same slice that renames a concept, and record the break in `CHANGELOG.md`.
65
66
  - Keep the project lens local-first and cybernetic: agents wrap durable local capabilities as actors, then use semantic tools and messages instead of repeatedly reconstructing shell commands.
66
67
  - Design recipes as agent-callable tools: make prompts, scopes, paths, models, and policy knobs public args/defaults when the caller should decide them at invocation time.
67
68
  - Decompose oversized bullets into sublists or hierarchy; long flat list items are a context-smell.
package/BACKLOG.md CHANGED
@@ -67,20 +67,6 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
67
67
  - Stale claims are reproducible and visible in worker status.
68
68
  - Tests cover stale-claim counting without adding scheduler/broker policy.
69
69
 
70
- ### M-23 Tool Boundary Type Tightening
71
-
72
- - Priority: Low.
73
- - Status: Planned.
74
- - Goal: Remove avoidable `any` at the Pi/tool boundary where a narrow local type can express the real contract without broad rewiring.
75
- - Why now: `index.ts` still keeps runtime tool definitions in a `Map<string, any>`; this is small but visible in the composition root.
76
- - Direction:
77
- - Add or reuse a narrow exported tool-definition type from the Pi adapter or tools domain.
78
- - Keep SDK details behind `lib/pi.ts`.
79
- - Do not introduce a broad type-modeling pass across every schema helper.
80
- - Acceptance:
81
- - `index.ts` no longer uses `Map<string, any>` for actor tool definitions.
82
- - TypeScript validation still passes without weakening public tool schemas.
83
-
84
70
  ### M-17 Message Delivery Outcome Contract
85
71
 
86
72
  - Priority: High.
@@ -103,7 +89,7 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
103
89
  - Priority: High.
104
90
  - Status: Planned.
105
91
  - Goal: Make successful ad hoc actor patterns easy to promote manually from draft memory into active user recipe memory.
106
- - Why now: Draft recipes under `~/.pi/agent/recipes/candidates` are replayable but intentionally not active tools; the directory name is retained for compatibility, and the two-stage memory model now needs an explicit operator-gated promotion path.
92
+ - Why now: Draft recipes under `~/.pi/agent/recipes/drafts` are replayable but intentionally not active tools, and the two-stage memory model needs an explicit operator-gated promotion path.
107
93
  - Direction:
108
94
  - List draft recipes with source run, timestamp, fingerprint, description/template preview, and validation status.
109
95
  - Promote a selected draft to `~/.pi/agent/recipes/<name>.json` only through an explicit action or explicit tool argument.
@@ -116,20 +102,6 @@ The backlog is intentionally pruned to the 20% of work most likely to deliver 80
116
102
  - Tests cover valid promotion, invalid draft, name collision, and packaged-recipe shadowing.
117
103
  - Docs explain draft memory vs active tool memory in one compact section.
118
104
 
119
- ### M-24 Registry Path Naming Cleanup
120
-
121
- - Priority: Low.
122
- - Status: Planned.
123
- - Goal: Reduce legacy-storage naming noise without changing the persistent file path.
124
- - Why now: `legacy-tool-registry.json` is still a compatibility storage path, but helper names and tests should make clear that the stable path is retained intentionally.
125
- - Direction:
126
- - Prefer neutral helper/test wording such as registry path or retained registry storage path.
127
- - Keep the on-disk filename unchanged unless a separate migration is justified.
128
- - Do not reintroduce legacy migration code.
129
- - Acceptance:
130
- - Path helpers and tests no longer imply an unfinished migration.
131
- - Existing registry storage compatibility remains unchanged.
132
-
133
105
  ### M-19 Recipe Doctor Risk Labels v2
134
106
 
135
107
  - Priority: Medium.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.33.0: Signal-First Compatibility Pruning
6
+
7
+ - `[Breaking]` Removed pre-1.x compatibility shims for draft recipe terminology: draft captures now live under `~/.pi/agent/recipes/drafts`, recipe inspection no longer emits `candidates`, spawn details no longer emit `candidate_recipe`, and shadow diagnostics use `blocked_fallback` instead of `blocked_candidate`.
8
+ - `[Context]` Documented the pre-stable policy that context compression wins over compatibility aliases until after a stable release greater than `1.x.x`; rename slices should remove legacy actor-facing names, fields, env vars, paths, and docs instead of carrying shims.
9
+ - `[Breaking]` Renamed retained registry storage from `legacy-tool-registry.json` to `tool-registry.json`, removing the stale compatibility noun from runtime path helpers and tests.
10
+ - `[Types]` Added a narrow actor tool-definition type and removed the remaining `Map<string, any>` from the composition root without expanding SDK type exposure.
11
+ - `[Scripts]` Collapsed the conformance runner back into a standalone script and removed its one-off lib domain, documenting that script-only glue should stay self-contained unless reuse or packaged runtime constraints justify a domain.
12
+
5
13
  ## 0.32.0: Actor Surface Minimization
6
14
 
7
15
  - `[Context]` Added durable signal/noise guidance for actor-facing surfaces: keep the model-facing concept ladder minimal, make feedback hints state-backed and action-shaped, and avoid speculative advisory prose.
@@ -17,5 +17,5 @@ export declare const EXTENSION_RUNTIME_PATHS: ExtensionRuntimePaths;
17
17
  export declare function getExtensionSkillsDir(extensionUrl: string): string;
18
18
  export declare function getExistingExtensionSkillPaths(extensionUrl: string): string[];
19
19
  export declare function getRecipeRoot(agentDir?: string): string;
20
- export declare function getRecipeCandidateRoot(agentDir?: string): string;
20
+ export declare function getRecipeDraftRoot(agentDir?: string): string;
21
21
  export declare function getPackagedRecipeRoot(): string;
package/dist/lib/paths.js CHANGED
@@ -13,7 +13,7 @@ export function getAgentDir(env = process.env) {
13
13
  : join(homedir(), ".pi", "agent");
14
14
  }
15
15
  export function getConfigPath(agentDir = getAgentDir()) {
16
- return join(agentDir, "legacy-tool-registry.json");
16
+ return join(agentDir, "tool-registry.json");
17
17
  }
18
18
  export function getExtensionTmpDir(agentDir = getAgentDir(), extensionName = "pi-actors") {
19
19
  return join(agentDir, "tmp", extensionName);
@@ -39,8 +39,8 @@ export function getExistingExtensionSkillPaths(extensionUrl) {
39
39
  export function getRecipeRoot(agentDir = getAgentDir()) {
40
40
  return join(agentDir, "recipes");
41
41
  }
42
- export function getRecipeCandidateRoot(agentDir = getAgentDir()) {
43
- return join(getRecipeRoot(agentDir), "candidates");
42
+ export function getRecipeDraftRoot(agentDir = getAgentDir()) {
43
+ return join(getRecipeRoot(agentDir), "drafts");
44
44
  }
45
45
  export function getPackagedRecipeRoot() {
46
46
  const here = dirname(fileURLToPath(import.meta.url));
@@ -47,6 +47,6 @@ export declare function discoverRecipeSources(sources: RecipeDiscoverySource[]):
47
47
  export declare function discoverRecipes(roots: string[]): RecipeDiscoveryResult;
48
48
  export declare function createRecipeIntegrityManifest(result: RecipeDiscoveryResult): RecipeIntegrityManifestEntry[];
49
49
  export declare function getShadowedLaunchDiagnostic(result: RecipeDiscoveryResult, id: string): Record<string, unknown> | undefined;
50
- export declare function listCandidateRecipes(root: string): Array<Record<string, unknown>>;
50
+ export declare function listDraftRecipes(root: string): Array<Record<string, unknown>>;
51
51
  export declare function summarizeDiscovery(result: RecipeDiscoveryResult): Record<string, unknown>;
52
52
  export declare function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | undefined;
@@ -324,16 +324,16 @@ function diagnosticDetails(result) {
324
324
  }
325
325
  function remediationForEntry(entry, activePath) {
326
326
  const riskyDiagnostics = entry.diagnostics.filter((message) => diagnosticSeverity(message) === "warning");
327
- const blockedCandidate = entry.shadows[0];
327
+ const blockedFallback = entry.shadows[0];
328
328
  if (entry.invalid) {
329
329
  return {
330
330
  id: entry.id,
331
- kind: blockedCandidate ? "blocking_invalid" : "invalid",
331
+ kind: blockedFallback ? "blocking_invalid" : "invalid",
332
332
  severity: "error",
333
333
  path: entry.path,
334
- ...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
335
- reason: blockedCandidate
336
- ? "invalid higher-priority recipe blocks a lower-priority candidate"
334
+ ...(blockedFallback ? { blocked_fallback: blockedFallback } : {}),
335
+ reason: blockedFallback
336
+ ? "invalid higher-priority recipe blocks a lower-priority fallback"
337
337
  : "recipe is invalid and cannot be exposed as a tool",
338
338
  action: "fix recipe syntax/config, or disable/delete/archive it to restore fallback",
339
339
  };
@@ -341,12 +341,12 @@ function remediationForEntry(entry, activePath) {
341
341
  if (entry.disabled && entry.active) {
342
342
  return {
343
343
  id: entry.id,
344
- kind: blockedCandidate ? "blocking_disabled" : "disabled",
344
+ kind: blockedFallback ? "blocking_disabled" : "disabled",
345
345
  severity: "warning",
346
346
  path: entry.path,
347
- ...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
348
- reason: blockedCandidate
349
- ? "disabled higher-priority recipe intentionally blocks a lower-priority candidate"
347
+ ...(blockedFallback ? { blocked_fallback: blockedFallback } : {}),
348
+ reason: blockedFallback
349
+ ? "disabled higher-priority recipe intentionally blocks a lower-priority fallback"
350
350
  : "recipe is disabled and not exposed as a tool",
351
351
  action: "keep disabled intentionally, re-enable, or delete/archive the file",
352
352
  };
@@ -420,12 +420,12 @@ export function getShadowedLaunchDiagnostic(result, id) {
420
420
  return undefined;
421
421
  return {
422
422
  active_path: active.path,
423
- blocked_candidate: active.shadows[0],
423
+ blocked_fallback: active.shadows[0],
424
424
  hint: "inspect_recipes_doctor",
425
425
  reason: active.invalid ? "shadowed_invalid" : "shadowed_disabled",
426
426
  };
427
427
  }
428
- export function listCandidateRecipes(root) {
428
+ export function listDraftRecipes(root) {
429
429
  return listRecipeFiles(root).map((path) => {
430
430
  const id = RecipeReferences.getRecipeIdFromPath(path);
431
431
  const config = RecipeReferences.readRawRecipeConfig(path);
@@ -8,6 +8,11 @@ import * as Execution from "./execution.ts";
8
8
  import * as Registry from "./registry.ts";
9
9
  export type RegisterToolInput = Registry.RegisterToolInput;
10
10
  export type RegisterToolRuntimeDeps<TContext> = Registry.RegisterToolRuntimeDeps<TContext>;
11
+ export interface ActorToolDefinition {
12
+ name: string;
13
+ execute?: (...args: never[]) => unknown;
14
+ [key: string]: unknown;
15
+ }
11
16
  export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContext> {
12
17
  configPath: string;
13
18
  getActiveTools: () => string[];
@@ -43,6 +48,6 @@ export interface ActorMessageToolDeps<TContext = unknown> {
43
48
  getTool?: (name: string) => any | undefined;
44
49
  }
45
50
  export declare function createActorMessageToolDefinition<TContext = unknown>(deps?: ActorMessageToolDeps<TContext>): any;
46
- export declare function createCoreActorToolDefinitions<TContext extends AsyncRunToolContext>(deps: CoreActorToolDefinitionDeps<TContext>): any[];
51
+ export declare function createCoreActorToolDefinitions<TContext extends AsyncRunToolContext>(deps: CoreActorToolDefinitionDeps<TContext>): ActorToolDefinition[];
47
52
  export declare function createRuntimeToolDefinition(cfg: RegisteredTool, exec: Execution.RegisteredToolExec): any;
48
53
  export {};
package/dist/lib/tools.js CHANGED
@@ -168,7 +168,7 @@ function compactAsyncRunStatus(value) {
168
168
  tokens.push(`code=${String(result.code)}`);
169
169
  if (result.killed === true)
170
170
  tokens.push("killed=true");
171
- const draftRecipe = status.draft_recipe ?? status.candidate_recipe;
171
+ const draftRecipe = status.draft_recipe;
172
172
  if (draftRecipe)
173
173
  tokens.push(`draft_recipe=${String(draftRecipe)}`);
174
174
  const nextActions = actorRunNextActions(run);
@@ -504,8 +504,8 @@ function compactRecipeDoctor(summary) {
504
504
  }
505
505
  for (const item of remediations.slice(0, 8)) {
506
506
  const action = compactPreview(item.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
507
- const blocked = item.blocked_candidate
508
- ? ` blocked=${compactPreview(item.blocked_candidate, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
507
+ const blocked = item.blocked_fallback
508
+ ? ` blocked=${compactPreview(item.blocked_fallback, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
509
509
  : "";
510
510
  lines.push(`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`);
511
511
  }
@@ -557,11 +557,7 @@ function compactRecipeRegistry(summary) {
557
557
  const diagnostics = Array.isArray(summary.diagnostics)
558
558
  ? summary.diagnostics.length
559
559
  : 0;
560
- const drafts = Array.isArray(summary.drafts)
561
- ? summary.drafts.length
562
- : Array.isArray(summary.candidates)
563
- ? summary.candidates.length
564
- : 0;
560
+ const drafts = Array.isArray(summary.drafts) ? summary.drafts.length : 0;
565
561
  const recommendations = Array.isArray(summary.recommendations)
566
562
  ? summary.recommendations.length
567
563
  : 0;
@@ -702,10 +698,10 @@ function shadowedRecipeLaunchDiagnostic(recipe) {
702
698
  ]);
703
699
  return RecipeDiscovery.getShadowedLaunchDiagnostic(discovery, recipe);
704
700
  }
705
- function candidateRecipeName(run) {
701
+ function draftRecipeName(run) {
706
702
  return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
707
703
  }
708
- function candidateRecipeDefaults(values) {
704
+ function draftRecipeDefaults(values) {
709
705
  const ignored = new Set([
710
706
  "actor_address",
711
707
  "communication_file",
@@ -716,18 +712,18 @@ function candidateRecipeDefaults(values) {
716
712
  const defaults = Object.fromEntries(Object.entries(values).filter(([key]) => !ignored.has(key)));
717
713
  return Object.keys(defaults).length > 0 ? defaults : undefined;
718
714
  }
719
- function writeSpawnCandidateRecipe(input, meta) {
715
+ function writeSpawnDraftRecipe(input, meta) {
720
716
  if (process.env.NODE_TEST_CONTEXT &&
721
- process.env.PI_ACTORS_ENABLE_SPAWN_CANDIDATES_IN_TEST !== "1")
717
+ process.env.PI_ACTORS_ENABLE_SPAWN_DRAFTS_IN_TEST !== "1")
722
718
  return undefined;
723
719
  if (input.template === undefined ||
724
720
  input.file !== undefined ||
725
721
  input.recipe !== undefined)
726
722
  return undefined;
727
- const root = Paths.getRecipeCandidateRoot();
723
+ const root = Paths.getRecipeDraftRoot();
728
724
  mkdirSync(root, { recursive: true });
729
- const path = join(root, candidateRecipeName(String(meta.run)));
730
- const defaults = candidateRecipeDefaults(meta.values);
725
+ const path = join(root, draftRecipeName(String(meta.run)));
726
+ const defaults = draftRecipeDefaults(meta.values);
731
727
  const recipe = {
732
728
  async: true,
733
729
  description: `Draft recipe captured from spawn run ${String(meta.run)}`,
@@ -743,7 +739,7 @@ function enhanceSpawnRecipeError(error, recipe) {
743
739
  if (!diagnostic)
744
740
  return error instanceof Error ? error : new Error(String(error));
745
741
  const original = error instanceof Error ? error.message : String(error);
746
- return Object.assign(new Error(`${original} reason=${diagnostic.reason} active_path=${diagnostic.active_path} blocked_candidate=${diagnostic.blocked_candidate} hint=${diagnostic.hint}`), {
742
+ return Object.assign(new Error(`${original} reason=${diagnostic.reason} active_path=${diagnostic.active_path} blocked_fallback=${diagnostic.blocked_fallback} hint=${diagnostic.hint}`), {
747
743
  ...diagnostic,
748
744
  original_error: original,
749
745
  });
@@ -864,13 +860,11 @@ export function createSpawnToolDefinition() {
864
860
  catch (error) {
865
861
  throw enhanceSpawnRecipeError(error, recipe);
866
862
  }
867
- const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
863
+ const draftRecipe = writeSpawnDraftRecipe(input, meta);
868
864
  const nextActions = actorRunNextActions(meta.run);
869
865
  const details = {
870
866
  ...meta,
871
- ...(candidateRecipe
872
- ? { candidate_recipe: candidateRecipe, draft_recipe: candidateRecipe }
873
- : {}),
867
+ ...(draftRecipe ? { draft_recipe: draftRecipe } : {}),
874
868
  next_actions: nextActions,
875
869
  };
876
870
  ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
@@ -962,8 +956,7 @@ export function createInspectToolDefinition(deps = {}) {
962
956
  const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
963
957
  const summaryBase = {
964
958
  ...RecipeDiscovery.summarizeDiscovery(discovered),
965
- drafts: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
966
- candidates: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
959
+ drafts: RecipeDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
967
960
  };
968
961
  const summary = {
969
962
  ...summaryBase,
@@ -1,33 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Internal conformance runner shim.
4
+ * Internal conformance runner.
5
5
  *
6
- * Runtime logic lives in lib/conformance.ts and is compiled to
7
- * dist/lib/conformance.js for installed JS-only packages.
6
+ * This script is intentionally standalone package/release glue rather than a
7
+ * lib domain: it only selects regression suites and formats their summary.
8
8
  */
9
9
 
10
- import { existsSync } from "node:fs";
11
- import { dirname, join } from "node:path";
12
- import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { spawnSync } from "node:child_process";
11
+ import { dirname } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ const conformanceSuites = [
15
+ "tests/protocol-examples.test.ts",
16
+ "tests/recipe-discovery.test.ts",
17
+ "tests/registry.test.ts",
18
+ "tests/runtime-registry.test.ts",
19
+ "tests/async-runs.test.ts",
20
+ "tests/actor-rooms.test.ts",
21
+ "tests/tools.test.ts",
22
+ ];
13
23
 
14
24
  function packageRoot() {
15
25
  return dirname(dirname(fileURLToPath(import.meta.url)));
16
26
  }
17
27
 
18
- function mainModulePath() {
19
- const root = packageRoot();
20
- const compiled = join(root, "dist", "lib", "conformance.js");
21
- return existsSync(compiled) ? compiled : join(root, "lib", "conformance.ts");
22
- }
28
+ const result = spawnSync(
29
+ process.execPath,
30
+ ["--experimental-strip-types", "--test", ...conformanceSuites],
31
+ { cwd: packageRoot(), encoding: "utf8", stdio: "pipe" },
32
+ );
23
33
 
24
- const { runConformance } = await import(pathToFileURL(mainModulePath()).href);
25
- const report = runConformance(packageRoot());
34
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
35
+ const summary = output
36
+ .split("\n")
37
+ .filter((line) =>
38
+ /^ℹ (tests|pass|fail|cancelled|skipped|todo|duration_ms) /.test(line),
39
+ )
40
+ .join("\n");
26
41
 
27
42
  console.log("pi-actors conformance");
28
- console.log(`suites ${report.suites}`);
29
- if (report.summary) console.log(report.summary);
30
- if (report.code !== 0) {
31
- console.error(report.output.trimEnd());
32
- process.exit(report.code);
43
+ console.log(`suites ${conformanceSuites.length}`);
44
+
45
+ if (summary) console.log(summary);
46
+ if ((result.status ?? 1) !== 0) {
47
+ console.error(output.trimEnd());
48
+ process.exit(result.status ?? 1);
33
49
  }
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.32.0
5
+ version: 0.33.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -213,7 +213,7 @@ Only matching filename ids compete. Higher priority shadows lower priority; with
213
213
  Muscle-memory lens: pi-actors has two durable executable-memory layers.
214
214
 
215
215
  1. `~/.pi/agent/recipes/*.json` and `*.md` are the agent's active capability memory. Every recipe in that directory becomes an easy-to-call tool automatically and survives into later sessions. Descriptions matter here because they become the tool's operator-facing title/context.
216
- 2. `~/.pi/agent/recipes/candidates/*.json` is draft memory captured from successful inline `spawn template=...` runs. The directory name is retained for compatibility; treat these as drafts, not active tools. Drafts do not enter the injected tool surface. They remain reusable by explicit path, e.g. `spawn file="~/.pi/agent/recipes/candidates/<name>.json"`, and can be promoted by moving or copying one level up into `~/.pi/agent/recipes`.
216
+ 2. `~/.pi/agent/recipes/drafts/*.json` is draft memory captured from successful inline `spawn template=...` runs. Drafts do not enter the injected tool surface. They remain reusable by explicit path, e.g. `spawn file="~/.pi/agent/recipes/drafts/<name>.json"`, and can be promoted by moving or copying one level up into `~/.pi/agent/recipes`.
217
217
 
218
218
  Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow draft memory by trying ad hoc actors successfully. Treat both as executable habits: drafts are the workbench/proving ground; root recipes are promoted muscle memory.
219
219
 
@@ -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.32.0
5
+ version: 0.33.0
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -31,7 +31,7 @@ Maintain this skill as a living orchestration standard. When real swarm work exp
31
31
  - `Lens`: A deliberately narrow cognitive role assigned to one subagent, such as security, tests, architecture, economics, or operator UX.
32
32
  - `Task Card`: A bounded implementation assignment with goal, allowed files, avoided files, expected output, and validation gates.
33
33
  - `Component Capability`: An abstract adapter operation such as launcher, reviewer, verifier, merger, quorum, checkpoint, follow-up, judge, or normalizer. Swarm may target these capabilities, but local adapters bind them to concrete tools, recipes, draft recipes, command templates, async runs, or services.
34
- - `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn. It can be replayed by explicit file path and later promoted into the active tool recipe root after enough dogfood. Its compatibility storage path may still include `recipes/candidates`.
34
+ - `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn under `~/.pi/agent/recipes/drafts`. It can be replayed by explicit file path and later promoted into the active tool recipe root after enough dogfood.
35
35
  - `Coordinator Checkpoint`: A deliberate subagent pause where the subagent preserves its working context, sends a bounded question or status to the orchestrator, receives a coordinator reply, and continues in the same subagent context.
36
36
  - `Evidence Checkpoint`: A deliberate stop where a subagent records sources, assumptions, confidence, contradictions, or blocking evidence gaps before synthesis.
37
37
  - `Integrator`: The human or agent that merges isolated branches/worktrees into the shared target and owns conflict resolution.
@@ -10,7 +10,7 @@ The registry source is location-discovered recipes, not a live tool-only JSON fi
10
10
 
11
11
  - `~/.pi/agent/recipes/*.json` and `*.md` are the highest-priority user recipe root and the operator-managed tool set.
12
12
  - Recipes in that root are tools by location.
13
- - `~/.pi/agent/recipes/candidates/*.json` stores captured inline-spawn draft recipes, not registered tools. The directory name is retained for compatibility; model-facing output calls them drafts. Promote one by moving or copying it up one level into `~/.pi/agent/recipes`. `inspect target=recipes view=summary` reports their count, and verbose output lists their paths/descriptions for explicit replay by file path.
13
+ - `~/.pi/agent/recipes/drafts/*.json` stores captured inline-spawn draft recipes, not registered tools. Promote one by moving or copying it up one level into `~/.pi/agent/recipes`. `inspect target=recipes view=summary` reports their count, and verbose output lists their paths/descriptions for explicit replay by file path.
14
14
  - Packaged pi-actors recipes are the lower-priority standard library of declarative actor config components, not automatically registered tools.
15
15
  - Ad hoc recipe files outside the user recipe root are components unless explicitly registered/copied into `~/.pi/agent/recipes`.
16
16
  - Recipe identity is the filename basename; `~/.pi/agent/recipes/docs_review.json` and `docs_review.md` both have id/tool name `docs_review`.
@@ -31,9 +31,9 @@ inspect target=recipes view=summary verbose=true
31
31
 
32
32
  `tool:pi-actors` is a reserved runtime-status actor: it reports the loaded package version, package root, source/dist mode, entrypoint path, recipe roots, and git commit when available. Use it after reloads to confirm which extension code is actually live.
33
33
 
34
- The recipe summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled. The doctor view keeps the same registry evidence but promotes an advisory action surface: compact output includes the highest-priority `top` remediation plus ordered actions for invalid/blocking, disabled, risky shell-boundary, and shadowed recipes. Verbose inspection keeps the structured `remediations`, `top_action`, diagnostic details, and blocked lower-priority candidate paths when a broken or disabled higher-priority recipe masks a fallback.
34
+ The recipe summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled. The doctor view keeps the same registry evidence but promotes an advisory action surface: compact output includes the highest-priority `top` remediation plus ordered actions for invalid/blocking, disabled, risky shell-boundary, and shadowed recipes. Verbose inspection keeps the structured `remediations`, `top_action`, diagnostic details, and blocked lower-priority fallback paths when a broken or disabled higher-priority recipe masks a fallback.
35
35
 
36
- Routine shadowing is quiet. If a bare `spawn` recipe launch already fails because an invalid or `disabled: true` user recipe blocks a lower-priority candidate, the launch error adds compact tokens such as `reason=shadowed_invalid` or `reason=shadowed_disabled`, `active_path`, `blocked_candidate`, and `hint=inspect_recipes_doctor`.
36
+ Routine shadowing is quiet. If a bare `spawn` recipe launch already fails because an invalid or `disabled: true` user recipe blocks a lower-priority fallback, the launch error adds compact tokens such as `reason=shadowed_invalid` or `reason=shadowed_disabled`, `active_path`, `blocked_fallback`, and `hint=inspect_recipes_doctor`.
37
37
 
38
38
  ## Registering Tools
39
39
 
package/index.ts CHANGED
@@ -102,7 +102,7 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
102
102
  onChange: () =>
103
103
  activeRunContext && scheduleRunEventUpdate(activeRunContext),
104
104
  });
105
- const actorToolDefinitions = new Map<string, any>();
105
+ const actorToolDefinitions = new Map<string, Tools.ActorToolDefinition>();
106
106
  const runtime = Runtime.createAutoToolsRuntime({
107
107
  configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
108
108
  exec: CommandTemplates.execCommandTemplate,
package/lib/paths.ts CHANGED
@@ -24,7 +24,7 @@ export interface ExtensionRuntimePaths {
24
24
  }
25
25
 
26
26
  export function getConfigPath(agentDir = getAgentDir()): string {
27
- return join(agentDir, "legacy-tool-registry.json");
27
+ return join(agentDir, "tool-registry.json");
28
28
  }
29
29
 
30
30
  export function getExtensionTmpDir(
@@ -63,8 +63,8 @@ export function getRecipeRoot(agentDir = getAgentDir()): string {
63
63
  return join(agentDir, "recipes");
64
64
  }
65
65
 
66
- export function getRecipeCandidateRoot(agentDir = getAgentDir()): string {
67
- return join(getRecipeRoot(agentDir), "candidates");
66
+ export function getRecipeDraftRoot(agentDir = getAgentDir()): string {
67
+ return join(getRecipeRoot(agentDir), "drafts");
68
68
  }
69
69
 
70
70
  export function getPackagedRecipeRoot(): string {
@@ -461,16 +461,16 @@ function remediationForEntry(
461
461
  const riskyDiagnostics = entry.diagnostics.filter(
462
462
  (message) => diagnosticSeverity(message) === "warning",
463
463
  );
464
- const blockedCandidate = entry.shadows[0];
464
+ const blockedFallback = entry.shadows[0];
465
465
  if (entry.invalid) {
466
466
  return {
467
467
  id: entry.id,
468
- kind: blockedCandidate ? "blocking_invalid" : "invalid",
468
+ kind: blockedFallback ? "blocking_invalid" : "invalid",
469
469
  severity: "error",
470
470
  path: entry.path,
471
- ...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
472
- reason: blockedCandidate
473
- ? "invalid higher-priority recipe blocks a lower-priority candidate"
471
+ ...(blockedFallback ? { blocked_fallback: blockedFallback } : {}),
472
+ reason: blockedFallback
473
+ ? "invalid higher-priority recipe blocks a lower-priority fallback"
474
474
  : "recipe is invalid and cannot be exposed as a tool",
475
475
  action:
476
476
  "fix recipe syntax/config, or disable/delete/archive it to restore fallback",
@@ -479,12 +479,12 @@ function remediationForEntry(
479
479
  if (entry.disabled && entry.active) {
480
480
  return {
481
481
  id: entry.id,
482
- kind: blockedCandidate ? "blocking_disabled" : "disabled",
482
+ kind: blockedFallback ? "blocking_disabled" : "disabled",
483
483
  severity: "warning",
484
484
  path: entry.path,
485
- ...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
486
- reason: blockedCandidate
487
- ? "disabled higher-priority recipe intentionally blocks a lower-priority candidate"
485
+ ...(blockedFallback ? { blocked_fallback: blockedFallback } : {}),
486
+ reason: blockedFallback
487
+ ? "disabled higher-priority recipe intentionally blocks a lower-priority fallback"
488
488
  : "recipe is disabled and not exposed as a tool",
489
489
  action:
490
490
  "keep disabled intentionally, re-enable, or delete/archive the file",
@@ -568,13 +568,13 @@ export function getShadowedLaunchDiagnostic(
568
568
  if (!active.invalid && !active.disabled) return undefined;
569
569
  return {
570
570
  active_path: active.path,
571
- blocked_candidate: active.shadows[0],
571
+ blocked_fallback: active.shadows[0],
572
572
  hint: "inspect_recipes_doctor",
573
573
  reason: active.invalid ? "shadowed_invalid" : "shadowed_disabled",
574
574
  };
575
575
  }
576
576
 
577
- export function listCandidateRecipes(root: string): Array<Record<string, unknown>> {
577
+ export function listDraftRecipes(root: string): Array<Record<string, unknown>> {
578
578
  return listRecipeFiles(root).map((path) => {
579
579
  const id = RecipeReferences.getRecipeIdFromPath(path);
580
580
  const config = RecipeReferences.readRawRecipeConfig(path);
package/lib/tools.ts CHANGED
@@ -27,6 +27,12 @@ export type RegisterToolInput = Registry.RegisterToolInput;
27
27
  export type RegisterToolRuntimeDeps<TContext> =
28
28
  Registry.RegisterToolRuntimeDeps<TContext>;
29
29
 
30
+ export interface ActorToolDefinition {
31
+ name: string;
32
+ execute?: (...args: never[]) => unknown;
33
+ [key: string]: unknown;
34
+ }
35
+
30
36
  export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContext> {
31
37
  configPath: string;
32
38
  getActiveTools: () => string[];
@@ -222,7 +228,7 @@ function compactAsyncRunStatus(value: unknown): string {
222
228
  tokens.push(`failures=${failures}`);
223
229
  if (result.code !== undefined) tokens.push(`code=${String(result.code)}`);
224
230
  if (result.killed === true) tokens.push("killed=true");
225
- const draftRecipe = status.draft_recipe ?? status.candidate_recipe;
231
+ const draftRecipe = status.draft_recipe;
226
232
  if (draftRecipe) tokens.push(`draft_recipe=${String(draftRecipe)}`);
227
233
  const nextActions = actorRunNextActions(run);
228
234
  if (nextActions.length > 0)
@@ -627,8 +633,8 @@ function compactRecipeDoctor(summary: Record<string, unknown>): string {
627
633
  item.action,
628
634
  Limits.DOCTOR_ACTION_PREVIEW_CHARS,
629
635
  );
630
- const blocked = item.blocked_candidate
631
- ? ` blocked=${compactPreview(item.blocked_candidate, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
636
+ const blocked = item.blocked_fallback
637
+ ? ` blocked=${compactPreview(item.blocked_fallback, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
632
638
  : "";
633
639
  lines.push(
634
640
  `${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`,
@@ -683,11 +689,7 @@ function compactRecipeRegistry(summary: Record<string, unknown>): string {
683
689
  const diagnostics = Array.isArray(summary.diagnostics)
684
690
  ? summary.diagnostics.length
685
691
  : 0;
686
- const drafts = Array.isArray(summary.drafts)
687
- ? summary.drafts.length
688
- : Array.isArray(summary.candidates)
689
- ? summary.candidates.length
690
- : 0;
692
+ const drafts = Array.isArray(summary.drafts) ? summary.drafts.length : 0;
691
693
  const recommendations = Array.isArray(summary.recommendations)
692
694
  ? summary.recommendations.length
693
695
  : 0;
@@ -873,11 +875,11 @@ function shadowedRecipeLaunchDiagnostic(
873
875
  return RecipeDiscovery.getShadowedLaunchDiagnostic(discovery, recipe);
874
876
  }
875
877
 
876
- function candidateRecipeName(run: string): string {
878
+ function draftRecipeName(run: string): string {
877
879
  return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
878
880
  }
879
881
 
880
- function candidateRecipeDefaults(
882
+ function draftRecipeDefaults(
881
883
  values: Record<string, unknown>,
882
884
  ): Record<string, unknown> | undefined {
883
885
  const ignored = new Set([
@@ -893,13 +895,13 @@ function candidateRecipeDefaults(
893
895
  return Object.keys(defaults).length > 0 ? defaults : undefined;
894
896
  }
895
897
 
896
- function writeSpawnCandidateRecipe(
898
+ function writeSpawnDraftRecipe(
897
899
  input: Record<string, unknown>,
898
900
  meta: AsyncRuns.AsyncRunMeta,
899
901
  ): string | undefined {
900
902
  if (
901
903
  process.env.NODE_TEST_CONTEXT &&
902
- process.env.PI_ACTORS_ENABLE_SPAWN_CANDIDATES_IN_TEST !== "1"
904
+ process.env.PI_ACTORS_ENABLE_SPAWN_DRAFTS_IN_TEST !== "1"
903
905
  )
904
906
  return undefined;
905
907
  if (
@@ -908,10 +910,10 @@ function writeSpawnCandidateRecipe(
908
910
  input.recipe !== undefined
909
911
  )
910
912
  return undefined;
911
- const root = Paths.getRecipeCandidateRoot();
913
+ const root = Paths.getRecipeDraftRoot();
912
914
  mkdirSync(root, { recursive: true });
913
- const path = join(root, candidateRecipeName(String(meta.run)));
914
- const defaults = candidateRecipeDefaults(meta.values);
915
+ const path = join(root, draftRecipeName(String(meta.run)));
916
+ const defaults = draftRecipeDefaults(meta.values);
915
917
  const recipe = {
916
918
  async: true,
917
919
  description: `Draft recipe captured from spawn run ${String(meta.run)}`,
@@ -930,7 +932,7 @@ function enhanceSpawnRecipeError(error: unknown, recipe: unknown): Error {
930
932
  const original = error instanceof Error ? error.message : String(error);
931
933
  return Object.assign(
932
934
  new Error(
933
- `${original} reason=${diagnostic.reason} active_path=${diagnostic.active_path} blocked_candidate=${diagnostic.blocked_candidate} hint=${diagnostic.hint}`,
935
+ `${original} reason=${diagnostic.reason} active_path=${diagnostic.active_path} blocked_fallback=${diagnostic.blocked_fallback} hint=${diagnostic.hint}`,
934
936
  ),
935
937
  {
936
938
  ...diagnostic,
@@ -1118,13 +1120,11 @@ export function createSpawnToolDefinition<
1118
1120
  } catch (error) {
1119
1121
  throw enhanceSpawnRecipeError(error, recipe);
1120
1122
  }
1121
- const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
1123
+ const draftRecipe = writeSpawnDraftRecipe(input, meta);
1122
1124
  const nextActions = actorRunNextActions(meta.run);
1123
1125
  const details = {
1124
1126
  ...meta,
1125
- ...(candidateRecipe
1126
- ? { candidate_recipe: candidateRecipe, draft_recipe: candidateRecipe }
1127
- : {}),
1127
+ ...(draftRecipe ? { draft_recipe: draftRecipe } : {}),
1128
1128
  next_actions: nextActions,
1129
1129
  };
1130
1130
  ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
@@ -1275,12 +1275,7 @@ export function createInspectToolDefinition<TContext = unknown>(
1275
1275
  const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
1276
1276
  const summaryBase = {
1277
1277
  ...RecipeDiscovery.summarizeDiscovery(discovered),
1278
- drafts: RecipeDiscovery.listCandidateRecipes(
1279
- join(recipeRoot, "candidates"),
1280
- ),
1281
- candidates: RecipeDiscovery.listCandidateRecipes(
1282
- join(recipeRoot, "candidates"),
1283
- ),
1278
+ drafts: RecipeDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
1284
1279
  };
1285
1280
  const summary = {
1286
1281
  ...summaryBase,
@@ -1943,7 +1938,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
1943
1938
 
1944
1939
  export function createCoreActorToolDefinitions<TContext extends AsyncRunToolContext>(
1945
1940
  deps: CoreActorToolDefinitionDeps<TContext>,
1946
- ): any[] {
1941
+ ): ActorToolDefinition[] {
1947
1942
  return [
1948
1943
  createRegisterToolDefinition<TContext>({
1949
1944
  configPath: deps.configPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -1,33 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Internal conformance runner shim.
4
+ * Internal conformance runner.
5
5
  *
6
- * Runtime logic lives in lib/conformance.ts and is compiled to
7
- * dist/lib/conformance.js for installed JS-only packages.
6
+ * This script is intentionally standalone package/release glue rather than a
7
+ * lib domain: it only selects regression suites and formats their summary.
8
8
  */
9
9
 
10
- import { existsSync } from "node:fs";
11
- import { dirname, join } from "node:path";
12
- import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { spawnSync } from "node:child_process";
11
+ import { dirname } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ const conformanceSuites = [
15
+ "tests/protocol-examples.test.ts",
16
+ "tests/recipe-discovery.test.ts",
17
+ "tests/registry.test.ts",
18
+ "tests/runtime-registry.test.ts",
19
+ "tests/async-runs.test.ts",
20
+ "tests/actor-rooms.test.ts",
21
+ "tests/tools.test.ts",
22
+ ];
13
23
 
14
24
  function packageRoot() {
15
25
  return dirname(dirname(fileURLToPath(import.meta.url)));
16
26
  }
17
27
 
18
- function mainModulePath() {
19
- const root = packageRoot();
20
- const compiled = join(root, "dist", "lib", "conformance.js");
21
- return existsSync(compiled) ? compiled : join(root, "lib", "conformance.ts");
22
- }
28
+ const result = spawnSync(
29
+ process.execPath,
30
+ ["--experimental-strip-types", "--test", ...conformanceSuites],
31
+ { cwd: packageRoot(), encoding: "utf8", stdio: "pipe" },
32
+ );
23
33
 
24
- const { runConformance } = await import(pathToFileURL(mainModulePath()).href);
25
- const report = runConformance(packageRoot());
34
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
35
+ const summary = output
36
+ .split("\n")
37
+ .filter((line) =>
38
+ /^ℹ (tests|pass|fail|cancelled|skipped|todo|duration_ms) /.test(line),
39
+ )
40
+ .join("\n");
26
41
 
27
42
  console.log("pi-actors conformance");
28
- console.log(`suites ${report.suites}`);
29
- if (report.summary) console.log(report.summary);
30
- if (report.code !== 0) {
31
- console.error(report.output.trimEnd());
32
- process.exit(report.code);
43
+ console.log(`suites ${conformanceSuites.length}`);
44
+
45
+ if (summary) console.log(summary);
46
+ if ((result.status ?? 1) !== 0) {
47
+ console.error(output.trimEnd());
48
+ process.exit(result.status ?? 1);
33
49
  }
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.32.0
5
+ version: 0.33.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -213,7 +213,7 @@ Only matching filename ids compete. Higher priority shadows lower priority; with
213
213
  Muscle-memory lens: pi-actors has two durable executable-memory layers.
214
214
 
215
215
  1. `~/.pi/agent/recipes/*.json` and `*.md` are the agent's active capability memory. Every recipe in that directory becomes an easy-to-call tool automatically and survives into later sessions. Descriptions matter here because they become the tool's operator-facing title/context.
216
- 2. `~/.pi/agent/recipes/candidates/*.json` is draft memory captured from successful inline `spawn template=...` runs. The directory name is retained for compatibility; treat these as drafts, not active tools. Drafts do not enter the injected tool surface. They remain reusable by explicit path, e.g. `spawn file="~/.pi/agent/recipes/candidates/<name>.json"`, and can be promoted by moving or copying one level up into `~/.pi/agent/recipes`.
216
+ 2. `~/.pi/agent/recipes/drafts/*.json` is draft memory captured from successful inline `spawn template=...` runs. Drafts do not enter the injected tool surface. They remain reusable by explicit path, e.g. `spawn file="~/.pi/agent/recipes/drafts/<name>.json"`, and can be promoted by moving or copying one level up into `~/.pi/agent/recipes`.
217
217
 
218
218
  Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow draft memory by trying ad hoc actors successfully. Treat both as executable habits: drafts are the workbench/proving ground; root recipes are promoted muscle memory.
219
219
 
@@ -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.32.0
5
+ version: 0.33.0
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -31,7 +31,7 @@ Maintain this skill as a living orchestration standard. When real swarm work exp
31
31
  - `Lens`: A deliberately narrow cognitive role assigned to one subagent, such as security, tests, architecture, economics, or operator UX.
32
32
  - `Task Card`: A bounded implementation assignment with goal, allowed files, avoided files, expected output, and validation gates.
33
33
  - `Component Capability`: An abstract adapter operation such as launcher, reviewer, verifier, merger, quorum, checkpoint, follow-up, judge, or normalizer. Swarm may target these capabilities, but local adapters bind them to concrete tools, recipes, draft recipes, command templates, async runs, or services.
34
- - `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn. It can be replayed by explicit file path and later promoted into the active tool recipe root after enough dogfood. Its compatibility storage path may still include `recipes/candidates`.
34
+ - `Draft Recipe`: A reusable but non-registered recipe captured from a successful inline actor spawn under `~/.pi/agent/recipes/drafts`. It can be replayed by explicit file path and later promoted into the active tool recipe root after enough dogfood.
35
35
  - `Coordinator Checkpoint`: A deliberate subagent pause where the subagent preserves its working context, sends a bounded question or status to the orchestrator, receives a coordinator reply, and continues in the same subagent context.
36
36
  - `Evidence Checkpoint`: A deliberate stop where a subagent records sources, assumptions, confidence, contradictions, or blocking evidence gaps before synthesis.
37
37
  - `Integrator`: The human or agent that merges isolated branches/worktrees into the shared target and owns conflict resolution.
@@ -1,12 +0,0 @@
1
- /**
2
- * Internal conformance runner logic.
3
- * Zones: CI/release validation, protocol regression suite selection
4
- */
5
- export declare const conformanceSuites: string[];
6
- export interface ConformanceReport {
7
- code: number;
8
- output: string;
9
- summary: string;
10
- suites: number;
11
- }
12
- export declare function runConformance(cwd?: URL): ConformanceReport;
@@ -1,28 +0,0 @@
1
- /**
2
- * Internal conformance runner logic.
3
- * Zones: CI/release validation, protocol regression suite selection
4
- */
5
- import { spawnSync } from "node:child_process";
6
- export const conformanceSuites = [
7
- "tests/protocol-examples.test.ts",
8
- "tests/recipe-discovery.test.ts",
9
- "tests/registry.test.ts",
10
- "tests/runtime-registry.test.ts",
11
- "tests/async-runs.test.ts",
12
- "tests/actor-rooms.test.ts",
13
- "tests/tools.test.ts",
14
- ];
15
- export function runConformance(cwd = new URL("..", import.meta.url)) {
16
- const result = spawnSync(process.execPath, ["--experimental-strip-types", "--test", ...conformanceSuites], { cwd, encoding: "utf8", stdio: "pipe" });
17
- const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
18
- const summary = output
19
- .split("\n")
20
- .filter((line) => /^ℹ (tests|pass|fail|cancelled|skipped|todo|duration_ms) /.test(line))
21
- .join("\n");
22
- return {
23
- code: result.status ?? 1,
24
- output,
25
- summary,
26
- suites: conformanceSuites.length,
27
- };
28
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * Internal conformance runner logic.
3
- * Zones: CI/release validation, protocol regression suite selection
4
- */
5
-
6
- import { spawnSync } from "node:child_process";
7
-
8
- export const conformanceSuites = [
9
- "tests/protocol-examples.test.ts",
10
- "tests/recipe-discovery.test.ts",
11
- "tests/registry.test.ts",
12
- "tests/runtime-registry.test.ts",
13
- "tests/async-runs.test.ts",
14
- "tests/actor-rooms.test.ts",
15
- "tests/tools.test.ts",
16
- ];
17
-
18
- export interface ConformanceReport {
19
- code: number;
20
- output: string;
21
- summary: string;
22
- suites: number;
23
- }
24
-
25
- export function runConformance(cwd = new URL("..", import.meta.url)): ConformanceReport {
26
- const result = spawnSync(
27
- process.execPath,
28
- ["--experimental-strip-types", "--test", ...conformanceSuites],
29
- { cwd, encoding: "utf8", stdio: "pipe" },
30
- );
31
-
32
- const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
33
- const summary = output
34
- .split("\n")
35
- .filter((line) =>
36
- /^ℹ (tests|pass|fail|cancelled|skipped|todo|duration_ms) /.test(line),
37
- )
38
- .join("\n");
39
-
40
- return {
41
- code: result.status ?? 1,
42
- output,
43
- summary,
44
- suites: conformanceSuites.length,
45
- };
46
- }