@llblab/pi-actors 0.28.1 → 0.29.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.29.0: Candidate Recipe Memory
6
+
7
+ - `[Spawn]` Capture inline spawn templates as non-registered candidate recipes under `~/.pi/agent/recipes/candidates`, making successful ad hoc actor patterns easy to replay by explicit path and promote manually.
8
+ - `[Inspect]` Add candidate recipe counts and verbose candidate metadata to recipe registry inspection without registering candidates as tools.
9
+ - `[Skills]` Documented the two-layer executable memory model: candidate recipes as a proving ground and root recipes as active tool memory.
10
+
5
11
  ## 0.28.1: Portable Agent Protocol Hotfix
6
12
 
7
13
  - `[Docs]` Removed a machine-local private validation skill reference from the repository agent protocol so extension guidance stays portable.
@@ -8,4 +8,5 @@ export declare function getConfigPath(agentDir?: string): string;
8
8
  export declare function getExtensionTmpDir(agentDir?: string, extensionName?: string): string;
9
9
  export declare function getRunStateRoot(agentDir?: string): string;
10
10
  export declare function getRecipeRoot(agentDir?: string): string;
11
+ export declare function getRecipeCandidateRoot(agentDir?: string): string;
11
12
  export declare function getPackagedRecipeRoot(): string;
