@llblab/pi-actors 0.14.3 → 0.16.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 (76) hide show
  1. package/AGENTS.md +5 -1
  2. package/BACKLOG.md +54 -32
  3. package/CHANGELOG.md +39 -0
  4. package/README.md +53 -61
  5. package/banner.jpg +0 -0
  6. package/docs/actor-messages.md +1 -1
  7. package/docs/async-runs.md +4 -4
  8. package/docs/command-templates.md +11 -11
  9. package/docs/recipe-library.md +7 -3
  10. package/docs/task-first-recipes.md +44 -43
  11. package/docs/template-recipes.md +45 -23
  12. package/docs/tool-registry.md +50 -42
  13. package/index.ts +34 -0
  14. package/lib/actor-messages.ts +20 -7
  15. package/lib/async-runs.ts +35 -12
  16. package/lib/command-templates.ts +6 -1
  17. package/lib/config.ts +3 -2
  18. package/lib/execution.ts +9 -5
  19. package/lib/observability.ts +20 -10
  20. package/lib/paths.ts +6 -1
  21. package/lib/prompts.ts +14 -21
  22. package/lib/recipe-discovery.ts +226 -0
  23. package/lib/recipe-migration.ts +123 -0
  24. package/lib/recipe-references.ts +45 -13
  25. package/lib/recipe-usage.ts +44 -0
  26. package/lib/registry.ts +48 -15
  27. package/lib/runtime.ts +59 -15
  28. package/lib/tools.ts +237 -65
  29. package/package.json +21 -11
  30. package/recipes/coordinator-locker.json +46 -0
  31. package/recipes/music-player.json +16 -2
  32. package/recipes/pipeline-architect-coordinator.json +11 -3
  33. package/recipes/pipeline-artifact-bundle.json +12 -3
  34. package/recipes/pipeline-artifact-report.json +9 -3
  35. package/recipes/pipeline-artifact-write.json +9 -3
  36. package/recipes/pipeline-async-run-ops.json +18 -9
  37. package/recipes/pipeline-checkpoint-continuation.json +14 -3
  38. package/recipes/pipeline-development-tasking.json +12 -3
  39. package/recipes/pipeline-docs-maintenance.json +12 -3
  40. package/recipes/pipeline-media-library.json +12 -3
  41. package/recipes/pipeline-quorum-review.json +12 -9
  42. package/recipes/pipeline-release-readiness.json +27 -9
  43. package/recipes/pipeline-release-summary.json +89 -0
  44. package/recipes/pipeline-repo-health.json +12 -3
  45. package/recipes/pipeline-research-synthesis.json +11 -3
  46. package/recipes/pipeline-review-readiness.json +12 -6
  47. package/recipes/subagent-artifact.json +9 -3
  48. package/recipes/subagent-checkpoint.json +10 -3
  49. package/recipes/subagent-conflict-report.json +11 -3
  50. package/recipes/subagent-contradiction-map.json +11 -3
  51. package/recipes/subagent-critic.json +11 -3
  52. package/recipes/subagent-evidence-map.json +11 -3
  53. package/recipes/subagent-followup.json +10 -3
  54. package/recipes/subagent-judge.json +11 -3
  55. package/recipes/subagent-merge.json +11 -3
  56. package/recipes/subagent-message.json +8 -3
  57. package/recipes/subagent-normalize.json +11 -3
  58. package/recipes/subagent-plan.json +11 -3
  59. package/recipes/subagent-prompt.json +10 -3
  60. package/recipes/subagent-quorum.json +10 -7
  61. package/recipes/subagent-review-coordinator.json +14 -6
  62. package/recipes/subagent-review.json +11 -3
  63. package/recipes/subagent-task-card.json +11 -3
  64. package/recipes/subagent-tools.json +10 -3
  65. package/recipes/subagent-verify.json +11 -3
  66. package/recipes/subagents-prompts.json +10 -3
  67. package/recipes/utility-coordinator-lock-snapshot.json +14 -0
  68. package/recipes/utility-run-ops-snapshot.json +3 -3
  69. package/recipes/utility-skill-summary.json +14 -0
  70. package/scripts/coordinator-locker.mjs +272 -0
  71. package/scripts/music-player.mjs +2 -1
  72. package/scripts/recipe-utils.mjs +239 -81
  73. package/scripts/validate-recipe.mjs +28 -10
  74. package/skills/actors/SKILL.md +301 -0
  75. package/skills/swarm/SKILL.md +451 -0
  76. package/skills/swarm/references/development-swarm.md +596 -0
