@llblab/pi-actors 0.57.1 → 0.58.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 +8 -0
- package/BACKLOG.md +13 -0
- package/CHANGELOG.md +7 -0
- package/README.md +7 -1
- package/dist/lib/file-state.d.ts +1 -0
- package/dist/lib/file-state.js +8 -2
- package/dist/lib/prompts.d.ts +2 -1
- package/dist/lib/prompts.js +6 -2
- package/dist/lib/recipes-discovery.d.ts +4 -0
- package/dist/lib/recipes-discovery.js +22 -0
- package/dist/lib/recipes-references.d.ts +1 -0
- package/dist/lib/recipes-references.js +1 -1
- package/dist/lib/recipes-usage.js +12 -9
- package/dist/lib/registry.d.ts +5 -1
- package/dist/lib/registry.js +199 -7
- package/dist/lib/tools-inspect.js +6 -3
- package/dist/lib/tools-register.js +1 -0
- package/dist/lib/tools-response.js +6 -1
- package/dist/skills/actors/SKILL.md +10 -1
- package/dist/skills/actors/references/persistent-tools.md +20 -2
- package/dist/skills/recipe-memory/SKILL.md +6 -0
- package/docs/README.md +1 -0
- package/docs/capability-memory.md +166 -0
- package/docs/inspection.md +3 -1
- package/docs/releasing.md +4 -0
- package/docs/tool-registry.md +20 -1
- package/lib/file-state.ts +7 -3
- package/lib/prompts.ts +7 -2
- package/lib/recipes-discovery.ts +26 -0
- package/lib/recipes-references.ts +1 -1
- package/lib/recipes-usage.ts +11 -8
- package/lib/registry.ts +195 -10
- package/lib/tools-inspect.ts +6 -3
- package/lib/tools-register.ts +1 -0
- package/lib/tools-response.ts +8 -1
- package/package.json +1 -1
- package/skills/actors/SKILL.md +10 -1
- package/skills/actors/references/persistent-tools.md +20 -2
- package/skills/recipe-memory/SKILL.md +6 -0
|
@@ -512,6 +512,10 @@ export function recipeRegistryNextActions(summary, view) {
|
|
|
512
512
|
if (summary.skill_recipe_catalog_partial === true) {
|
|
513
513
|
actions.push("inspect target=recipes view=imports verbose=true");
|
|
514
514
|
}
|
|
515
|
+
if ((Array.isArray(summary.retained) && summary.retained.length > 0) ||
|
|
516
|
+
(Array.isArray(summary.retained_diagnostics) && summary.retained_diagnostics.length > 0)) {
|
|
517
|
+
actions.push("inspect target=recipes view=summary verbose=true");
|
|
518
|
+
}
|
|
515
519
|
if (drafts.length > 0) {
|
|
516
520
|
actions.push("inspect target=recipes view=summary verbose=true");
|
|
517
521
|
const firstPath = typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
|
|
@@ -555,6 +559,7 @@ export function compactRecipeRegistry(summary) {
|
|
|
555
559
|
const active = count("active", summary.active), shadowed = count("shadowed", summary.shadowed);
|
|
556
560
|
const invalid = count("invalid", summary.invalid), disabled = count("disabled", summary.disabled);
|
|
557
561
|
const diagnostics = count("diagnostics", summary.diagnostics), drafts = count("drafts", summary.drafts);
|
|
562
|
+
const retained = count("retained", summary.retained);
|
|
558
563
|
const recommendations = count("recommendations", summary.recommendations);
|
|
559
564
|
const skillCatalogPartial = summary.skill_recipe_catalog_partial === true;
|
|
560
565
|
const currentPolicy = Array.isArray(summary.active)
|
|
@@ -564,7 +569,7 @@ export function compactRecipeRegistry(summary) {
|
|
|
564
569
|
? summary.next_actions
|
|
565
570
|
: [];
|
|
566
571
|
const hint = nextActions.length ? nextActionMetadata(nextActions[0], Limits.RUN_DELIVERY_MODEL_MAX_BYTES) : undefined;
|
|
567
|
-
const render = () => ({ recipes: "registry", active, drafts, shadowed, invalid, disabled,
|
|
572
|
+
const render = () => ({ recipes: "registry", active, drafts, retained, shadowed, invalid, disabled,
|
|
568
573
|
current_policy: currentPolicy, skill_catalog_partial: skillCatalogPartial, recommendations, diagnostics,
|
|
569
574
|
...(hint ? hint.selected : {}) });
|
|
570
575
|
return hint
|
|
@@ -19,6 +19,9 @@ Make a maintained capability a persistent agent-callable tool
|
|
|
19
19
|
Keep the same capability but narrow caller defaults
|
|
20
20
|
→ use register_tool from=<skill>/<recipe> defaults={...}
|
|
21
21
|
|
|
22
|
+
Retain a new named capability without exposing a tool
|
|
23
|
+
→ use register_tool expose=false with from or template
|
|
24
|
+
|
|
22
25
|
Register a trusted command directly
|
|
23
26
|
→ use register_tool template="..."
|
|
24
27
|
|
|
@@ -54,6 +57,12 @@ Run Control ≠ actor chat
|
|
|
54
57
|
|
|
55
58
|
A Skill Recipe is a maintained component addressed by `<skill>/<recipe>`. `spawn` creates a Run from a Recipe. `register_tool from=<skill>/<recipe>` persists compact logical delegation and activates from the resolved effective contract without copying, symlinking, or ambient re-resolution. A tool is callable in the current session only when activation evidence says `callable_now: true`.
|
|
56
59
|
|
|
60
|
+
Sync/async is an execution contract, not a usefulness or tool-exposure rule. A sync tool awaits its result; an async tool returns a Run launch receipt, with the eventual result delivered separately. A subagent-backed tool is a capability launcher, not a persisted live subagent. Register a stable, meaningfully parameterized capability when its name and contract save repeated planning; prefer one-off `spawn` over a redundant generic launcher or a new tool for every mission.
|
|
61
|
+
|
|
62
|
+
Use `register_tool expose=false` with a new name and one `template` or `from` source to retain a capability without requesting tool activation. Its `kind: retained_recipe`, `activation: not_requested` receipt is deliberate retention, not failed registration; it omits global callability flags because a same-named tool may already exist. Reuse the exact returned Recipe path through `spawn`, or deliberately expose it later with ordinary `register_tool from=<exact path>`. Retained update, delete and draft promotion are unsupported; do not hand-edit the live registry to bypass those guards.
|
|
63
|
+
|
|
64
|
+
Automatic retained lifecycle is not implemented. Current user-root tool discovery and automatic review actions are unchanged. `inspect target=recipes view=summary` distinguishes retained definitions under `recipes/library` from root tool candidates; verbose detail gives exact paths, resolved mode and invalid/disabled diagnostics. Storage is not proof of review, callability or a running Actor. `expose` is registration input, not a Recipe field or automatic-review action; do not invent retain/defer review commands. See [persistent tools](./references/persistent-tools.md) for current selection and activation proof.
|
|
65
|
+
|
|
57
66
|
`actors` owns generic Recipe/tool/Run mechanics. The owning capability Skill owns capability-specific selection and constraints. `swarm` owns multi-actor decomposition and integration methodology.
|
|
58
67
|
|
|
59
68
|
## Presentation Direction and Current Output
|
|
@@ -168,7 +177,7 @@ When a pi-actors operation fails:
|
|
|
168
177
|
3. Report resolver, registry, activation, Run, Trace, or Control truth exactly.
|
|
169
178
|
4. Retry only after the owning state is healthy.
|
|
170
179
|
|
|
171
|
-
Stop if spawn and registry resolve the same Recipe differently.
|
|
180
|
+
Stop if spawn and registry resolve the same Recipe differently. When tool exposure was requested, stop if registration persists but is not callable. A `retained_recipe` receipt with `activation: not_requested` instead permits ordinary Recipe reuse, not a claim of tool invocation. Stop if an operation cannot be proven through pi-actors surfaces.
|
|
172
181
|
|
|
173
182
|
Never recover by copying maintained Recipe args, defaults, Control, artifacts, or helper commands. Never hard-code a `{skill_dir}` replacement path. Never introduce `bash -lc`, `eval`, direct bundled-helper execution, or shell backgrounding to bypass resolution. Never call `spawn` and claim a tool call. Use [diagnostics](./references/diagnostics.md) for the safe next action.
|
|
174
183
|
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Use a persistent tool when the agent should call a trusted capability by name across turns or sessions. Use `spawn` instead for one-off Recipe execution.
|
|
4
4
|
|
|
5
|
+
## Select the capability, not the execution mode
|
|
6
|
+
|
|
7
|
+
Both sync and async Recipes can be tools. Sync invocation awaits the result; async invocation returns a launch receipt and the eventual result arrives separately. Neither synchrony nor a model-backed implementation establishes usefulness. A subagent-backed tool launches a capability, not a saved live subagent. Synchronous tools execute inline; non-singleton async calls launch Run executions, while singleton attachment follows its existing compatibility contract.
|
|
8
|
+
|
|
9
|
+
Prefer a stable purpose/name, caller-owned parameterized inputs, a clear result/acceptance boundary, understood effects and failure/cancellation limits, and meaningful leverage over existing tools or one-off `spawn`. Do not register every transient mission or a generic arbitrary-prompt alias merely to add a name. A successful registration is not permission for new effects, and a launch receipt is not successful task completion.
|
|
10
|
+
|
|
11
|
+
Current user-root Recipes are tool candidates by location; Skill components do not become tools automatically. Explicit create-only retention is supported through `register_tool expose=false`; it is separate from tool exposure. Automatic retained lifecycle is not implemented. Read-only `inspect target=recipes view=summary` reports retained counts for `recipes/library`; use `verbose=true` for exact `retained` paths, resolved mode, invalid/disabled state and diagnostics. Reuse a valid enabled entry through `spawn recipe=<exact path>` with required caller values, or deliberately register it with `from=<exact path>`. A stored definition is not review, activation or Run evidence; use `tool:<name>` for activation and `runtime view=runs` for running instances. A `library/<stem>` usage-lineage label is private accounting identity, not a callable name or Skill Recipe reference; reuse the exact file path from inspection. Do not populate or migrate the live registry manually. `expose` is a registration input, not a Recipe field; do not invent review actions or bypass unsupported retained mutations. Existing explicit `register_tool` works for both modes; automatic review remains under its existing policy. Launch counts and value-free structural review alone do not establish semantic purpose, a good name, or accepted useful reuse.
|
|
12
|
+
|
|
5
13
|
## Choose one source mode
|
|
6
14
|
|
|
7
15
|
```text
|
|
@@ -32,9 +40,19 @@ Resolution uses Pi's authoritative active-session Skill snapshot across every Sk
|
|
|
32
40
|
|
|
33
41
|
Use `description` to narrow agent-facing intent when useful. Every supplied default must name a caller-owned source arg and satisfy its type or enum. Never default runtime-owned origins.
|
|
34
42
|
|
|
43
|
+
## Retain without exposing a tool
|
|
44
|
+
|
|
45
|
+
Use `register_tool name=<name> expose=false from=<skill>/<recipe>` or a trusted `template` source to create `recipes/library/<name>.json`. A description is still required for authored command templates; source defaults/args/origins still belong to the maintained Recipe. This is create-only: retained `update=true`, null/empty deletion, `draft` mode and direct Recipe dependencies on managed disposable drafts are rejected, including Recipe-valued template leaves. A duplicate retained JSON/Markdown stem is a conflict; no older file is overwritten.
|
|
46
|
+
|
|
47
|
+
The `kind: retained_recipe`, `activation: not_requested` receipt confirms persistence/admission, not host activation or review. It returns the exact Recipe path and required/optional argument names, without global callability flags or default values. A same-named tool is left untouched. Use the exact returned path with `spawn`; if compact output omits the whole path, follow its read-only verbose Recipe inspection hint rather than guessing it. Retention records no launch and makes no self-contained-backup promise for referenced sources.
|
|
48
|
+
|
|
49
|
+
To expose a retained capability deliberately, call ordinary `register_tool name=<tool> from=<exact retained path>`, prove activation below, then call the actual tool. Deleting that admitted wrapper through the ordinary deletion path leaves the retained source; it does not stop active Runs. This is not a migration or safe-retention rule for old standalone tools.
|
|
50
|
+
|
|
51
|
+
Publication uses a complete atomic no-overwrite file link; unsupported filesystems refuse without an overwrite fallback. A storage, admission or lock-release failure can follow committed bytes. Inspect the retained inventory and preserve the original cause; do not blindly repeat the request or delete an uncertain file.
|
|
52
|
+
|
|
35
53
|
## Prove registration
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
When tool exposure was requested, read registration as a state transition, not one success word:
|
|
38
56
|
|
|
39
57
|
```text
|
|
40
58
|
resolved
|
|
@@ -46,7 +64,7 @@ resolved
|
|
|
46
64
|
→ callable_now
|
|
47
65
|
```
|
|
48
66
|
|
|
49
|
-
|
|
67
|
+
For tool exposure, when `callable_now` is true, call the actual generated tool and verify `launch_kind: "tool"` when provenance matters. When false, stop at the reported activation boundary and use tool/Recipe diagnosis. A Recipe spawn is not an activation test or tool invocation substitute.
|
|
50
68
|
|
|
51
69
|
Use:
|
|
52
70
|
|
|
@@ -7,6 +7,12 @@ description: Use only for internal automatic Recipe-memory review, diagnosis, or
|
|
|
7
7
|
|
|
8
8
|
Use this Skill only when automatic draft-memory or active-tool review needs diagnosis or bounded recovery. Normal agents must not spawn, register, specialize, or invoke `recipe-memory/draft-review` or `recipe-memory/tool-review`; they are package-owned internal reviewer components.
|
|
9
9
|
|
|
10
|
+
## Evidence limits
|
|
11
|
+
|
|
12
|
+
Automatic reviewers receive value-free contract shape, risk labels, usage counts, and batch-local equality groups, not executable content, descriptions, paths, or canonical names. Structural evidence and launch counts alone do not establish semantic purpose, good tool names, successful reuse, or accepted outcomes. Promotion preserves unchanged captured executable content; it does not generalize a one-off mission.
|
|
13
|
+
|
|
14
|
+
Explicit create-only retention is owned by `actors` through `register_tool expose=false`, not by these internal reviewers. Automatic retained lifecycle is not implemented. Current draft promote/discard and tool-review behavior are unchanged. Missing evidence is unknown; this guidance adds no retain/defer action, retry permission, automatic rewrite, or live migration. Do not bypass confidentiality or edit review state to simulate the future policy.
|
|
15
|
+
|
|
10
16
|
## Diagnose first
|
|
11
17
|
|
|
12
18
|
```text
|
package/docs/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Living index of all documentation in the `/docs` directory.
|
|
|
18
18
|
|
|
19
19
|
## Planned Contracts
|
|
20
20
|
|
|
21
|
+
- [capability-memory.md](./capability-memory.md) — Adopted separation of retained Recipes and deliberate tool exposure, with implemented read-only library inventory and isolated usage identity; explicit create-only persistence is implemented without tool activation, while retained update/delete and automatic lifecycle/migration remain separate
|
|
21
22
|
- [windows-process-observation.md](./windows-process-observation.md) — User-approved recovery preparation: passing inert Windows x64 MI child experiment, reviewed unadmitted raw codec and artifact preparation contract; broader profile evidence, unsigned asset distribution and default activation remain separate gates
|
|
22
23
|
- [run-supervision.md](./run-supervision.md) — Accepted future supervision contract and implemented AX/evidence slices, with same-logical-session file research and candidate-bound acceptance; the next selective AX/runtime release does not enable deadlines, independent owner-loss supervision or public completion streams
|
|
23
24
|
- [inactive-foundations-inventory.md](./inactive-foundations-inventory.md) — Reachability and package evidence for packaged-but-unactivated supervision, stream-numbering and native foundations, distinguishing reviewed runtime dependencies from optional ones; no removal decision, nothing activated
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Capability Memory and Tool Exposure
|
|
2
|
+
|
|
3
|
+
## Status and Scope
|
|
4
|
+
|
|
5
|
+
Adopted design direction: preserve reusable capabilities separately from exposing named agent tools. Sync/async remains an execution contract, not a maturity score or registration rule. This document adopts the separation and its safety constraints, not a new storage format, Recipe field, public API, automatic-review action, or migration of existing Recipes.
|
|
6
|
+
|
|
7
|
+
Explicit create-only retention and read-only library inventory are implemented. Automatic retention/exposure, retained update/deletion and live portfolio migration remain unimplemented. Current tool-root discovery, automatic promote/discard decisions and ordinary tool activation behavior remain unchanged. [BACKLOG.md](../BACKLOG.md) owns remaining work; this document owns design truth, not another task list.
|
|
8
|
+
|
|
9
|
+
## Three Independent Questions
|
|
10
|
+
|
|
11
|
+
- **Execution:** Does an invocation await the result inline or return a detached Run launch receipt?
|
|
12
|
+
- **Maturity:** Is this an unreviewed capture or a retained capability with stated validation evidence? Retention does not certify successful work, safety in every context, or generality.
|
|
13
|
+
- **Exposure:** Should the agent be offered this capability as a named tool? A retained Recipe may remain usable without occupying the tool interface.
|
|
14
|
+
|
|
15
|
+
A Recipe defines reusable execution. A tool is an agent-facing entrypoint to a capability. A Run is one concrete execution. Saving a definition, exposing a tool, activating it in the current host, launching it, and accepting its result remain separate facts.
|
|
16
|
+
|
|
17
|
+
The product goal is useful procedural memory with a selective tool interface, not the largest possible collection of callable names. More captures, launches, promotions, or registrations alone do not establish improvement.
|
|
18
|
+
|
|
19
|
+
## Current Baseline and Its Limits
|
|
20
|
+
|
|
21
|
+
- `lib/tools-local.ts` awaits synchronous Recipe execution, but an async tool calls `startRun` and returns launch metadata. The eventual Actor result is separate; tool registration does not itself launch an Actor.
|
|
22
|
+
- `lib/async-runs.ts` also permits ordinary non-singleton Recipes without `async: true` through the public `spawn` path. The flag does not divide all capabilities into foreground-only and background-only classes.
|
|
23
|
+
- `lib/tools-spawn.ts` captures inline-template launches as drafts with `async: true`. It does not capture maintained/file Recipe launches through that draft path. Filtering automatic tool promotion to sync alone would exclude this existing capture stream; ordinary foreground commands are not automatically captured by that mechanism.
|
|
24
|
+
- `lib/runtime.ts` discovers the user Recipe root with `defaultTool: true`; `lib/recipes-discovery.ts` exposes valid enabled entries by location, not by sync/async. Active Skill components already demonstrate reusable Recipes without automatic tool exposure.
|
|
25
|
+
- `lib/draft-review.ts` admits only promote/discard decisions. `lib/draft-sleep.ts` derives promotion from unchanged immutable captured Recipe content. Review does not generalize a hard-coded mission into a new parameterized capability.
|
|
26
|
+
- `lib/review-projection.ts` gives automatic reviewers value-free contract shape, risk labels, usage counts, and batch-local content-equality groups. It excludes commands, descriptions, argument names/values, canonical names, and paths. Those observations cannot by themselves establish a capability's purpose, a meaningful semantic name, or future task usefulness.
|
|
27
|
+
- `lib/recipes-usage.ts` records launches and lineage, not successful task completion or the coordinator's acceptance of a useful result. A launch count is not outcome evidence; a command exit code alone would not prove task success either.
|
|
28
|
+
|
|
29
|
+
These are limits on the conclusions the current mechanism supports, not permission to bypass its confidentiality projection or discard its transaction protections.
|
|
30
|
+
|
|
31
|
+
## Read-Only Inventory Checkpoint
|
|
32
|
+
|
|
33
|
+
Retained definitions belong under `~/.pi/agent/recipes/library`; the tool root remains its existing direct `.json`/`.md` files. This separation also fails safely on older hosts whose nonrecursive tool discovery ignores the library directory, unlike an unknown opt-out field that an older host could ignore. No new Recipe field or logical-reference namespace is introduced.
|
|
34
|
+
|
|
35
|
+
Use the same management surface:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
inspect target=recipes view=summary
|
|
39
|
+
inspect target=recipes view=summary verbose=true
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Compact output adds `retained` separately from root `active` candidates and drafts. Verbose/details contains `retained` rows with exact file paths, execution mode when the definition resolves, invalid/disabled state and diagnostics, plus `retained_diagnostics`. Root and retained definitions resolve in the same active Skill context. This read does not create directories, mutate Recipes/usage, register tools, or launch Actors. A stored row is not evidence of review, a callable tool, or a running Actor. Empty/missing library is an empty inventory, not an instruction to initialize one.
|
|
43
|
+
|
|
44
|
+
A valid enabled row's exact file path can be reused through existing `spawn recipe=<path>` with its required caller values, or explicitly specialized through existing `register_tool from=<path>`. Invalid/disabled entries remain visible for diagnosis, not recommended for execution. Running instances remain under `inspect target=runtime view=runs` and `run:<id>`; tool activation/schema remains under `tool:<name>`. The inventory is not an alternate Run or activation registry.
|
|
45
|
+
|
|
46
|
+
Use the explicit retention operation below rather than hand-populating the live registry or moving existing tools. Source/packed acceptance is separate from reloading an already running host; a loaded older tool schema does not acquire the new input automatically.
|
|
47
|
+
|
|
48
|
+
## Explicit Persistence Contract
|
|
49
|
+
|
|
50
|
+
Status: Implemented for explicit create-only retention; automatic lifecycle remains separate.
|
|
51
|
+
|
|
52
|
+
The existing `register_tool` persistence owner accepts an optional Boolean **input** `expose`. This is not a Recipe field or a new tool/registry. `kind: retained_recipe` and `activation: not_requested` distinguish successful retention from failed tool activation.
|
|
53
|
+
|
|
54
|
+
- **Omitted or true:** Preserve current tool-root persistence, source modes, update/delete behavior, activation and callability proof.
|
|
55
|
+
- **False:** Save a new named definition under `recipes/library/<name>.json` without requesting host registration, changing the active-tool set, or launching work. A name and exactly one `template` or `from` source are required. Apply existing name/reserved-name and effective typed-argument/default admission; sync/async is not a selection filter.
|
|
56
|
+
- **References, not relocation:** `from` persists the existing compact logical/file delegation, retaining the source's origins, imports, async, artifacts and Control. A retained wrapper is not a self-contained backup of its source. Reject direct references whose selected source is in managed disposable drafts rather than create an entry knowingly dependent on draft disposal. Apply the same owning resolver/lifetime check to Recipe-valued leaves in authored template graphs, not just the `from` spelling. This is not a transitive lifetime guarantee for arbitrary external files or their dependencies. `template` authors new content at its final library location. No existing Recipe is moved, rewritten or generalized.
|
|
57
|
+
- **Create-only first slice:** Retained `update=true`, null/empty-template deletion, and `draft` source mode fail before persistence. They need explicit source-preservation, CAS/recovery and dependency contracts before becoming supported; do not fall back to active-tool mutation. A duplicate retained stem in either JSON or Markdown is a conflict, not permission to shadow or overwrite it. A tool-root entry with the same stem remains a distinct entrypoint and is not modified.
|
|
58
|
+
|
|
59
|
+
Explicit flow (supply the capability's required caller values):
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
register_tool name=changes_review from=swarm/lens-review expose=false
|
|
63
|
+
inspect target=recipes view=summary verbose=true
|
|
64
|
+
spawn recipe=<exact retained path> values=<caller-owned inputs>
|
|
65
|
+
register_tool name=changes_review from=<exact retained path>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The last step deliberately creates a compact wrapper in the ordinary tool root. It does not copy the executable graph or move the retained definition. `callable_now` and actual tool invocation still need the existing proof. Removing an admitted exposed wrapper through the existing deletion path leaves the retained source intact; this does not turn deletion of an old standalone tool into safe retention. Active Runs retain their original execution contract and are not stopped by wrapper withdrawal.
|
|
69
|
+
|
|
70
|
+
### Receipt and Failure Truth
|
|
71
|
+
|
|
72
|
+
A successful retain receipt must distinguish **retention** from failed activation: `kind: retained_recipe`, `result: retained`, `persisted: true`, `resolved: true`, `validated: true`, and `activation: not_requested`, plus the exact retained Recipe path and effective required/optional argument names. It must not claim a global `callable_now: false` or `active_tool: false`: a separately exposed tool with the same name may already be callable. The retained branch never calls the host registration/activation ports, and the receipt's next action is read-only Recipe inspection rather than repair/reload advice. This is an extension of the receipt contract, not reconstruction of missing host state.
|
|
73
|
+
|
|
74
|
+
Exact paths and selected metadata must use the existing whole-value YAML/omission budget, with full structured details preserved. No executable body, secret-bearing defaults, source installation root or invented semantic assessment is added to the receipt. Storage/validation do not prove review or useful task completion.
|
|
75
|
+
|
|
76
|
+
Preflight validates mode/type/source/size and resolves the effective contract before creating library state. The serialized candidate obeys the existing 1 MiB per-Recipe bound even when admission uses an in-memory authored candidate. Operation selection preserves the existing name/source guard order and releases its tool-name reservation before retention locks are acquired. Persistence uses the existing canonical mutation-lock/atomic-write owner, with root → library → target ordering, regular non-symlink files, root identity/containment rechecks and JSON/Markdown conflict detection at the commit boundary. Exclusive publication links complete staged bytes without overwriting a competing creator; an unsupported filesystem refuses without a replacement fallback. Do not infer filesystem sandboxing or hostile-writer exclusion from cooperative locks.
|
|
77
|
+
|
|
78
|
+
The first slice has no prior retained bytes to overwrite. It must preserve any competing/pre-existing entry and re-admit its own exact persisted candidate before returning success. Rollback may remove only its own proven unchanged candidate under the original root/target fences; an uncertain replacement, release or notification failure preserves effect uncertainty and original causes rather than blindly deleting or replaying. The retained failure boundary encloses the whole root-lock operation, including post-publication release; exact original or aggregate rejections remain its private cause. Ordinary registration keeps its existing rollback and callback behavior; the retained branch is not a reason to restyle or reorder unrelated guards.
|
|
79
|
+
|
|
80
|
+
Retention itself records no launch, success, review or fabricated usage. There is no automatic expiry, background retention loop or live portfolio migration in this slice. Aggregate retention bounds and expiry/disposal remain required before future automatic retention, not a reason to impose an arbitrary new tool-count quota now.
|
|
81
|
+
|
|
82
|
+
### One Usage Owner, Distinct Entrypoints
|
|
83
|
+
|
|
84
|
+
Library launches use the existing `.usage` owner under the parent Recipe root, not a second ledger root inside `library`. Private ledger identity is `library/<stem>` for retained entries and the existing stem for tool-root entries; this is not a new Recipe name or public `<skill>/<recipe>` reference. A root wrapper and retained definition may share a filename without merging their launch counters or rewriting each other's revision history. JSON/Markdown representations follow the same rule. Neither creating a definition nor exposing its wrapper is a launch.
|
|
85
|
+
|
|
86
|
+
Automatic same-source rename inference stays within the retained namespace or within the existing root/draft namespace; disappearance plus matching content cannot silently transfer lineage across the new boundary. Any future deliberate transition must use the existing explicit lineage move/transaction owner. Do not migrate pre-existing nested/legacy ledgers by guessing their ownership or manufacture missing counts; preserve them for separately admitted diagnosis/migration.
|
|
87
|
+
|
|
88
|
+
The management result is one coherent path: **save deliberately → find through existing Recipe inspection → reuse the exact Recipe reference → expose explicitly when useful → withdraw only the wrapper when appropriate**. This first contract does not adopt automatic retention/exposure, semantic review, retained updates/deletion, or conversion of existing tools/drafts.
|
|
89
|
+
|
|
90
|
+
## Adopted Lifecycle Direction
|
|
91
|
+
|
|
92
|
+
The following is a conceptual lifecycle, not currently supported review syntax:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
Unreviewed capture
|
|
96
|
+
├─ Discard under an admitted retention policy
|
|
97
|
+
├─ Defer when evidence is insufficient
|
|
98
|
+
└─ Retain as a reviewed Recipe with explicit evidence limits
|
|
99
|
+
├─ Reuse by the existing Recipe execution path
|
|
100
|
+
└─ Deliberately expose as a named tool
|
|
101
|
+
└─ Resolve → validate → persist → activate → prove callable_now
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Sync and async Recipes can be retained or exposed. Do not leave a reviewed capability permanently classified as an unreviewed draft merely because it is async. Do not expose a synchronous one-off merely because it returns inline. Demotion of a useful tool should be able to withdraw exposure without erasing the retained capability or pretending it has never been reviewed.
|
|
105
|
+
|
|
106
|
+
Insufficient semantic evidence is **unknown**, not a low usefulness score or an instruction to delete. The future review contract must represent retain/defer separately from expose/discard and explain what evidence supports each decision. Deferral is not unlimited retention: the implementation contract must bound the retained portfolio and distinguish evidence deferral from deterministic safety rejection or explicitly admitted expiry/disposal. Missing usefulness evidence alone is not disposal authority; these limits do not weaken existing invalid/secret-touching rejection or grant new deletion permission. The exact persisted representation and review vocabulary require a concrete implementation contract; the diagram does not add action names to today's validators.
|
|
107
|
+
|
|
108
|
+
No automatic generalization or executable rewrite follows from review. Authoring a reusable capability from a one-off capture is a separate authorized change with explicit inputs, effects, and validation. Preserve the existing rule that an automatic executor derives unchanged executable content from its trusted capture.
|
|
109
|
+
|
|
110
|
+
## When a Tool Earns Its Name
|
|
111
|
+
|
|
112
|
+
Tool exposure is justified by a stable agent-facing contract, not workload duration or whether a model runs inside it:
|
|
113
|
+
|
|
114
|
+
- **Purpose:** A recognizable recurring outcome and a name/description supported by semantic evidence, rather than an opaque content-group identity or a guessed task label.
|
|
115
|
+
- **Inputs:** Caller-owned typed arguments and useful parameterization; no accidental commitment to a single captured prompt, temporary file, credential, installation path, or operator-specific mission.
|
|
116
|
+
- **Result:** A clear deliverable and acceptance boundary. For async tools, explicitly distinguish the launch receipt from terminal completion and the requested result.
|
|
117
|
+
- **Effects:** Understandable filesystem/network/service effects, authority requirements, and failure or cancellation limits. Registration does not grant new permissions; launch failure may leave effects, and a stop request is not proof of scope closure.
|
|
118
|
+
- **Leverage:** Meaningful reduction in repeated planning or orchestration compared with existing tools and ordinary Recipe spawning. A generic arbitrary-prompt launcher that merely aliases `spawn` usually adds little.
|
|
119
|
+
- **Evidence:** Available validation, actual reuse, outcome acceptance, and uncertainty appropriate to the capability. Do not invent a universal call-count threshold or equate repeated launch attempts with successful reuse.
|
|
120
|
+
|
|
121
|
+
Examples are illustrative, not new registered tools:
|
|
122
|
+
|
|
123
|
+
- A parameterized async change-review capability can be a useful named tool: it launches a fresh reviewer Run and later returns the declared report.
|
|
124
|
+
- A one-off synchronous command with a fixed task path may be worth neither retaining nor exposing.
|
|
125
|
+
- A useful but infrequent pipeline may stay retained and be spawned intentionally without becoming a standing tool.
|
|
126
|
+
- A long-lived playback service may earn a launcher tool and declared Control without becoming a synchronous function.
|
|
127
|
+
|
|
128
|
+
A subagent-backed tool is a **capability launcher**, not a persisted live subagent or a handle to an old Run. Ordinary invocations create distinct executions; singleton compatibility and reuse remain owned by the existing Run contract. Registering every transient subagent, Run ID, or mission as a new tool defeats this distinction.
|
|
129
|
+
|
|
130
|
+
## Review Evidence and Confidentiality
|
|
131
|
+
|
|
132
|
+
Keep automatic review read-only and value-free. Structural evidence can support eligibility checks, contract-shape comparisons, and bounded prioritization; it is not a substitute for understanding the task. Content-equality groups prove only the equality represented by the capture, not semantic equivalence or interchangeability.
|
|
133
|
+
|
|
134
|
+
A future semantic evidence channel must have a separately reviewed provenance, privacy, size, and authority contract before use. Raw commands, prompts, descriptions, paths, outcomes, and purportedly sanitized summaries are not automatically safe reviewer inputs. An operator-reviewed capability description or accepted-outcome record is a possible design input, not an available trusted carrier today. Do not let actor-authored text become admission authority or let missing evidence force an irreversible discard.
|
|
135
|
+
|
|
136
|
+
Evaluate memory quality by useful accepted reuse and reduced reconstruction cost, balanced against tool-selection ambiguity, stale contracts, runtime/review cost, and accidental exposure. Those are evaluation goals, not currently collected metrics or a requirement to add telemetry. Validate the policy on bounded controlled fixtures before claiming better agent behavior.
|
|
137
|
+
|
|
138
|
+
## Ownership and Migration Constraints
|
|
139
|
+
|
|
140
|
+
Extend existing owners instead of creating a second execution kernel, tool registry, Recipe resolver, or parallel task list:
|
|
141
|
+
|
|
142
|
+
- **Recipe resolution/discovery:** Preserve exact Skill/file identity, imports, runtime-owned origins, validation, and the distinction between retained definitions and tool candidates. A non-tool library must remain reachable through an admitted existing Recipe execution path.
|
|
143
|
+
- **Registry/runtime:** Own explicit exposure, host activation, withdrawal limitations, and `callable_now` evidence. Existing explicit `register_tool` intent remains valid for sync and async Recipes; the target policy does not silently prohibit either.
|
|
144
|
+
- **Draft/tool review and transactions:** Own evidence-limited recommendations, immutable captures, CAS, quarantine, journals, recovery, and admission of unchanged content. Retain/defer and withdrawal need explicit replay-safe transitions before changing automatic policy.
|
|
145
|
+
- **Usage/lineage:** Preserve revision and lifetime evidence across retention, exposure, withdrawal, and rollback without counting a migration as a launch or inventing successful outcomes.
|
|
146
|
+
- **Owning Skills:** Explain only implemented operations and approved selection guidance. Do not tell an agent to use a future library path, metadata flag, review action, or public command.
|
|
147
|
+
|
|
148
|
+
The retained directory, read-only discovery and initial create-only persistence/receipt contract are implemented. Retained updates/deletion, automatic transitions, aggregate retention bounds and the semantic evidence channel remain separate unresolved implementation choices. Prefer the smallest extension of current owners; do not add a new Recipe exposure field, database, service, or eager loading of every retained Recipe into the model context. Moving a Recipe can change relative imports and `{recipe_dir}` semantics: explicit retention needs an admitted source-preservation rule before any write, not a blind move from drafts or the tool root.
|
|
149
|
+
|
|
150
|
+
Existing user tools must not be silently demoted, deleted, renamed, generalized, or re-registered during adoption. Preserve executable bytes, delegated logical references, defaults, imports, Control, artifacts, runtime origins, usage, and recovery evidence. Prepare a read-only classification first; exact portfolio changes require an admitted migration contract and the authority applicable to that portfolio. Source tests or packaging do not authorize changes to the operator's live registry or reload the running host.
|
|
151
|
+
|
|
152
|
+
## Acceptance Properties
|
|
153
|
+
|
|
154
|
+
- **Orthogonality:** A useful async capability can be exposed; an unsuitable sync capture is not automatically exposed. Either mode can be retained without becoming a tool.
|
|
155
|
+
- **Evidence limits:** Structurally similar but semantically different Recipes cannot acquire an asserted purpose/usefulness solely from the same review shape. Unavailable semantic evidence stays explicit and can defer a decision without destructive loss.
|
|
156
|
+
- **Identity and replay:** Retain/expose/withdraw/recover preserve source and lineage, reject stale/conflicting plans, and do not create promotion/demotion churn or duplicate executable copies as new capabilities.
|
|
157
|
+
- **Invocation truth:** Explicit sync and async registration still works. Actual tool calls remain distinct from `spawn`; launch, eventual result, singleton attachment, and current callability retain their original meanings.
|
|
158
|
+
- **Compatibility:** Source and packed-package journeys preserve existing safety and explicit-registration behavior before any automatic-policy migration. No test writes into the operator's registry.
|
|
159
|
+
- **Product evidence:** Controlled evaluation must distinguish accepted useful reuse and selection cost from mere growth in Recipe/tool counts. Until measured, improved "muscle memory" remains a design hypothesis, not a shipped performance claim.
|
|
160
|
+
|
|
161
|
+
## Related
|
|
162
|
+
|
|
163
|
+
- [Tool Registry](./tool-registry.md) — Current registration, discovery, activation, and automatic-review behavior.
|
|
164
|
+
- [Skill-Owned Recipe Components](./recipe-library.md) — Existing reusable components without automatic tool exposure.
|
|
165
|
+
- [Template Recipes](./template-recipes.md) — Recipe and execution contracts.
|
|
166
|
+
- [Project Backlog](../BACKLOG.md) — Canonical remaining implementation and acceptance work.
|
package/docs/inspection.md
CHANGED
|
@@ -95,12 +95,14 @@ inspect target=recipes view=imports verbose=true
|
|
|
95
95
|
inspect target=recipes view=reviews verbose=true
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
- `status` reports registry/watch state, active/shadowed/invalid/disabled user Recipes, active Skill components, drafts, usage, and diagnostics.
|
|
98
|
+
- `status` reports registry/watch state, active/shadowed/invalid/disabled user Recipes, active Skill components, drafts, retained-library definitions, usage, and diagnostics.
|
|
99
99
|
- `summary` returns the compact discovery-oriented projection of the same current context. `registry` is the same inspection path under a second accepted view name: with identical `verbose` and other inputs it returns identical facts, details and rendering, so only `verbose` selects compact text versus structured output. The compact label stays `recipes: registry` for both names.
|
|
100
100
|
- `doctor` diagnoses one canonical `<skill>/<recipe>` identity when `identity` is provided; without an identity it reports registry remediation evidence.
|
|
101
101
|
- `imports` exposes active Skill namespaces and component import inventory for resolution diagnosis.
|
|
102
102
|
- `reviews` exposes bounded automatic draft/tool review state and recovery guidance.
|
|
103
103
|
|
|
104
|
+
The compact Recipe catalog adds `retained` as a separate count for direct `.json`/`.md` definitions under `recipes/library`. Details/verbose includes `retained` rows (exact path, source_kind=user_retained_recipe, tool=false, resolved async mode, invalid/disabled state and diagnostics) and `retained_diagnostics`. Both root and library use the active session Skill context. Storage placement does not certify review, current callability or Run state; invalid/disabled definitions remain diagnostic. The missing library is empty, without initialization. Inspection performs no retention write, activation or launch. Valid enabled exact paths remain ordinary explicit-file references for `spawn` or deliberate `register_tool from` specialization. Inspection adds no mutation or logical-reference syntax; use explicit `register_tool expose=false` for supported create-only retention, and see [capability memory](./capability-memory.md#read-only-inventory-checkpoint).
|
|
105
|
+
|
|
104
106
|
`identity` is accepted only with `view=doctor`. Skill inventory is fail-soft diagnostic evidence; exact launch and registration still resolve against the immutable active-session context.
|
|
105
107
|
|
|
106
108
|
## Tool Targets
|
package/docs/releasing.md
CHANGED
|
@@ -19,6 +19,10 @@ Simple tools/registration fixtures bind original runner births and actual `close
|
|
|
19
19
|
|
|
20
20
|
Implementation and native CI acceptance are tracked in BACKLOG until the complete sharded candidate passes.
|
|
21
21
|
|
|
22
|
+
## Local Validation
|
|
23
|
+
|
|
24
|
+
Run `npm run validate` and `npm run audit:dependencies` with Node24 selected, including after any login-shell setup. When launching validation from an Actor, clear `PI_ACTORS_DELIVERY_OWNER_ID`, `PI_ACTORS_DELIVERY_PARENT_RUN`, `PI_ACTORS_DELIVERY_PARENT_INSTANCE` and `PI_ACTORS_DELIVERY_PARENT_STATE_DIR` only in the test workload's environment. Otherwise independent SDK fixture coordinators may inherit the validation Actor's completion lineage and wait for the wrong coordinator. Keep the launcher's own lineage intact; this isolation changes neither product defaults nor test cases, the standard agent-directory preload or watchdogs.
|
|
25
|
+
|
|
22
26
|
## One-time npm Trusted Publisher setup
|
|
23
27
|
|
|
24
28
|
Configure the existing public package `@llblab/pi-actors` on npmjs.com with a GitHub Actions Trusted Publisher using these exact values:
|
package/docs/tool-registry.md
CHANGED
|
@@ -38,7 +38,20 @@ register_tool name=repo_check template=null
|
|
|
38
38
|
|
|
39
39
|
An empty `template` string is the equivalent deletion form. Do not delete registry files manually. Host-visible dynamic definitions may remain visible until reload because Pi cannot unregister them, but extension-local lookup rejects the deleted definition immediately. Name collisions for creation or replacement require `update=true`. Invalid content fails before active mutation.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Ordinary tool registration resolves the effective delegated contract before persistence, then reports logical `source`, effective `required_args` / `optional_args`, and distinct `persisted`, `registry_active`, `host_registered`, `active_tool`, and `callable_now` states. A callable result points to the actual generated tool as the next action. An uncallable result names its `activation_boundary` and status/doctor action without suggesting spawn as a substitute. Diagnose one maintained source with `inspect target=recipes view=doctor identity=<skill>/<recipe>`; diagnose final activation, source, schema summary, and separate spawn/tool usage with `inspect target=tool:<name> view=status`. Treat the tool as callable in the current session only when `callable_now` is true; persistence alone is not activation proof. Ordinary tool-registration results omit raw persisted paths and executable template/config payloads; retained receipts deliberately return the reusable file path, never an executable body.
|
|
42
|
+
|
|
43
|
+
## Retain a New Capability Without Tool Exposure
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
register_tool name=repo_check expose=false template="make check" description="Run repository checks"
|
|
47
|
+
register_tool name=review_changes expose=false from=swarm/lens-review
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This create-only mode writes `recipes/library/<name>.json`, not a tool-root entry. It accepts a new name and one template/from source; retained update, null/empty deletion, draft promotion and direct references to managed disposable drafts (including Recipe-valued template leaves) are rejected. JSON/Markdown stem conflicts, invalid source/defaults, oversized candidates and unsafe storage fail without activating a tool. Existing tools with the same name are untouched. Publication is a complete atomic no-overwrite file link; unsupported filesystems refuse rather than falling back to replacement.
|
|
51
|
+
|
|
52
|
+
A `kind: retained_recipe`, `activation: not_requested` receipt confirms admission/persistence and returns the exact Recipe path with required/optional argument names. It omits global callability flags and defaults: a separately exposed same-named tool might already be callable. The next hint is read-only Recipe inspection, not activation repair. Reuse the exact retained file with `spawn recipe=<path>`, or expose a compact wrapper deliberately with ordinary `register_tool name=<tool> from=<path>`. Deleting an admitted wrapper through the existing deletion path does not delete its retained source or stop active Runs. No automatic retention, review decision, source relocation or live migration is added.
|
|
53
|
+
|
|
54
|
+
A post-persistence admission, storage, publication-cleanup or lock-release failure can leave committed bytes; original causes are retained and uncertain files are not blindly deleted. Inspect the inventory before considering any retry. Ordinary tool registration keeps its existing rollback and activation contract.
|
|
42
55
|
|
|
43
56
|
## Resolution
|
|
44
57
|
|
|
@@ -66,6 +79,12 @@ Async registered tools add runtime-owned optional invocation parameters after re
|
|
|
66
79
|
|
|
67
80
|
These parameters are not Recipe-authored args. Singleton tools do not expose `run_id` because their canonical Run identity is owned by the Skill Recipe.
|
|
68
81
|
|
|
82
|
+
## Retention Versus Exposure — Adopted Direction
|
|
83
|
+
|
|
84
|
+
The [capability-memory design](./capability-memory.md) separates preserving a reviewed reusable Recipe from offering it as a named tool. Either sync or async execution can qualify; a subagent-backed tool launches a capability, not a persisted live subagent. Async calls return a launch receipt, not the eventual work result.
|
|
85
|
+
|
|
86
|
+
The explicit create-only slice is implemented; the automatic lifecycle remains a target. The user root still exposes valid enabled Recipes by location, automatic draft review still chooses promote/discard, and ordinary `register_tool` remains available for both modes. `expose=false` explicitly retains new template/from definitions without activation. Read-only `inspect target=recipes view=summary` lists library definitions separately from tool candidates; verbose detail provides exact paths, mode and invalid/disabled diagnostics. No Recipe exposure field or retain/defer review action is added. A library row is storage intent, not proof of review, callability or a running Actor. Structural review and launch counts alone cannot prove semantic usefulness or accepted successful reuse. Remaining integration belongs to [BACKLOG.md](../BACKLOG.md).
|
|
87
|
+
|
|
69
88
|
## Automatic Review
|
|
70
89
|
|
|
71
90
|
Automatic draft/tool review remains silent and mechanically fenced:
|
package/lib/file-state.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { existsSync, linkSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { basename, dirname, join, parse, resolve } from "node:path";
|
|
12
12
|
|
|
@@ -297,14 +297,18 @@ function replaceFileWithRetry(source: string, target: string): void {
|
|
|
297
297
|
export function writeTextAtomic(
|
|
298
298
|
path: string,
|
|
299
299
|
content: string,
|
|
300
|
-
options: { onBeforeReplace?(): void } = {},
|
|
300
|
+
options: { exclusive?: boolean; onBeforeReplace?(): void } = {},
|
|
301
301
|
): void {
|
|
302
302
|
mkdirSync(dirname(path), { recursive: true });
|
|
303
303
|
const tempPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
304
304
|
try {
|
|
305
305
|
writeFileSync(tempPath, content, "utf8");
|
|
306
306
|
options.onBeforeReplace?.();
|
|
307
|
-
|
|
307
|
+
if (options.exclusive === true) {
|
|
308
|
+
// Publish a complete new file atomically without clobbering an uncooperative creator.
|
|
309
|
+
linkSync(tempPath, path);
|
|
310
|
+
unlinkSync(tempPath);
|
|
311
|
+
} else replaceFileWithRetry(tempPath, path);
|
|
308
312
|
} catch (error) {
|
|
309
313
|
try {
|
|
310
314
|
unlinkSync(tempPath);
|
package/lib/prompts.ts
CHANGED
|
@@ -7,15 +7,18 @@
|
|
|
7
7
|
export const REGISTER_TOOL_DESCRIPTION =
|
|
8
8
|
"Register a persistent custom tool from one maintained Recipe, command template, or captured draft. " +
|
|
9
9
|
"Use from for Recipe specialization, template for trusted commands, or draft for promotion. " +
|
|
10
|
+
"Set expose=false to retain a new template/from Recipe without tool activation. " +
|
|
10
11
|
"Definitions persist under ~/.pi/agent/recipes; activation is reported separately.";
|
|
11
12
|
|
|
12
13
|
export const REGISTER_TOOL_PROMPT_SNIPPET =
|
|
13
|
-
"
|
|
14
|
+
"Persist command templates or Recipes with register_tool; use expose=false to retain without tool activation";
|
|
14
15
|
|
|
15
16
|
export const REGISTER_TOOL_GUIDELINES = [
|
|
16
17
|
"Use register_tool from=<skill>/<recipe> with defaults={...} to specialize a maintained Recipe without copying its contract.",
|
|
17
18
|
"Use register_tool template only for trusted command templates, and use register_tool draft only for captured draft promotion.",
|
|
18
|
-
"
|
|
19
|
+
"Use register_tool expose=false to retain a new named template/from Recipe without activation; update, delete and draft promotion are unsupported in this mode.",
|
|
20
|
+
"A register_tool retained_recipe receipt with activation=not_requested is deliberate retention, not failed activation; reuse its exact Recipe path through spawn.",
|
|
21
|
+
"When register_tool requests tool exposure, trust callable_now and activation; persistence alone is not callability.",
|
|
19
22
|
'Set template=null or template="" in register_tool to delete a persisted tool.',
|
|
20
23
|
"Set update=true in register_tool to overwrite an existing tool registration.",
|
|
21
24
|
];
|
|
@@ -42,6 +45,8 @@ export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
|
|
|
42
45
|
"Optional caller-owned defaults. Keys and values must satisfy the effective source or command-template argument contract.",
|
|
43
46
|
draft:
|
|
44
47
|
"Promote a captured draft Recipe path from ~/.pi/agent/recipes/drafts. This is a source mode; do not combine it with from or template.",
|
|
48
|
+
expose:
|
|
49
|
+
"Set false to retain a new named template/from Recipe in recipes/library without registering or activating a tool. Default true preserves ordinary tool registration. Retained update, delete and draft promotion are unsupported.",
|
|
45
50
|
async:
|
|
46
51
|
"Set true for a co-located async template recipe. Omit for ordinary command templates or file-backed recipe references.",
|
|
47
52
|
template:
|
package/lib/recipes-discovery.ts
CHANGED
|
@@ -756,6 +756,32 @@ function templatePreview(value: unknown): string | undefined {
|
|
|
756
756
|
: rendered;
|
|
757
757
|
}
|
|
758
758
|
|
|
759
|
+
export function inventoryRetainedRecipes(
|
|
760
|
+
recipeRoot: string,
|
|
761
|
+
resolutionContext?: RecipeResolutionContext,
|
|
762
|
+
): { recipes: Array<Record<string, unknown>>; diagnostics: string[] } {
|
|
763
|
+
const discovery = discoverRecipeSources([
|
|
764
|
+
{ root: join(recipeRoot, "library"), resolutionContext },
|
|
765
|
+
]);
|
|
766
|
+
return {
|
|
767
|
+
recipes: [...discovery.active.values()]
|
|
768
|
+
.map((entry) => ({
|
|
769
|
+
id: entry.id,
|
|
770
|
+
path: entry.path,
|
|
771
|
+
source_kind: "user_retained_recipe",
|
|
772
|
+
tool: false,
|
|
773
|
+
invalid: entry.invalid,
|
|
774
|
+
disabled: entry.disabled,
|
|
775
|
+
...(entry.config ? { async: entry.config.async === true } : {}),
|
|
776
|
+
...(entry.config?.description ? { description: entry.config.description } : {}),
|
|
777
|
+
...(entry.diagnostics.length ? { diagnostics: entry.diagnostics } : {}),
|
|
778
|
+
...(entry.riskLabels.length ? { risk_labels: entry.riskLabels } : {}),
|
|
779
|
+
}))
|
|
780
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
781
|
+
diagnostics: discovery.diagnostics,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
759
785
|
export function listDraftRecipes(root: string): Array<Record<string, unknown>> {
|
|
760
786
|
return listRecipeFiles(root).map((path) => {
|
|
761
787
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -16,7 +16,7 @@ import * as Paths from "./paths.ts";
|
|
|
16
16
|
import * as RecipeControl from "./recipe-control.ts";
|
|
17
17
|
import * as Schema from "./schema.ts";
|
|
18
18
|
|
|
19
|
-
const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
|
|
19
|
+
export const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
|
|
20
20
|
const MAX_RECIPE_IMPORT_DEPTH = 32;
|
|
21
21
|
|
|
22
22
|
export interface TemplateRecipeImportBinding {
|
package/lib/recipes-usage.ts
CHANGED
|
@@ -101,7 +101,7 @@ function readRecipeSource(path: string): { fingerprint: string } | undefined {
|
|
|
101
101
|
|
|
102
102
|
function inferRecipeRoot(path: string): string {
|
|
103
103
|
const parent = dirname(resolve(path));
|
|
104
|
-
return basename(parent)
|
|
104
|
+
return ["drafts", "library"].includes(basename(parent)) ? dirname(parent) : parent;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function pathKey(path: string, recipeRoot: string): string {
|
|
@@ -112,8 +112,9 @@ function pathKey(path: string, recipeRoot: string): string {
|
|
|
112
112
|
return relation.replaceAll("\\", "/");
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function recipeName(path: string): string {
|
|
116
|
-
|
|
115
|
+
function recipeName(path: string, recipeRoot: string): string {
|
|
116
|
+
const name = basename(path).replace(/\.(?:json|md)$/u, "");
|
|
117
|
+
return dirname(resolve(path)) === resolve(recipeRoot, "library") ? `library/${name}` : name;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
function lineageFilename(lineageName: string): string {
|
|
@@ -232,8 +233,10 @@ function findRelocatedRecipe(
|
|
|
232
233
|
index: RecipeUsageIndex,
|
|
233
234
|
fingerprint: string,
|
|
234
235
|
recipeRoot: string,
|
|
236
|
+
newPath: string,
|
|
235
237
|
): { indexedName: string; path: string; record: RecipeUsageRecord } | undefined {
|
|
236
238
|
const candidates = Object.entries(index.paths).flatMap(([oldPath, indexedName]) => {
|
|
239
|
+
if (oldPath.startsWith("library/") !== newPath.startsWith("library/")) return [];
|
|
237
240
|
if (existsSync(join(recipeRoot, oldPath))) return [];
|
|
238
241
|
const found = readIndexedLedger(indexedName, recipeRoot);
|
|
239
242
|
return found?.record.fingerprint === fingerprint
|
|
@@ -288,7 +291,7 @@ export function ensureRecipeLineage(
|
|
|
288
291
|
const source = readRecipeSource(path);
|
|
289
292
|
if (!source) return false;
|
|
290
293
|
const key = pathKey(path, recipeRoot);
|
|
291
|
-
const name = recipeName(path);
|
|
294
|
+
const name = recipeName(path, recipeRoot);
|
|
292
295
|
const index = readIndex(recipeRoot);
|
|
293
296
|
const indexedName = index.paths[key];
|
|
294
297
|
if (indexedName) {
|
|
@@ -392,11 +395,11 @@ export function recordRecipeLaunch(
|
|
|
392
395
|
if (!source) return false;
|
|
393
396
|
const { fingerprint } = source;
|
|
394
397
|
const key = pathKey(path, recipeRoot);
|
|
395
|
-
const desiredName = recipeName(path);
|
|
398
|
+
const desiredName = recipeName(path, recipeRoot);
|
|
396
399
|
const index = readIndex(recipeRoot);
|
|
397
400
|
const relocated = index.paths[key]
|
|
398
401
|
? undefined
|
|
399
|
-
: findRelocatedRecipe(index, fingerprint, recipeRoot);
|
|
402
|
+
: findRelocatedRecipe(index, fingerprint, recipeRoot, key);
|
|
400
403
|
const indexedName = index.paths[key] ?? relocated?.indexedName;
|
|
401
404
|
const found = indexedName
|
|
402
405
|
? readIndexedLedger(indexedName, recipeRoot)
|
|
@@ -436,7 +439,7 @@ export function recordRecipeLaunch(
|
|
|
436
439
|
former_names: renamed
|
|
437
440
|
? appendBounded(stored?.former_names ?? [], stored!.lineage_name)
|
|
438
441
|
: relocated
|
|
439
|
-
? appendBounded(stored?.former_names ?? [], recipeName(relocated.path))
|
|
442
|
+
? appendBounded(stored?.former_names ?? [], recipeName(join(recipeRoot, relocated.path), recipeRoot))
|
|
440
443
|
: stored?.former_names ?? [],
|
|
441
444
|
former_paths: relocated
|
|
442
445
|
? appendBounded(stored?.former_paths ?? [], relocated.path)
|
|
@@ -591,7 +594,7 @@ export function moveRecipeUsage(
|
|
|
591
594
|
const toKey = pathKey(toPath, recipeRoot);
|
|
592
595
|
const index = readIndex(recipeRoot);
|
|
593
596
|
const oldName = index.paths[fromKey];
|
|
594
|
-
const newName = recipeName(toPath);
|
|
597
|
+
const newName = recipeName(toPath, recipeRoot);
|
|
595
598
|
if (!oldName) {
|
|
596
599
|
return index.paths[toKey] === newName && Boolean(readLedger(newName, recipeRoot));
|
|
597
600
|
}
|