package/dist/lib/paths.js CHANGED
@@ -24,6 +24,9 @@ export function getRunStateRoot(agentDir = getAgentDir()) {
24
24
  export function getRecipeRoot(agentDir = getAgentDir()) {
25
25
  return join(agentDir, "recipes");
26
26
  }
27
+ export function getRecipeCandidateRoot(agentDir = getAgentDir()) {
28
+ return join(getRecipeRoot(agentDir), "candidates");
29
+ }
27
30
  export function getPackagedRecipeRoot() {
28
31
  const here = dirname(fileURLToPath(import.meta.url));
29
32
  const compiledRoot = resolve(here, "..", "..", "recipes");
@@ -47,5 +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
51
  export declare function summarizeDiscovery(result: RecipeDiscoveryResult): Record<string, unknown>;
51
52
  export declare function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | undefined;
@@ -426,6 +426,17 @@ export function getShadowedLaunchDiagnostic(result, id) {
426
426
  reason: active.invalid ? "shadowed_invalid" : "shadowed_disabled",
427
427
  };
428
428
  }
429
+ export function listCandidateRecipes(root) {
430
+ return listRecipeFiles(root).map((path) => {
431
+ const id = RecipeReferences.getRecipeIdFromPath(path);
432
+ const config = RecipeReferences.readRawRecipeConfig(path);
433
+ return {
434
+ id,
435
+ path,
436
+ ...(config?.description ? { description: config.description } : {}),
437
+ };
438
+ });
439
+ }
429
440
  export function summarizeDiscovery(result) {
430
441
  const recommendations = result.entries
431
442
  .map((entry) => recommendationForEntry(entry, result.active.get(entry.id)?.path))
package/dist/lib/tools.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Owns generated runtime tool schemas and the register_tool management tool schema
5
5
  */
6
6
  import { execFileSync } from "node:child_process";
7
- import { existsSync, readFileSync } from "node:fs";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
8
  import { dirname, join } from "node:path";
9
9
  import * as ActorMessages from "./actor-messages.js";
10
10
  import * as ActorRooms from "./actor-rooms.js";
@@ -144,6 +144,8 @@ function compactAsyncRunStatus(value) {
144
144
  tokens.push(`code=${String(result.code)}`);
145
145
  if (result.killed === true)
146
146
  tokens.push("killed=true");
147
+ if (status.candidate_recipe)
148
+ tokens.push(`candidate_recipe=${String(status.candidate_recipe)}`);
147
149
  return `\n${tokens.join(" ")}`;
148
150
  }
149
151
  function compactRunMessages(messages) {
@@ -474,10 +476,13 @@ function compactRecipeRegistry(summary) {
474
476
  const diagnostics = Array.isArray(summary.diagnostics)
475
477
  ? summary.diagnostics.length
476
478
  : 0;
479
+ const candidates = Array.isArray(summary.candidates)
480
+ ? summary.candidates.length
481
+ : 0;
477
482
  const recommendations = Array.isArray(summary.recommendations)
478
483
  ? summary.recommendations.length
479
484
  : 0;
480
- return `\nrecipes active=${active} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}`;
485
+ return `\nrecipes active=${active} candidates=${candidates} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}`;
481
486
  }
482
487
  function compactActorMessageResult(message, result) {
483
488
  const tokens = [
@@ -581,6 +586,40 @@ function shadowedRecipeLaunchDiagnostic(recipe) {
581
586
  ]);
582
587
  return RecipeDiscovery.getShadowedLaunchDiagnostic(discovery, recipe);
583
588
  }
589
+ function candidateRecipeName(run) {
590
+ return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
591
+ }
592
+ function candidateRecipeDefaults(values) {
593
+ const ignored = new Set([
594
+ "actor_address",
595
+ "communication_file",
596
+ "default_room",
597
+ "run_id",
598
+ "state_dir",
599
+ ]);
600
+ const defaults = Object.fromEntries(Object.entries(values).filter(([key]) => !ignored.has(key)));
601
+ return Object.keys(defaults).length > 0 ? defaults : undefined;
602
+ }
603
+ function writeSpawnCandidateRecipe(input, meta) {
604
+ if (process.env.NODE_TEST_CONTEXT &&
605
+ process.env.PI_ACTORS_ENABLE_SPAWN_CANDIDATES_IN_TEST !== "1")
606
+ return undefined;
607
+ if (input.template === undefined || input.file !== undefined || input.recipe !== undefined)
608
+ return undefined;
609
+ const root = Paths.getRecipeCandidateRoot();
610
+ mkdirSync(root, { recursive: true });
611
+ const path = join(root, candidateRecipeName(String(meta.run)));
612
+ const defaults = candidateRecipeDefaults(meta.values);
613
+ const recipe = {
614
+ async: true,
615
+ description: `Candidate recipe captured from spawn run ${String(meta.run)}`,
616
+ ...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
617
+ ...(defaults ? { defaults } : {}),
618
+ template: input.template,
619
+ };
620
+ writeFileSync(path, `${JSON.stringify(recipe, null, 2)}\n`, { flag: "wx" });
621
+ return path;
622
+ }
584
623
  function enhanceSpawnRecipeError(error, recipe) {
585
624
  const diagnostic = shadowedRecipeLaunchDiagnostic(recipe);
586
625
  if (!diagnostic)
@@ -704,16 +743,20 @@ export function createSpawnToolDefinition() {
704
743
  catch (error) {
705
744
  throw enhanceSpawnRecipeError(error, recipe);
706
745
  }
746
+ const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
747
+ const details = candidateRecipe
748
+ ? { ...meta, candidate_recipe: candidateRecipe }
749
+ : meta;
707
750
  ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
708
751
  ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
709
752
  return {
710
753
  content: [
711
754
  {
712
755
  type: "text",
713
- text: maybeJsonText(meta, input.verbose === true, compactAsyncRunStatus(meta)),
756
+ text: maybeJsonText(details, input.verbose === true, compactAsyncRunStatus(details)),
714
757
  },
715
758
  ],
716
- details: meta,
759
+ details,
717
760
  };
718
761
  },
719
762
  };
@@ -773,7 +816,11 @@ export function createInspectToolDefinition(deps = {}) {
773
816
  },
774
817
  { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
775
818
  ]);
776
- const summary = RecipeDiscovery.summarizeDiscovery(discovered);
819
+ const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
820
+ const summary = {
821
+ ...RecipeDiscovery.summarizeDiscovery(discovered),
822
+ candidates: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
823
+ };
777
824
  return {
778
825
  content: [
779
826
  {
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.28.1
5
+ version: 0.29.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -235,11 +235,16 @@ Priority for same-id recipes:
235
235
 
236
236
  Only matching filename ids compete. Higher priority shadows lower priority; within one priority layer, same-id JSON shadows Markdown. An invalid or `disabled: true` higher-priority recipe blocks fallback so the agent does not silently run standard-library behavior when a user override is broken or intentionally disabled.
237
237
 
238
- Muscle-memory lens: `~/.pi/agent/recipes/*.json` and `*.md` are the agent's capability memory. Every recipe in that directory becomes an easy-to-call tool automatically and survives into later sessions. Agents grow this memory either by calling `register_tool`, which writes recipe files there under the hood, or by deliberately editing those recipe files. Treat this directory like `MEMORY.md` for executable habits: useful local patterns belong there; packaged recipes elsewhere are reusable components, not tools.
238
+ Muscle-memory lens: pi-actors has two durable executable-memory layers.
239
+
240
+ 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.
241
+ 2. `~/.pi/agent/recipes/candidates/*.json` is candidate memory captured from successful inline `spawn template=...` runs. Candidates are not registered tools and 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`.
242
+
243
+ Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow candidate memory by trying ad hoc actors successfully. Treat both as executable habits: candidates are the workbench/proving ground; root recipes are promoted muscle memory.
239
244
 
240
245
  Usage lens: user recipes may carry extension-maintained launch metadata such as `usage.calls` and `usage.last_called`. The extension increments the counter when it starts that concrete recipe; agents should not hand-edit counters as part of normal recipe maintenance. Treat usage as evidence for usefulness analysis: heavily used recipes are good candidates for promotion, documentation, or stronger tests; unused recipes are cleanup candidates. Do not use failure counts as a primary usefulness signal because failures may reflect bad caller judgment rather than bad recipes. Do not delete or demote solely from counters without operator approval.
241
246
 
242
- Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. If the run was repeatable, parameterized, safe enough, and likely useful later, the agent may promote it by calling `register_tool` with a concise name, typed args/defaults, and a reviewed template or recipe path. Do not wait for UI buttons; do not auto-register every success; do not persist temp paths, secrets, one-off prompts, or project-private assumptions without normalization and approval.
247
+ Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. Inline spawns leave candidate recipes as replayable evidence, not active tools. If a candidate is repeatable, parameterized, safe enough, and likely useful later, the agent may promote it by moving/copying it into `~/.pi/agent/recipes` or by calling `register_tool` with a concise name, typed args/defaults, and a reviewed template or recipe path. Do not auto-register every success; do not promote temp paths, secrets, one-off prompts, or project-private assumptions without normalization and approval.
243
248
 
244
249
  Cleanup rule: periodically inspect `~/.pi/agent/recipes` as the live muscle-memory set. For each stale, duplicate, too-specific, or low-value recipe, choose one explicit action: keep as a tool, move it out of the agent recipe root to retain recipe-only memory, merge into a better recipe, or delete/archive the file. Prefer moving over deletion when the recipe may still be useful as a component. Never silently remove tools during unrelated work.
245
250
 
@@ -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.28.1
5
+ version: 0.29.0
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -30,7 +30,8 @@ Maintain this skill as a living orchestration standard. When real swarm work exp
30
30
  - `Async Run`: A local lifecycle envelope around a command-template swarm composer or utility. It owns state, logs, status, cancellation, and observability, not swarm semantics.
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
- - `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, command templates, async runs, or services.
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, candidate recipes, command templates, async runs, or services.
34
+ - `Candidate 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.
34
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.
35
36
  - `Evidence Checkpoint`: A deliberate stop where a subagent records sources, assumptions, confidence, contradictions, or blocking evidence gaps before synthesis.
36
37
  - `Integrator`: The human or agent that merges isolated branches/worktrees into the shared target and owns conflict resolution.
@@ -10,6 +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` are captured inline-spawn candidates, 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.
13
14
  - Packaged pi-actors recipes are the lower-priority standard library of declarative actor config components, not automatically registered tools.
14
15
  - Ad hoc recipe files outside the user recipe root are components unless explicitly registered/copied into `~/.pi/agent/recipes`.
15
16
  - Recipe identity is the filename basename; `~/.pi/agent/recipes/docs_review.json` and `docs_review.md` both have id/tool name `docs_review`.
package/lib/paths.ts CHANGED
@@ -36,6 +36,10 @@ export function getRecipeRoot(agentDir = getAgentDir()): string {
36
36
  return join(agentDir, "recipes");
37
37
  }
38
38
 
39
+ export function getRecipeCandidateRoot(agentDir = getAgentDir()): string {
40
+ return join(getRecipeRoot(agentDir), "candidates");
41
+ }
42
+
39
43
  export function getPackagedRecipeRoot(): string {
40
44
  const here = dirname(fileURLToPath(import.meta.url));
41
45
  const compiledRoot = resolve(here, "..", "..", "recipes");
@@ -575,6 +575,18 @@ export function getShadowedLaunchDiagnostic(
575
575
  };
576
576
  }
577
577
 
578
+ export function listCandidateRecipes(root: string): Array<Record<string, unknown>> {
579
+ return listRecipeFiles(root).map((path) => {
580
+ const id = RecipeReferences.getRecipeIdFromPath(path);
581
+ const config = RecipeReferences.readRawRecipeConfig(path);
582
+ return {
583
+ id,
584
+ path,
585
+ ...(config?.description ? { description: config.description } : {}),
586
+ };
587
+ });
588
+ }
589
+
578
590
  export function summarizeDiscovery(
579
591
  result: RecipeDiscoveryResult,
580
592
  ): Record<string, unknown> {
package/lib/tools.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { execFileSync } from "node:child_process";
8
- import { existsSync, readFileSync } from "node:fs";
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { dirname, join } from "node:path";
10
10
 
11
11
  import * as ActorMessages from "./actor-messages.ts";
@@ -183,6 +183,7 @@ function compactAsyncRunStatus(value: unknown): string {
183
183
  tokens.push(`failures=${failures}`);
184
184
  if (result.code !== undefined) tokens.push(`code=${String(result.code)}`);
185
185
  if (result.killed === true) tokens.push("killed=true");
186
+ if (status.candidate_recipe) tokens.push(`candidate_recipe=${String(status.candidate_recipe)}`);
186
187
  return `\n${tokens.join(" ")}`;
187
188
  }
188
189
 
@@ -574,10 +575,13 @@ function compactRecipeRegistry(summary: Record<string, unknown>): string {
574
575
  const diagnostics = Array.isArray(summary.diagnostics)
575
576
  ? summary.diagnostics.length
576
577
  : 0;
578
+ const candidates = Array.isArray(summary.candidates)
579
+ ? summary.candidates.length
580
+ : 0;
577
581
  const recommendations = Array.isArray(summary.recommendations)
578
582
  ? summary.recommendations.length
579
583
  : 0;
580
- return `\nrecipes active=${active} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}`;
584
+ return `\nrecipes active=${active} candidates=${candidates} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}`;
581
585
  }
582
586
 
583
587
  function compactActorMessageResult(
@@ -722,6 +726,50 @@ function shadowedRecipeLaunchDiagnostic(
722
726
  return RecipeDiscovery.getShadowedLaunchDiagnostic(discovery, recipe);
723
727
  }
724
728
 
729
+ function candidateRecipeName(run: string): string {
730
+ return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
731
+ }
732
+
733
+ function candidateRecipeDefaults(values: Record<string, unknown>): Record<string, unknown> | undefined {
734
+ const ignored = new Set([
735
+ "actor_address",
736
+ "communication_file",
737
+ "default_room",
738
+ "run_id",
739
+ "state_dir",
740
+ ]);
741
+ const defaults = Object.fromEntries(
742
+ Object.entries(values).filter(([key]) => !ignored.has(key)),
743
+ );
744
+ return Object.keys(defaults).length > 0 ? defaults : undefined;
745
+ }
746
+
747
+ function writeSpawnCandidateRecipe(
748
+ input: Record<string, unknown>,
749
+ meta: AsyncRuns.AsyncRunMeta,
750
+ ): string | undefined {
751
+ if (
752
+ process.env.NODE_TEST_CONTEXT &&
753
+ process.env.PI_ACTORS_ENABLE_SPAWN_CANDIDATES_IN_TEST !== "1"
754
+ )
755
+ return undefined;
756
+ if (input.template === undefined || input.file !== undefined || input.recipe !== undefined)
757
+ return undefined;
758
+ const root = Paths.getRecipeCandidateRoot();
759
+ mkdirSync(root, { recursive: true });
760
+ const path = join(root, candidateRecipeName(String(meta.run)));
761
+ const defaults = candidateRecipeDefaults(meta.values);
762
+ const recipe = {
763
+ async: true,
764
+ description: `Candidate recipe captured from spawn run ${String(meta.run)}`,
765
+ ...(meta.artifacts ? { artifacts: meta.artifacts } : {}),
766
+ ...(defaults ? { defaults } : {}),
767
+ template: input.template,
768
+ };
769
+ writeFileSync(path, `${JSON.stringify(recipe, null, 2)}\n`, { flag: "wx" });
770
+ return path;
771
+ }
772
+
725
773
  function enhanceSpawnRecipeError(error: unknown, recipe: unknown): Error {
726
774
  const diagnostic = shadowedRecipeLaunchDiagnostic(recipe);
727
775
  if (!diagnostic) return error instanceof Error ? error : new Error(String(error));
@@ -911,6 +959,10 @@ export function createSpawnToolDefinition<
911
959
  } catch (error) {
912
960
  throw enhanceSpawnRecipeError(error, recipe);
913
961
  }
962
+ const candidateRecipe = writeSpawnCandidateRecipe(input, meta);
963
+ const details = candidateRecipe
964
+ ? { ...meta, candidate_recipe: candidateRecipe }
965
+ : meta;
914
966
  ActorRooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
915
967
  ActorRooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
916
968
  return {
@@ -918,13 +970,13 @@ export function createSpawnToolDefinition<
918
970
  {
919
971
  type: "text" as const,
920
972
  text: maybeJsonText(
921
- meta,
973
+ details,
922
974
  input.verbose === true,
923
- compactAsyncRunStatus(meta),
975
+ compactAsyncRunStatus(details),
924
976
  ),
925
977
  },
926
978
  ],
927
- details: meta,
979
+ details,
928
980
  };
929
981
  },
930
982
  };
@@ -1031,7 +1083,11 @@ export function createInspectToolDefinition<TContext = unknown>(
1031
1083
  },
1032
1084
  { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
1033
1085
  ]);
1034
- const summary = RecipeDiscovery.summarizeDiscovery(discovered);
1086
+ const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
1087
+ const summary = {
1088
+ ...RecipeDiscovery.summarizeDiscovery(discovered),
1089
+ candidates: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
1090
+ };
1035
1091
  return {
1036
1092
  content: [
1037
1093
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.28.1",
3
+ "version": "0.29.0",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.28.1
5
+ version: 0.29.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -235,11 +235,16 @@ Priority for same-id recipes:
235
235
 
236
236
  Only matching filename ids compete. Higher priority shadows lower priority; within one priority layer, same-id JSON shadows Markdown. An invalid or `disabled: true` higher-priority recipe blocks fallback so the agent does not silently run standard-library behavior when a user override is broken or intentionally disabled.
237
237
 
238
- Muscle-memory lens: `~/.pi/agent/recipes/*.json` and `*.md` are the agent's capability memory. Every recipe in that directory becomes an easy-to-call tool automatically and survives into later sessions. Agents grow this memory either by calling `register_tool`, which writes recipe files there under the hood, or by deliberately editing those recipe files. Treat this directory like `MEMORY.md` for executable habits: useful local patterns belong there; packaged recipes elsewhere are reusable components, not tools.
238
+ Muscle-memory lens: pi-actors has two durable executable-memory layers.
239
+
240
+ 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.
241
+ 2. `~/.pi/agent/recipes/candidates/*.json` is candidate memory captured from successful inline `spawn template=...` runs. Candidates are not registered tools and 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`.
242
+
243
+ Agents grow active memory by calling `register_tool` or by deliberate recipe-file edits. They grow candidate memory by trying ad hoc actors successfully. Treat both as executable habits: candidates are the workbench/proving ground; root recipes are promoted muscle memory.
239
244
 
240
245
  Usage lens: user recipes may carry extension-maintained launch metadata such as `usage.calls` and `usage.last_called`. The extension increments the counter when it starts that concrete recipe; agents should not hand-edit counters as part of normal recipe maintenance. Treat usage as evidence for usefulness analysis: heavily used recipes are good candidates for promotion, documentation, or stronger tests; unused recipes are cleanup candidates. Do not use failure counts as a primary usefulness signal because failures may reflect bad caller judgment rather than bad recipes. Do not delete or demote solely from counters without operator approval.
241
246
 
242
- Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. If the run was repeatable, parameterized, safe enough, and likely useful later, the agent may promote it by calling `register_tool` with a concise name, typed args/defaults, and a reviewed template or recipe path. Do not wait for UI buttons; do not auto-register every success; do not persist temp paths, secrets, one-off prompts, or project-private assumptions without normalization and approval.
247
+ Promotion lens: successful transient/ad hoc actor runs are evidence, not commands. Inline spawns leave candidate recipes as replayable evidence, not active tools. If a candidate is repeatable, parameterized, safe enough, and likely useful later, the agent may promote it by moving/copying it into `~/.pi/agent/recipes` or by calling `register_tool` with a concise name, typed args/defaults, and a reviewed template or recipe path. Do not auto-register every success; do not promote temp paths, secrets, one-off prompts, or project-private assumptions without normalization and approval.
243
248
 
244
249
  Cleanup rule: periodically inspect `~/.pi/agent/recipes` as the live muscle-memory set. For each stale, duplicate, too-specific, or low-value recipe, choose one explicit action: keep as a tool, move it out of the agent recipe root to retain recipe-only memory, merge into a better recipe, or delete/archive the file. Prefer moving over deletion when the recipe may still be useful as a component. Never silently remove tools during unrelated work.
245
250
 
@@ -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.28.1
5
+ version: 0.29.0
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -30,7 +30,8 @@ Maintain this skill as a living orchestration standard. When real swarm work exp
30
30
  - `Async Run`: A local lifecycle envelope around a command-template swarm composer or utility. It owns state, logs, status, cancellation, and observability, not swarm semantics.
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
- - `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, command templates, async runs, or services.
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, candidate recipes, command templates, async runs, or services.
34
+ - `Candidate 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.
34
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.
35
36
  - `Evidence Checkpoint`: A deliberate stop where a subagent records sources, assumptions, confidence, contradictions, or blocking evidence gaps before synthesis.
36
37
  - `Integrator`: The human or agent that merges isolated branches/worktrees into the shared target and owns conflict resolution.