@@ -30,13 +30,13 @@ scope snapshot → changelog/package check → release lens reviews → risk ver
30
30
 
31
31
  Likely needed cells:
32
32
 
33
- - package metadata reader
34
- - changelog section extractor
35
- - package contents summarizer
36
- - validation command wrapper
37
- - release-risk reviewer
38
- - readiness merger/judge
39
- - release checklist artifact writer
33
+ - Package metadata reader
34
+ - Changelog section extractor
35
+ - Package contents summarizer
36
+ - Validation command wrapper
37
+ - Release-risk reviewer
38
+ - Readiness merger/judge
39
+ - Release checklist artifact writer
40
40
 
41
41
  Existing seeds:
42
42
 
@@ -49,7 +49,7 @@ Existing seeds:
49
49
 
50
50
  Implemented seed:
51
51
 
52
- - `pipeline-release-readiness`: changelog section → package summary → validation wrapper → release review coordinator → artifact report.
52
+ - `pipeline-release-readiness`: changelog section → package summary → packaged skill summary → validation wrapper → release review coordinator → artifact report.
53
53
 
54
54
  ### Repository Health Cell
55
55
 
@@ -63,12 +63,12 @@ git status/log → package/docs/backlog snapshot → validation summary → heal
63
63
 
64
64
  Likely needed cells:
65
65
 
66
- - git status/log utility
67
- - package version reader
68
- - backlog open/blocked extractor
69
- - docs index checker
70
- - validation summary normalizer
71
- - next-action recommender
66
+ - Git status/log utility
67
+ - Package version reader
68
+ - Backlog open/blocked extractor
69
+ - Docs index checker
70
+ - Validation summary normalizer
71
+ - Next-action recommender
72
72
 
73
73
  Existing seeds:
74
74
 
@@ -93,11 +93,11 @@ run-state summary → actor-message tail → stale/active classification → rec
93
93
 
94
94
  Likely needed cells:
95
95
 
96
- - run summary helper
96
+ - Run summary helper
97
97
  - JSONL actor-message tailer
98
- - stale-run classifier
99
- - control-message recommender
100
- - run report artifact
98
+ - Stale-run classifier
99
+ - Control-message recommender
100
+ - Run report artifact
101
101
 
102
102
  Existing seeds:
103
103
 
@@ -122,13 +122,13 @@ question framing → evidence map → contradiction map → claim verification
122
122
 
123
123
  Likely needed cells:
124
124
 
125
- - question framer
126
- - source inventory utility
127
- - evidence mapper
128
- - contradiction mapper
129
- - verifier
130
- - synthesis merger
131
- - limitations normalizer
125
+ - Question framer
126
+ - Source inventory utility
127
+ - Evidence mapper
128
+ - Contradiction mapper
129
+ - Verifier
130
+ - Synthesis merger
131
+ - Limitations normalizer
132
132
 
133
133
  Existing seeds:
134
134
 
@@ -149,11 +149,11 @@ goal → mutation zones → task cards → validation gates → conflict risks
149
149
 
150
150
  Likely needed cells:
151
151
 
152
- - mutation-zone planner
153
- - task-card generator
154
- - ownership/conflict checker
155
- - validation-gate normalizer
156
- - integrator handoff artifact
152
+ - Mutation-zone planner
153
+ - Task-card generator
154
+ - Ownership/conflict checker
155
+ - Validation-gate normalizer
156
+ - Integrator handoff artifact
157
157
 
158
158
  Existing seeds:
159
159
 
@@ -173,11 +173,11 @@ doc file inventory → index diff → stale link/routing review → rewrite sugg
173
173
 
174
174
  Likely needed cells:
175
175
 
176
- - markdown index utility
177
- - link checker wrapper
178
- - docs consistency reviewer
179
- - docs update planner
180
- - docs artifact writer
176
+ - Markdown index utility
177
+ - Link checker wrapper
178
+ - Docs consistency reviewer
179
+ - Docs update planner
180
+ - Docs artifact writer
181
181
 
182
182
  Existing seeds:
183
183
 
@@ -202,10 +202,10 @@ media scan → playlist build → playback start → message summary → control
202
202
 
203
203
  Likely needed cells:
204
204
 
205
- - playlist builder
206
- - music player
207
- - run/message summary
208
- - control recommender
205
+ - Playlist builder
206
+ - Music player
207
+ - Run/message summary
208
+ - Control recommender
209
209
 
210
210
  Existing seeds:
211
211
 
@@ -226,8 +226,9 @@ Prefer adding a high-level recipe when at least three cells already exist and th
226
226
 
227
227
  Good next candidates for the standard library after the first task-first wave:
228
228
 
229
- 1. Package/release metadata enrichment: implemented in `pipeline-release-readiness` by adding `utility-package-summary` between changelog extraction and validation, making release-readiness reports more evidence-rich without adding publish automation.
230
- 2. Artifact packaging and manifesting: implemented as `pipeline-artifact-bundle`, which composes optional validation, `pipeline-artifact-write`, `utility-artifact-manifest`, deterministic manifest writing, and an actor-message handoff when the caller explicitly requests filesystem writes.
231
- 3. Async run cleanup planning: extend async-run operations with stale-run classification and recommended `message`, `cancel`, or `kill` controls, keeping actual control execution operator-gated.
229
+ 1. Package/release metadata enrichment: implemented in `pipeline-release-readiness` by adding `utility-package-summary` and `utility-skill-summary` between changelog extraction and validation, making release-readiness reports more evidence-rich without adding publish automation.
230
+ 2. Evidence-only release summary: implemented as `pipeline-release-summary`, which composes changelog/package/skill/validation evidence into a release summary, risk checklist, and PR body draft artifact while leaving commit, PR, merge, tag, and publish actions to explicit release gates.
231
+ 3. Artifact packaging and manifesting: implemented as `pipeline-artifact-bundle`, which composes optional validation, `pipeline-artifact-write`, `utility-artifact-manifest`, deterministic manifest writing, and an actor-message handoff when the caller explicitly requests filesystem writes.
232
+ 4. Async run cleanup planning: extend async-run operations with stale-run classification and recommended `message`, `cancel`, or `kill` controls, keeping actual control execution operator-gated.
232
233
 
233
- Each candidate should land with the minimum missing cells rather than a broad one-shot framework. Already implemented task-first seeds include `pipeline-release-readiness`, `pipeline-repo-health`, `pipeline-async-run-ops`, `pipeline-docs-maintenance`, `pipeline-media-library`, and `pipeline-artifact-bundle`.
234
+ Each candidate should land with the minimum missing cells rather than a broad one-shot framework. Already implemented task-first seeds include `pipeline-release-readiness`, `pipeline-release-summary`, `pipeline-repo-health`, `pipeline-async-run-ops`, `pipeline-docs-maintenance`, `pipeline-media-library`, and `pipeline-artifact-bundle`.
@@ -21,6 +21,8 @@ A recipe wraps one command-template tree. The wrapped `template` keeps the norma
21
21
 
22
22
  Layer boundary: `imports`, `{ "name": "alias" }` imported-recipe nodes, `{alias.defaults.key}` references, fallback expressions, and recipe-local ternaries are recipe-loading features. They resolve before the command-template graph runs and do not extend the portable Command Template Standard. Typed imports are recipe definitions: they expose the imported recipe's command-template-shaped metadata (`template`, `args`, `defaults`, flags, and `values`), while async-run launch fields such as `async` and `state_dir` remain lifecycle configuration for starting a run, not part of the imported execution graph.
23
23
 
24
+ Packaged recipes are the pi-actors recipe standard library: declarative actor config components that can be imported, launched, inspected, overridden, or composed by user recipes. Treat them as stable building blocks rather than user-local policy.
25
+
24
26
  ## Layer Ownership
25
27
 
26
28
  Template-recipe standard owns:
@@ -67,6 +69,36 @@ Async recipe:
67
69
 
68
70
  `name` names the saved definition when an explicit name is needed. File-backed recipes usually omit it because the filename is the canonical recipe id. `template` is the command-template tree. `async: true` selects detached run mode when the recipe is invoked through a registered tool.
69
71
 
72
+ ## Discovery Priority
73
+
74
+ Recipe priority only matters when two discovered recipes have the same filename id. The conceptual ladder from lowest to highest priority is:
75
+
76
+ 1. No recipe for that id.
77
+ 2. Packaged pi-actors recipe components, acting as the standard library.
78
+ 3. Explicitly referenced ad hoc user recipe files located outside `~/.pi/agent/recipes`.
79
+ 4. User recipe files under `~/.pi/agent/recipes/*.json`.
80
+
81
+ The high-priority user recipe directory is also the default tool set: recipes placed there are agent tools unless they explicitly set `tool: false`. This preserves the old advantage of a tool-only registry because listing `~/.pi/agent/recipes` shows the operator-managed tool surface. Packaged and ad hoc recipes are recipe components by default; they opt into agent-tool exposure with `tool: true`.
82
+
83
+ Higher-priority files shadow lower-priority files with the same basename. A highest-priority invalid recipe is still visible and blocks fallback so operators do not accidentally run packaged behavior when a user override is broken. A highest-priority recipe with `disabled: true` also blocks fallback and intentionally disables that id.
84
+
85
+ ## Usage Metadata
86
+
87
+ User-owned recipes may accumulate extension-maintained usage metadata:
88
+
89
+ ```json
90
+ {
91
+ "usage": {
92
+ "calls": 12,
93
+ "last_called": "2026-05-22T10:30:00.000Z"
94
+ }
95
+ }
96
+ ```
97
+
98
+ The extension increments `usage.calls` and updates `usage.last_called` when it starts that concrete recipe, either through a recipe-backed tool call or a direct async recipe-file run. Agents should treat these fields as cleanup evidence, not as authored recipe contract. Packaged standard-library recipes are not mutated for usage metadata.
99
+
100
+ There is intentionally no failure counter in the recipe contract. A failed launch can reflect caller misuse, missing runtime values, or an environmental problem rather than recipe uselessness. Cleanup decisions should be explicit operator work: keep as a tool, set `tool: false`, merge, delete, or archive.
101
+
70
102
  For object form, keep `template` last. Recipe metadata comes first; executable content stays last.
71
103
 
72
104
  ## Named Artifacts
@@ -95,7 +127,12 @@ Use recipe-level `mailbox` to document the semantic messages a recipe actor acce
95
127
  ```json
96
128
  {
97
129
  "mailbox": {
98
- "accepts": ["control.continue", "control.revise", "control.approve", "control.stop"],
130
+ "accepts": [
131
+ "control.continue",
132
+ "control.revise",
133
+ "control.approve",
134
+ "control.stop"
135
+ ],
99
136
  "emits": ["checkpoint.needs_scope", "branch.done", "run.done"]
100
137
  }
101
138
  }
@@ -166,34 +203,19 @@ Call-time params override file params. `values` are merged with file values; cal
166
203
 
167
204
  ## Registered Recipe Tools
168
205
 
169
- A registered tool can point at an actor recipe by storing the recipe path or name in `template`:
206
+ A registered tool is a recipe file exposed as an agent tool. User recipes under `~/.pi/agent/recipes/*.json` are tools by default; packaged/ad hoc recipes opt in with `tool: true`:
170
207
 
171
208
  ```json
172
209
  {
173
- "docs_review": {
174
- "description": "Start an async docs review actor",
175
- "args": ["scope:path", "model:string=openai-codex/gpt-5.5"],
176
- "template": "docs-review.json"
177
- }
178
- }
179
- ```
180
-
181
- If `docs-review.json` contains `async: true`, calling `docs_review` starts a detached actor run and returns metadata. If `async` is omitted or false, calling `docs_review` executes the recipe foreground and returns normal tool output.
182
-
183
- A registered tool may also co-locate an actor recipe directly in `actors-tools.json`:
184
-
185
- ```json
186
- {
187
- "review_docs": {
188
- "description": "Start an async docs review",
189
- "name": "review-docs",
190
- "async": true,
191
- "template": "review {scope}"
192
- }
210
+ "description": "Start an async docs review actor",
211
+ "tool": true,
212
+ "async": true,
213
+ "args": ["scope:path", "model:string"],
214
+ "template": "review {scope} --model {model}"
193
215
  }
194
216
  ```
195
217
 
196
- The co-located entry must still own `template` directly and must not define `tool`.
218
+ If a tool recipe contains `async: true`, calling the tool starts a detached actor run and returns metadata. If `async` is omitted or false, calling the tool executes the recipe foreground and returns normal tool output.
197
219
 
198
220
  ## Values And Public Args
199
221
 
@@ -1,39 +1,49 @@
1
1
  # Tool Registry
2
2
 
3
- `pi-actors` stores registered actor-control command-template tools and template-recipe launchers in `~/.pi/agent/actors-tools.json` and registers them automatically on session start.
3
+ `pi-actors` stores persistent agent tools as recipe files under `~/.pi/agent/recipes/*.json` and registers the active tool set automatically on session start.
4
4
 
5
- This document is the local adaptation of the portable [Command Template Standard](./command-templates.md).
5
+ This document is the local adaptation of the portable [Command Template Standard](./command-templates.md) and the recipe-file runtime described in [Template Recipe Standard](./template-recipes.md).
6
6
 
7
- ## Rename Migration
7
+ ## Registry Model
8
8
 
9
- `pi-actors` reads only `~/.pi/agent/actors-tools.json` as its registry source. When moving from `pi-auto-tools`, copy the previous registry explicitly:
9
+ The 0.16 registry source is file-discovered recipes, not a live tool-only JSON file:
10
10
 
11
- ```bash
12
- cp ~/.pi/agent/auto-tools.json ~/.pi/agent/actors-tools.json
13
- ```
11
+ - `~/.pi/agent/recipes/*.json` is the highest-priority user recipe root and the operator-managed tool set.
12
+ - Recipes in that root are tools by default.
13
+ - `tool: false` keeps a user recipe file recipe-only.
14
+ - Packaged pi-actors recipes are the lower-priority standard library of declarative actor config components.
15
+ - Packaged or ad hoc recipes opt into tool exposure with `tool: true`.
16
+ - Recipe identity is the filename basename; `~/.pi/agent/recipes/docs_review.json` has id/tool name `docs_review`.
17
+
18
+ `~/.pi/agent/actors-tools.json` is legacy compatibility input. On startup, pi-actors migrates it into recipe files when possible, writes a migration report, and archives the source only when migration has no conflicts or invalid generated recipes.
19
+
20
+ Because the user recipe directory is sticky agent muscle memory, runtime launches update `usage.calls` and `usage.last_called` on user-owned recipe files. Use that evidence during focused cleanup passes: keep valuable tools, set `tool: false` for useful components that should leave the active tool surface, merge duplicates, or delete/archive low-value files. The extension does not maintain a failure counter and agents should not silently clean tools during unrelated work.
14
21
 
15
- If a short-lived `~/.pi/agent/tools.json` file exists from the early `pi-actors` rename window, copy that file instead:
22
+ `register_tool` is the preferred agent-facing mutation API. It creates, updates, and deletes recipe files in `~/.pi/agent/recipes`; agents do not need to edit the files directly for normal registration. Direct file edits are still valid for operators and advanced agents. Runtime behavior is reactive: file creation, deletion, or edits in the user recipe root trigger validation and tool-set refresh, with invalid recipes surfaced as diagnostics rather than silently ignored.
16
23
 
17
- ```bash
18
- cp ~/.pi/agent/tools.json ~/.pi/agent/actors-tools.json
24
+ Inspect the discovered registry with:
25
+
26
+ ```text
27
+ inspect target=recipes view=status
28
+ inspect target=recipes view=summary verbose=true
19
29
  ```
20
30
 
21
- No automatic rewrite is performed so operators can decide when to retire old config files.
31
+ The summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled.
22
32
 
23
33
  ## Registering Tools
24
34
 
25
35
  `register_tool` is the interactive API for listing, creating, updating, or deleting persistent tools. Call it without arguments to list registered tools.
26
36
 
27
37
  ```text
28
- register_tool name=transcribe_groq \
29
- description="Transcribe audio files using Groq Whisper API" \
30
- template="~/.pi/agent/skills/groq-stt/scripts/transcribe.mjs {file} {lang=ru} {model=whisper-large-v3-turbo}"
38
+ register_tool name=transcribe_audio \
39
+ description="Transcribe an audio file" \
40
+ template="~/bin/transcribe {file:path} {lang=ru} {model:string}"
31
41
  ```
32
42
 
33
43
  ```text
34
44
  register_tool name=call_subagent \
35
45
  description="Run pi as a non-interactive sub-agent" \
36
- template="pi -p --model {model=openai-codex/gpt-5.5} --no-tools {prompt}"
46
+ template="pi -p --model {model} --no-tools {prompt}" args="prompt:string,model:string"
37
47
  ```
38
48
 
39
49
  Use `update=true` to overwrite an existing tool. Omit `template` and co-located recipe fields during update to keep the previous execution binding.
@@ -47,31 +57,30 @@ Use `update=true` to overwrite an existing tool. Omit `template` and co-located
47
57
  ]
48
58
  ```
49
59
 
50
- For reusable actor workflows, register a small tool whose `template` points to an actor recipe instead of embedding the launch graph in the tool itself:
60
+ For reusable actor workflows, register a small tool whose `template` points to an existing actor recipe instead of embedding the launch graph in the tool itself:
51
61
 
52
62
  ```text
53
63
  register_tool name=docs_review \
54
64
  description="Start an async docs review actor" \
55
- template="docs-review.json" \
56
- args="scope:path,model:string=openai-codex/gpt-5.5"
65
+ template="docs-review" \
66
+ args="scope:path,model:string"
57
67
  ```
58
68
 
59
- This stores the recipe path in the registry as `template`. If `~/.pi/agent/recipes/docs-review.json` contains `async: true`, calling the tool starts a detached actor run and returns metadata immediately. If `async` is omitted or false, the same recipe runs foreground and returns normal tool output.
69
+ This writes or updates `~/.pi/agent/recipes/docs_review.json` with `tool: true` and a recipe-reference template. If the referenced recipe contains `async: true`, calling the tool starts a detached actor run and returns metadata immediately. If `async` is omitted or false, the same recipe runs foreground and returns normal tool output.
60
70
 
61
- When co-location is clearer than a separate file, the registry entry may include recipe fields directly beside tool metadata:
71
+ When co-location is clearer than a separate file, `register_tool` writes the recipe fields directly into the user recipe file:
62
72
 
63
73
  ```json
64
74
  {
65
- "review_docs": {
66
- "description": "Start an async docs review",
67
- "name": "review-docs",
68
- "async": true,
69
- "template": "pi -p --model openai-codex/gpt-5.5 --tools read,bash \"Review {scope}\""
70
- }
75
+ "description": "Start an async docs review",
76
+ "tool": true,
77
+ "async": true,
78
+ "args": ["scope:path", "model:string"],
79
+ "template": "pi -p --model {model} --tools read,bash \"Review {scope}\""
71
80
  }
72
81
  ```
73
82
 
74
- This is still not a cycle: `name` names the saved definition when it differs from the tool key, `async: true` selects detached run mode, and `template` remains the executable body. Co-located recipe entries must not define `tool`.
83
+ This is still not a cycle: the filename is the saved definition id, `async: true` selects detached run mode, and `template` remains the executable body.
75
84
 
76
85
  Delete a tool with `template=null`:
77
86
 
@@ -81,23 +90,22 @@ register_tool name=call_subagent template=null
81
90
 
82
91
  ## Stored Shape
83
92
 
84
- Tool names come from the top-level registry keys. Tool entries define `template`; it may be an inline command template, a template recipe JSON path/name, or the body of a co-located template recipe when `async` or entry-local `name` is present. Template entries keep `template` last, matching the command-template readability rule. The commands above persist entries like this:
93
+ Tool names come from recipe filenames in `~/.pi/agent/recipes`. Recipe files define `template`; it may be an inline command template, a command-template sequence, or an async recipe body. Template entries keep `template` last, matching the command-template readability rule. The commands above persist recipe files like this:
85
94
 
86
95
  ```json
87
96
  {
88
- "transcribe_groq": {
89
- "description": "Transcribe audio files using Groq Whisper API",
90
- "template": "~/.pi/agent/skills/groq-stt/scripts/transcribe.mjs {file} {lang=ru} {model=whisper-large-v3-turbo}"
91
- },
92
- "call_subagent": {
93
- "description": "Run pi as a non-interactive sub-agent",
94
- "template": "pi -p --model {model=openai-codex/gpt-5.5} --no-tools {prompt}"
95
- },
96
- "docs_review": {
97
- "description": "Start an async docs review actor",
98
- "args": ["scope:path", "model:string=openai-codex/gpt-5.5"],
99
- "template": "docs-review.json"
100
- }
97
+ "description": "Transcribe an audio file",
98
+ "tool": true,
99
+ "template": "~/bin/transcribe {file:path} {lang=ru} {model:string}"
100
+ }
101
+ ```
102
+
103
+ ```json
104
+ {
105
+ "description": "Run pi as a non-interactive sub-agent",
106
+ "tool": true,
107
+ "args": ["prompt:string", "model:string"],
108
+ "template": "pi -p --model {model} --no-tools {prompt}"
101
109
  }
102
110
  ```
103
111
 
@@ -106,7 +114,7 @@ Tool names come from the top-level registry keys. Tool entries define `template`
106
114
  When `args` is omitted, `pi-actors` derives tool parameters from placeholders in `template`:
107
115
 
108
116
  ```text
109
- template="~/bin/transcribe {file} {lang=ru} {model=whisper-large-v3-turbo}"
117
+ template="~/bin/transcribe {file:path} {lang=ru} {model:string}"
110
118
  ```
111
119
 
112
120
  The optional `args` field is an explicit placeholder declaration, matching the command-template standard. Untyped declarations remain valid:
package/index.ts CHANGED
@@ -40,6 +40,8 @@ const RESERVED_TOOL_NAMES = new Set([
40
40
  export default function toolRegistryExtension(pi: ExtensionAPI) {
41
41
  let runsAnimationInterval: NodeJS.Timeout | undefined;
42
42
  let runsNotifyTimeout: NodeJS.Timeout | undefined;
43
+ let recipeReloadTimeout: NodeJS.Timeout | undefined;
44
+ let recipeRootWatcher: FSWatcher | undefined;
43
45
  let stateRootWatcher: FSWatcher | undefined;
44
46
  const runDirWatchers = new Map<string, FSWatcher>();
45
47
  const observedRuns = new Map<string, Observability.RunObservedStatus>();
@@ -147,23 +149,54 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
147
149
  watchRunDir(ctx, `${RUN_STATE_ROOT}/${entry.name}`);
148
150
  }
149
151
  }
152
+ const closeRecipeWatcher = (): void => {
153
+ recipeRootWatcher?.close();
154
+ recipeRootWatcher = undefined;
155
+ if (recipeReloadTimeout) clearTimeout(recipeReloadTimeout);
156
+ recipeReloadTimeout = undefined;
157
+ };
158
+ const scheduleRecipeReload = (ctx: ExtensionContext): void => {
159
+ if (recipeReloadTimeout) clearTimeout(recipeReloadTimeout);
160
+ recipeReloadTimeout = setTimeout(() => {
161
+ runtime.loadTools(ctx);
162
+ ctx.ui.notify("Recipe tools refreshed from ~/.pi/agent/recipes", "info");
163
+ }, 150);
164
+ recipeReloadTimeout.unref?.();
165
+ };
166
+ const watchRecipeRoot = (ctx: ExtensionContext): void => {
167
+ const recipeRoot = Paths.getRecipeRoot();
168
+ if (recipeRootWatcher || !existsSync(recipeRoot)) return;
169
+ try {
170
+ recipeRootWatcher = watch(recipeRoot, () => scheduleRecipeReload(ctx));
171
+ recipeRootWatcher.on("error", () => {
172
+ recipeRootWatcher?.close();
173
+ recipeRootWatcher = undefined;
174
+ });
175
+ } catch {
176
+ // Watching is best-effort; restarting the session reloads recipe tools.
177
+ }
178
+ };
150
179
  const actorToolDefinitions = new Map<string, any>();
151
180
  const runtime = Runtime.createAutoToolsRuntime({
152
181
  configPath: CONFIG_PATH,
153
182
  exec: CommandTemplates.execCommandTemplate,
183
+ getActiveTools: () => pi.getActiveTools(),
154
184
  getAllTools: () => pi.getAllTools(),
155
185
  registerTool: (definition) => {
156
186
  actorToolDefinitions.set(definition.name, definition);
157
187
  pi.registerTool(definition);
158
188
  },
159
189
  reservedToolNames: RESERVED_TOOL_NAMES,
190
+ setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
160
191
  });
161
192
  pi.on("session_start", async (_event, ctx) => {
162
193
  await Temp.prepareExtensionTempDir(TEMP_DIR);
163
194
  runtime.loadTools(ctx);
164
195
  updateRunUi(ctx);
165
196
  closeRunWatchers();
197
+ closeRecipeWatcher();
166
198
  refreshRunWatchers(ctx);
199
+ watchRecipeRoot(ctx);
167
200
  if (runsAnimationInterval) clearInterval(runsAnimationInterval);
168
201
  runsAnimationInterval = setInterval(() => updateRunUi(ctx, false), 1000);
169
202
  runsAnimationInterval.unref?.();
@@ -172,6 +205,7 @@ export default function toolRegistryExtension(pi: ExtensionAPI) {
172
205
  if (runsAnimationInterval) clearInterval(runsAnimationInterval);
173
206
  runsAnimationInterval = undefined;
174
207
  closeRunWatchers();
208
+ closeRecipeWatcher();
175
209
  });
176
210
  pi.on("before_agent_start", async (event) => ({
177
211
  systemPrompt: `${event.systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
@@ -44,13 +44,15 @@ export function parseActorAddress(address: string): ActorAddress {
44
44
  const value = address.trim();
45
45
  if (value === "coordinator") return { kind: "coordinator" };
46
46
  const separator = value.indexOf(":");
47
- if (separator < 0) throw new Error(`Actor address must include kind: ${address}`);
47
+ if (separator < 0)
48
+ throw new Error(`Actor address must include kind: ${address}`);
48
49
  const kind = value.slice(0, separator) as ActorAddressKind;
49
50
  const rest = value.slice(separator + 1);
50
51
  switch (kind) {
51
52
  case "branch": {
52
53
  const [run, branch, ...extra] = rest.split("/");
53
- if (extra.length > 0) throw new Error(`Branch address has too many parts: ${address}`);
54
+ if (extra.length > 0)
55
+ throw new Error(`Branch address has too many parts: ${address}`);
54
56
  return {
55
57
  kind,
56
58
  value: assertToken(run || "", "branch run"),
@@ -74,14 +76,19 @@ export function formatActorAddress(address: ActorAddress): string {
74
76
  return `${address.kind}:${assertToken(address.value || "", `${address.kind} address`)}`;
75
77
  }
76
78
 
77
- function normalizeOptionalString(value: unknown, label: string): string | undefined {
79
+ function normalizeOptionalString(
80
+ value: unknown,
81
+ label: string,
82
+ ): string | undefined {
78
83
  if (value === undefined || value === null) return undefined;
79
84
  if (typeof value !== "string") throw new Error(`${label} must be a string`);
80
85
  const normalized = value.trim();
81
86
  return normalized || undefined;
82
87
  }
83
88
 
84
- function normalizeMetadata(value: unknown): Record<string, unknown> | undefined {
89
+ function normalizeMetadata(
90
+ value: unknown,
91
+ ): Record<string, unknown> | undefined {
85
92
  if (value === undefined || value === null) return undefined;
86
93
  if (typeof value !== "object" || Array.isArray(value)) {
87
94
  throw new Error("message metadata must be an object");
@@ -113,8 +120,14 @@ export function normalizeActorMessage(input: unknown): ActorMessage {
113
120
  ? { correlation_id: String(record.correlation_id) }
114
121
  : {}),
115
122
  ...(from ? { from: formatActorAddress(parseActorAddress(from)) } : {}),
116
- ...(record.metadata !== undefined ? { metadata: normalizeMetadata(record.metadata) } : {}),
117
- ...(record.reply_to !== undefined ? { reply_to: String(record.reply_to) } : {}),
118
- ...(record.summary !== undefined ? { summary: String(record.summary) } : {}),
123
+ ...(record.metadata !== undefined
124
+ ? { metadata: normalizeMetadata(record.metadata) }
125
+ : {}),
126
+ ...(record.reply_to !== undefined
127
+ ? { reply_to: String(record.reply_to) }
128
+ : {}),
129
+ ...(record.summary !== undefined
130
+ ? { summary: String(record.summary) }
131
+ : {}),
119
132
  };
120
133
  }