@llblab/pi-actors 0.57.1 → 0.58.1

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 (49) hide show
  1. package/AGENTS.md +11 -3
  2. package/BACKLOG.md +14 -1
  3. package/CHANGELOG.md +13 -1
  4. package/README.md +7 -1
  5. package/dist/lib/file-state.d.ts +1 -0
  6. package/dist/lib/file-state.js +8 -2
  7. package/dist/lib/pi.js +1 -1
  8. package/dist/lib/prompts.d.ts +2 -1
  9. package/dist/lib/prompts.js +6 -2
  10. package/dist/lib/recipes-discovery.d.ts +4 -0
  11. package/dist/lib/recipes-discovery.js +22 -0
  12. package/dist/lib/recipes-references.d.ts +1 -0
  13. package/dist/lib/recipes-references.js +1 -1
  14. package/dist/lib/recipes-usage.js +12 -9
  15. package/dist/lib/registry.d.ts +5 -1
  16. package/dist/lib/registry.js +199 -7
  17. package/dist/lib/run-delivery.d.ts +1 -1
  18. package/dist/lib/run-delivery.js +19 -9
  19. package/dist/lib/tools-inspect.js +6 -3
  20. package/dist/lib/tools-register.js +1 -0
  21. package/dist/lib/tools-response.js +20 -16
  22. package/dist/skills/actors/SKILL.md +11 -2
  23. package/dist/skills/actors/references/persistent-tools.md +20 -2
  24. package/dist/skills/actors/references/runs.md +1 -1
  25. package/dist/skills/recipe-memory/SKILL.md +6 -0
  26. package/docs/README.md +1 -0
  27. package/docs/agent-experience.md +5 -6
  28. package/docs/capability-memory.md +121 -0
  29. package/docs/coordinator-delivery.md +2 -2
  30. package/docs/inspection.md +3 -1
  31. package/docs/output-projection-map.md +1 -1
  32. package/docs/releasing.md +5 -1
  33. package/docs/tool-registry.md +37 -1
  34. package/lib/file-state.ts +7 -3
  35. package/lib/pi.ts +1 -1
  36. package/lib/prompts.ts +7 -2
  37. package/lib/recipes-discovery.ts +26 -0
  38. package/lib/recipes-references.ts +1 -1
  39. package/lib/recipes-usage.ts +11 -8
  40. package/lib/registry.ts +195 -10
  41. package/lib/run-delivery.ts +22 -11
  42. package/lib/tools-inspect.ts +6 -3
  43. package/lib/tools-register.ts +1 -0
  44. package/lib/tools-response.ts +20 -13
  45. package/package.json +1 -1
  46. package/skills/actors/SKILL.md +11 -2
  47. package/skills/actors/references/persistent-tools.md +20 -2
  48. package/skills/actors/references/runs.md +1 -1
  49. package/skills/recipe-memory/SKILL.md +6 -0
package/AGENTS.md CHANGED
@@ -52,6 +52,8 @@ Pi host
52
52
 
53
53
  `index.ts` wires Pi ports and must not own domain behavior. Keep the local TypeScript import graph acyclic. Prefer cohesive existing owners over per-phase or per-iteration modules; file size alone does not justify extraction. Earn new files through a real ownership, dependency, authority or reuse boundary. Colocation must preserve distinct typed admission/privacy contracts, not collapse them into generic helpers or broad facades. For architecture-affecting work, load and follow `.agents/skills/domain-dag/SKILL.md`; its validator is local agent tooling, not an npm, CI, or release gate.
54
54
 
55
+ For product validation launched from an Actor, clear only `PI_ACTORS_DELIVERY_OWNER_ID`, `PI_ACTORS_DELIVERY_PARENT_RUN`, `PI_ACTORS_DELIVERY_PARENT_INSTANCE` and `PI_ACTORS_DELIVERY_PARENT_STATE_DIR` in the test command's environment. SDK fixtures are independent coordinators; inheriting the validation Actor's lineage can suppress their completion. Keep the launcher's own Run lineage, standard test preload, cases and watchdogs unchanged. Use Node24 for CI-equivalent release validation.
56
+
55
57
  The committed `dist/` tree is required for npm and git package installs. `npm run build` compiles into a temporary candidate and atomically replaces the distributive; `npm run build:check` must reject source/artifact drift without rewriting the working tree.
56
58
 
57
59
  Unit-owner files may construct real SDK sessions. Under a zero-SDK-session bound, do not use a broad negative `--test-name-pattern` as an execution guard: Node24 matches test ancestors, including `<root>`, and can admit forbidden leaves. Before execution, prove the declaration/import/ancestor closure with explicit positive leaf names and/or supported `--test-skip-pattern` exclusions. If exclusion is unproved, stop. Budget session-only lifecycle constructors separately from public fixture Runs and driver/provider/reopen sessions; passing logs never retroactively authorize excess.
@@ -86,12 +88,12 @@ For ordinary Run-file investigation, follow the accepted [same-session research
86
88
  - `file-state.ts`: canonical generic file-mutation locks and atomic JSON/text writers. withFileMutationLock preserves an exact synchronous callback rejection and a later surfaced release fault as ordered non-enumerable standard AggregateError errors/cause, including undefined/primitive/original aggregates; never coerce/flatten, replay callbacks, roll back bytes or interpret returned thenables. A single callback/release-only rejection and normal return remain exact. Atomic replacement separately preserves a single surfaced native-shaped rejection exactly, including null/undefined; retry-code access is null-safe and guarded. If that access throws, ordered private standard AggregateError errors/cause retains original then observation fault without coercion/flattening; no retry follows failed classification. Keep ordinary EPERM/EBUSY 20-attempt / 19×50ms limits, one onBeforeReplace call and already committed bytes unchanged. This is not writer rollback, fsync or cleanup proof. Acquisition, token/reclaim/lock interval and best-effort removal uncertainty stay separate. No async locking, exhaustive cleanup/native closure, public receipt or recovery of already-masked lower writer causes follows. Controlled release-fault fixtures retain their exact owner/frame and assert no workload/host exit.
87
89
  - `state-readers.ts`: resilient JSON/JSONL parsing; strict UTF-8 JSON is opt-in; capped strict reads use available nonblocking/nofollow flags, require a regular opened descriptor and reach the complete declared file, never admit a valid prefix after premature EOF. Legacy degradation stays unchanged; bounded ownership-marker admission also selects strict reading. Execution manifest export/reference admission selects it; malformed bytes must not normalize into admitted source metadata.
88
90
  - `runtime-identity.ts`, `runtime-triage.ts`: immutable package/schema identity and pure pending/stale Control classification.
89
- - `tools-response.ts`: shared response formatting, including the flat launch receipt, two-space opaque Markdown field and typed inspect-next primitives now reused by Control, pure admitted-snapshot text/JSON rendering and the literal-block primitive reused by completion packets. Launches retain every selected fact/reuse distinction, one optional current-session action only without a fully shown direct row, whole literal navigation and shared16KiB bounds including the no-navigation fallback. Never migrate the older formatRunFileNavigationHandoff packet grammar implicitly; completion/version contracts stay independent. Own-data/scalar/schema validation grants no visibility/lifecycle/timing permission. Preserve complete identities/references, CRLF/BOM/Unicode and exact ms; use dynamic literal fences rather than whitespace normalization. Compact JSON retains the same bounded payload; text fence amplification refuses the whole view, never clips or silently changes format. Tool spacing/envelope and combined delivery budgets remain separate, not extra allowance. Synthetic terminal-format fixtures prove no frozen-terminal timing or visual/public acceptance.
91
+ - `tools-response.ts`: shared response formatting, including the flat launch receipt, two-space opaque Markdown field and typed inspect-next primitives now reused by Control, pure admitted-snapshot text/JSON rendering and the literal-block primitive reused by completion packets. Its YAML and legacy Recipe hint renderers share one private exact-literal classifier; keep their framing, budget and opaque-fallback contracts separate. Launches retain every selected fact/reuse distinction, one optional current-session action only without a fully shown direct row, whole literal navigation and shared16KiB bounds including the no-navigation fallback. Never migrate the older formatRunFileNavigationHandoff packet grammar implicitly; completion/version contracts stay independent. Own-data/scalar/schema validation grants no visibility/lifecycle/timing permission. Preserve complete identities/references, CRLF/BOM/Unicode and exact ms; use dynamic literal fences rather than whitespace normalization. Compact JSON retains the same bounded payload; text fence amplification refuses the whole view, never clips or silently changes format. Tool spacing/envelope and combined delivery budgets remain separate, not extra allowance. Synthetic terminal-format fixtures prove no frozen-terminal timing or visual/public acceptance.
90
92
  - `tools-spawn.ts`, `tools-register.ts`, `tools-local.ts`: Run creation, persistent capabilities, Recipe-backed tools and compact results. Compact spawn/async-tool launch receipts retain facts, expose actual existing-Run reuse and omit the redundant session-map hint only when the complete direct row is shown; absent/unavailable/omitted rows retain that request. Compact launch metadata uses bare block YAML with nested complete location values; preserve the original JSON details, expert facts, shared bounds and old completion text. Keep one leading result-separator LF for host invocation spacing, outside the pure YAML emitter. Spawn-verbose source selects legacy pretty JSON once after eager compact evaluation, then emits full YAML only from that clone without a new fixed byte/depth cap. Keep original stringify throws outside the projection catch; absent selected JSON, malformed selected keys/strings and recoverable post-selection failures withhold the whole projection with an attributable no-auto-repeat receipt, not repaired values, partial full facts or private causes. Returned details and unrelated JSON helpers stay unchanged; compiled-package and loaded-host adoption remain separate. Do not synthesize routine Run-inspection next actions or unconditional delivery/readiness promises. Preserve existing structured/verbose metadata, expert Inspect access and separately owned targeted diagnostic remediations. Runtime argument-repair guidance is pre-dispatch only: once an executor is entered or arguments are handed to the async launcher, rethrow the exact original value/cause/details even when its text matches validation prefixes. Preparation side effects, dispatch, actuation and closure remain distinct; add no retry and preserve configured execution policy.
91
93
  - `inspector.ts`: owned-Run evidence and exact-session/generation-fenced actions through injected canonical control dependencies. `inspector-overlay.ts` and `inspector-command.ts` retain UI/navigation and host command wiring separately. **Actor Inspector** remains the product and command name, not a separate domain.
92
94
  - `pi.ts`: Pi SDK envelope boundary. AX completion fallback identity comes from the closed terminal marker for new records or the original headers for retained captures when private details are stripped; keep the original legacy RegExp coercion order, reject noncanonical/indented/broken markers and never acknowledge by body equality; never let authored body labels or Unicode line separators override it. Preserve older header recognition and detailed-envelope precedence. Parsed ids remain hints until exact owned-journal/content admission; they grant no recipient, source or native authority.
93
95
  - `observability.ts`, `run-ui-runtime.ts`: Trace attention, terminal reconciliation and Pi delivery. Fresh urgent-steer admission uses formatRunSteerMessage's single canonical AX layout only after the retained run/generation/directory/event lookup. Envelope.content is already frozen presentation: replay it directly without a new journal aspect, source refresh or reformatting. Attention and steer have no presentation selector or alternate new-message formatter; deliverRunAttentionNotifications uses the same canonical AX primitives for new notify/followup output. Saved envelope.content remains the compatibility boundary. The two AX checkpoint/reference helpers now have real ordinary/urgent reuse inside observability; keep ordinary first-eight coverage independent of steer's shared16KiB reservation/refusal loop. No ambient ordinary byte cap, event/detail copy, wake change or regeneration of committed content follows. Fresh attention uses shallow typed block-YAML metadata, bare without a body and delimited before an opaque zero-added-indent body. Preserve scalar-aligned500UTF-16 legacy previews, existing ellipsis and unknown local/upstream loss, original stringify/getter faults, whole reference sequences and first-eight slots before malformed filtering. Ordinary notify/followup remains unbounded; urgent steer validates raw essentials before reference reads and fits the complete final YAML/body with later-group omission coverage under16KiB. Serialization stays local to observability, not a new global formatter allowance; selection/read/acceptance authority stays here. Fresh urgent diagnostics also reuse its opaque field primitive: event/attention/stage records distinguish retryable admission, failed send request with unconfirmed delivery/presentation, and queued recovery with its exact evidence status/reason. Do not infer no effect from an exception or turn checkpoint retry into workload replay. Diagnostic display keeps only240pair-aligned raw message units and authored Markdown, with boundary U+2026/[omitted: unknown] for loss and unavailable for malformed text. This current UI projection is separate from unchanged whitespace-normalized/trimmed4096-unit last_error, original Error/cause and frozen envelope.content; create no saved format/aspect, reader, recovery loop or extra source permission. Fresh locally owned runtime notices use tools-response.ts:formatYamlNotice, a UI-only flat-field serializer without the model byte/depth ceilings: retain full well-formed fields, withhold malformed selected text explicitly, and keep existing diagnostic-preview bounds. Callback failure preserves the original onCallbackError value and opaque diagnostic; completion recovery preserves evidence status/pending phase/reason with no forced send; idle completion delivery is labeled accepted only after the original durable acceptance; teardown counts/whole literal summary path are explicitly report data, not scope closure. Watcher/retirement messages are forwarded unchanged from observability's new AX producer formatting. Watcher messages reuse the already captured detail/recovery strings: never read Error.message twice, add primitive-error coercion, change raw signatures/IDs/ts/dedup/32suffix/rearm policy or regenerate older diagnostic text. Paths stay whole dynamic literals. Retirement notices preserve optional-notify argument short-circuiting, exact error-message observations, attempted/sendStop/cancel/result/failure behavior including notifier throws, and unconfirmed closure. Stop returned and cancellation reported are not native success; fallback reason is stop path failed because the path also contains notifier execution. Do not repair already-discarded lower causes or rewrite result errors from the display. No new clipping, identities, state reads, notifier timing/levels/dedup, retries, error wrappers or native authority. Fresh completion admission creates canonical AX only after existing owner/location rechecks; an already-admitted batch returns untouched, including every older presentation format. Keep active/idle timing, acceptance, exact generation and source admission authority unchanged; fresh completion-prefix allocation follows the run-delivery contract below. Color terminal status only in `ctx.mode === "tui"`; JSON/print/RPC status must not require an initialized terminal theme or disable mandatory completion delivery. Shared ready-forest selection and ambient candidate checks are metadata-only for primary sources. Read/verify a selected primary source only when collecting eligible completion members, retaining exact source owner/generation and refusing capture promotion or lost-source stdout substitution. Unsupported complete raw-stream selections remain explicit or legacy diagnostics, never fabricated primary output; separately admitted executor-final-response text is not complete raw-stream selection.
94
- - `run-delivery.ts`: owner-scoped durable packet/phase persistence and bounded projections. Fresh ordinary source admission captures presentation_format=ax_records, own-data result_layout=completion_yaml_files_v1 and frozen text_budget, with no presentation selector/excerpt_format; direct and ordinary UI admission share this path. The separate inactive numbered-stream capture still inherits frontmatter_v2; do not change the private default globally or relabel retained captures. Preserve all stored encodings/absence and exact replay bytes/hints, including headings_shared_kinds and frontmatter_v1/v2; reject mixed/unknown aspects without invoking accessors. Journals store snapshots, not complete text, so neither an unreleased version nor a schema fix permits rewriting old codecs. The second frontmatter capture layout quotes unknown index as YAML "?"; the first keeps its original bytes, including unquoted ?. Fresh headers begin index, run, state, then admitted kind:primary/diagnostic; meaningful metadata and whole source/generation/location references follow. Display index is not member_index or native identity. Raw admitted Markdown follows the closed YAML at the left margin, without #N heading, result:/preview: wrapper, structural list indentation or whole-body literal fence. Known display loss is [N characters omitted] at the cut; no added ellipsis or body clipping flags/counts in YAML. Fresh rooted captures keep unknown upstream/fallback completeness/privacy warnings in header text_warnings; only an exact known character-omission marker follows a cut body. Fallback has no primary kind. Retained layouts keep their original marker placement. Visible text is admitted output/final Actor response or error/failure diagnostics, never command metadata or copied command excerpts. Source text_basis remains technical. No routine group windows/status copies, members wrapper, outer counters, Batch caption or standing full-map instruction appears in new model content; captured facts remain journal data. Material whole-result/reference omission, non-done omitted states and a whole inventory reference remain explicit. A single shown row keeps the same shape even when other results were omitted. Complete model content includes all exact references and only the necessary YAML-core packet identity/relative body-marker lengths, without fresh compiler HTML tails, under49152UTF-8 bytes for up to64candidate rows/256captured members. Equal scalar/CRLF-safe text-prefix shares freeze at admission, including service overhead; no redistribution, fixed16KiB result/2KiB preview cap, source recapture or later reallocation. Preserve every saved16KiB/48KiB allowance. Prefix admission must consider full-list/decimal discontinuities rather than miss fitting rows when omission scaffolding shrinks. Whole text has no display-cut marker when complete; known upstream clipping still forbids a full-original-output claim. The source reader selects at most48KiB from the same verified postprivacy observation; source-binding previews stay160code points. Retain chosen packet prefixes, not64independent48KiB copies. Unfit metadata rows keep bounded inventory recovery. Curate/copy/freeze members, arrays, artifacts and bindings; schema DTOs grant no publication permission and phase changes do not mutate member facts. Preserve generation/parent/source identities, exact ranges/hashes and whole references. The completion-only TUI uses independent native Markdown body components and literal Text header/marker/service components, with no global Markdown patch, source/native IO or scheduling authority. New rooted captures expose files.groups in the header: one literal root per group, relative entry paths, English role/purpose and read_first group/entry indices. Reconstruct original selected references exactly; never normalize dot/symlink routes, guess cwd or treat declared targets as produced. Unresolved paths retain path_basis=as_recorded/base_unavailable with a null root and no recommendation. Observation.group binds its original mutable Run-directory facts. All packet/inventory/whole-row omission metadata renders in the first header, not a service footer; no runtime text follows the last body except its known cut marker. Retained core-v1 captures keep their original references/locations and compact hiding rules; authored body lookalikes never disappear. Fresh ordinary hints use completion_yaml_core_v1 plus a whole-content digest; validated core lengths derive block positions. Retained completion_frontmatter_v1 hints keep their SHA-256-bound UTF-16 header/body/marker/service blocks and original compiler frames, dense ordered coverage and scalar/CRLF-safe endpoints. Invalid/missing hints and expansion reveal original full Text. These are display/correlation consistency, not authentication/read/action/acceptance/native authority. Malformed frontmatter-shaped stripped history cannot regain identity from a body Batch label or become absence/retry permission; ordinary thematic-break prose retains its legacy path. Retained pre-record codecs keep host rendering; older record/literal codecs keep exact original hints, dynamic fences, count spelling and separators. Fresh ordinary indices remain packet-local; no default stream activation follows. Fresh UI member locations are observed before journal locks after actual current-session selection/recheck, with captured_file_locations and frozen file_navigation. Data-only decoding does not grant current body/source visibility. Location snapshots retain16KiB, outcomes outrank lower-priority references, and each shown record has a whole location row or explicit omission; wholly omitted records stay inventory-covered. Reload/render/phase recovery never refreshes sources or frozen availability. Old absent/current_session_request captures keep their bytes, and optional full-map research stays a separate current-session action. Source/package checks do not prove the loaded host was reloaded.
96
+ - `run-delivery.ts`: owner-scoped durable packet/phase persistence and bounded projections. Fresh ordinary source admission captures presentation_format=ax_records, own-data result_layout=completion_yaml_files_v2 and frozen text_budget, with no presentation selector/excerpt_format; direct and ordinary UI admission share this path. The separate inactive numbered-stream capture still inherits frontmatter_v2; do not change the private default globally or relabel retained captures. Preserve all stored encodings/absence and exact replay bytes/hints, including headings_shared_kinds and frontmatter_v1/v2; reject mixed/unknown aspects without invoking accessors. Journals store snapshots, not complete text, so neither an unreleased version nor a schema fix permits rewriting old codecs. The second frontmatter capture layout quotes unknown index as YAML "?"; the first keeps its original bytes, including unquoted ?. Fresh headers begin run, state, then admitted kind:primary/diagnostic; prepend the packet-local index only when two or more results are shown. meaningful metadata and whole source/generation/location references follow. Display index is not member_index or native identity. Raw admitted Markdown follows the closed YAML at the left margin, without #N heading, result:/preview: wrapper, structural list indentation or whole-body literal fence. Known display loss is [N characters omitted] at the cut; no added ellipsis or body clipping flags/counts in YAML. Fresh rooted captures keep unknown upstream/fallback completeness/privacy warnings in header text_warnings; only an exact known character-omission marker follows a cut body. Fallback has no primary kind. Retained layouts keep their original marker placement. Visible text is admitted output/final Actor response or error/failure diagnostics, never command metadata or copied command excerpts. Source text_basis remains technical. No routine group windows/status copies, members wrapper, outer counters, Batch caption or standing full-map instruction appears in new model content; captured facts remain journal data. Material whole-result/reference omission, non-done omitted states and a whole inventory reference remain explicit. Exactly one shown row omits only its display index, including with other results omitted; preserve omission/inventory facts and internal member_index/generation/correlation. The rooted_files_v2 service discriminator gates this new presentation; retained rooted-files-v1/core-v1 snapshots keep their exact indices and bytes. Do not extend this packet-local exception to numbered streams. Complete model content includes all exact references and only the necessary YAML-core packet identity/relative body-marker lengths, without fresh compiler HTML tails, under49152UTF-8 bytes for up to64candidate rows/256captured members. Equal scalar/CRLF-safe text-prefix shares freeze at admission, including service overhead; no redistribution, fixed16KiB result/2KiB preview cap, source recapture or later reallocation. Preserve every saved16KiB/48KiB allowance. Prefix admission must consider full-list/decimal discontinuities rather than miss fitting rows when omission scaffolding shrinks. Whole text has no display-cut marker when complete; known upstream clipping still forbids a full-original-output claim. The source reader selects at most48KiB from the same verified postprivacy observation; source-binding previews stay160code points. Retain chosen packet prefixes, not64independent48KiB copies. Unfit metadata rows keep bounded inventory recovery. Curate/copy/freeze members, arrays, artifacts and bindings; schema DTOs grant no publication permission and phase changes do not mutate member facts. Preserve generation/parent/source identities, exact ranges/hashes and whole references. The completion-only TUI uses independent native Markdown body components and literal Text header/marker/service components, with no global Markdown patch, source/native IO or scheduling authority. New rooted captures expose files.groups in the header: one literal root per group, relative entry paths, English role/purpose and read_first group/entry indices. Reconstruct original selected references exactly; never normalize dot/symlink routes, guess cwd or treat declared targets as produced. Unresolved paths retain path_basis=as_recorded/base_unavailable with a null root and no recommendation. Observation.group binds its original mutable Run-directory facts. All packet/inventory/whole-row omission metadata renders in the first header, not a service footer; no runtime text follows the last body except its known cut marker. Retained core-v1 captures keep their original references/locations and compact hiding rules; authored body lookalikes never disappear. Fresh ordinary hints use completion_yaml_core_v1 plus a whole-content digest; validated core lengths derive block positions. Retained completion_frontmatter_v1 hints keep their SHA-256-bound UTF-16 header/body/marker/service blocks and original compiler frames, dense ordered coverage and scalar/CRLF-safe endpoints. Invalid/missing hints and expansion reveal original full Text. These are display/correlation consistency, not authentication/read/action/acceptance/native authority. Malformed frontmatter-shaped stripped history cannot regain identity from a body Batch label or become absence/retry permission; ordinary thematic-break prose retains its legacy path. Retained pre-record codecs keep host rendering; older record/literal codecs keep exact original hints, dynamic fences, count spelling and separators. Fresh ordinary indices remain packet-local; no default stream activation follows. Fresh UI member locations are observed before journal locks after actual current-session selection/recheck, with captured_file_locations and frozen file_navigation. Data-only decoding does not grant current body/source visibility. Location snapshots retain16KiB, outcomes outrank lower-priority references, and each shown record has a whole location row or explicit omission; wholly omitted records stay inventory-covered. Reload/render/phase recovery never refreshes sources or frozen availability. Old absent/current_session_request captures keep their bytes, and optional full-map research stays a separate current-session action. Source/package checks do not prove the loaded host was reloaded.
95
97
  - automatic draft/tool review domains: structurally redacted model review, journaled mutation, lineage, recovery, and explicit retry/reset safety.
96
98
 
97
99
  The bundled Skill Recipe dependency DAG is `artifacts → actors, swarm` and `project-work → actors, artifacts, swarm`; `actors`, `music-player`, `swarm`, and `recipe-memory` have no cross-Skill Recipe dependencies.
@@ -166,13 +168,19 @@ The user-requested JSONL bundle/CodeMode direction is recorded in docs/compositi
166
168
 
167
169
  `~/.pi/agent/recipes/*.json` is executable capability memory. Preserve filename identity, atomic writes, canonical per-path locks with atomic owner publication and non-blocking abandoned staging, explicit operator-gated changes, and transportability.
168
170
 
171
+ The adopted [capability-memory direction](docs/capability-memory.md) separates retaining a reusable Recipe from exposing a named tool. Sync/async is an execution contract, not a maturity/usefulness score or automatic registration rule; subagent-backed tools are capability launchers, not persisted live Runs. Explicit `register_tool expose=false` creates a new template/from Recipe under `recipes/library` without host activation or tool-map mutation. Its retained_recipe/not_requested receipt omits global callability flags because an unrelated same-named tool may already be active. Keep it create-only, source/size-admitted and exclusively published with root/library/target fences; preserve uncertain committed bytes and original causes. Retained update/delete/draft promotion and direct disposable-draft references are unsupported. Automatic retained lifecycle is not implemented: current root-based exposure and review actions remain unchanged. Read-only Recipe inspection now inventories `recipes/library` separately, in the same active Skill context, without granting review/callability/Run status. Keep library files outside automatic tool discovery, including older-host behavior. Do not invent automatic-review actions or Recipe exposure flags, or migrate the live portfolio from this implementation alone.
172
+
173
+ Structural review and launch counts do not establish semantic purpose, good tool names, successful reuse, or accepted outcomes. Missing evidence is unknown, not a reason to fabricate usefulness or irreversibly discard a potentially useful capability. Preserve value-free review and unchanged-source executor boundaries; semantic evidence and new retention/exposure transitions require their own privacy, provenance, transaction and acceptance contract.
174
+
175
+ Retained usage belongs to the parent Recipe root's existing `.usage` owner. Keep private `library/<stem>` lineage distinct from a same-stem tool entrypoint; that ledger label is not a public Recipe reference. Missing files and matching content do not authorize implicit lineage transfer between retained and tool/draft namespaces. Preserve explicit transaction/move ownership and unknown legacy nested evidence rather than guessing a migration.
176
+
169
177
  Automatic review receives value-free structural projections, not executable content, paths, prose, canonical names, or secrets. Deterministic executors derive unchanged Recipes from trusted captures. Approved mutation must journal intent before mutation and roll forward safely after crashes.
170
178
 
171
179
  `PI_ACTORS_AUTOMATIC_REVIEW=off` disables scheduling and safe-boundary activation while remaining visible in runtime status.
172
180
 
173
181
  ## Output and Observability
174
182
 
175
- The [completion-stream contract](docs/coordinator-delivery.md#completion-streams-adopted-contract) is adopted and under implementation, not active. Keep persistent numbering in the existing delivery owner: no counter reset from missing state, no branch-history reconstruction and no activation before a coupled owner-locked packet/counter/target commit and compatible recovery/presentation. Current packet-local indices and all saved bytes/hints remain unchanged until that integration is validated. The pure whole-capture safe-integer range projection reserves nothing and grants no session/delivery authority. Kernel pending-intent storage requires an existing valid owner journal, not missing-state initialization; it supplies no fresh-session bootstrap or public start action. Never silently fall back to packet-local capture after retaining an intent. Staged numbering facts bind a complete frozen member permutation and exact range/scope or unavailability; projection must not re-sort or infer numbers from body labels/current state. Their compiler-bound technical/footer recognition is consistency/correlation only. Kernel-only captureRunCompletionStreamBatch now requires an existing valid stream journal and atomically commits the full fixed snapshot, advanced counter and pending-target activation under the existing lock. Counters are positive exact next values or explicit unavailability; malformed/missing records, mixed scopes and counters behind numbered packets stay refused. All saved numbers replay even if the current counter becomes unavailable; new packets freeze #? on admitted unavailability or whole-range exhaustion. Packet finalization/receipt rotation never clear stream state. Default runtime stream capture/recovery still requires an admitted internal binding. A trusted kernel-only completionStreamSessionAdmission port opts in only to verified fresh new/fork creation with an original manager/current ticket; an event reason, ID or copied DTO alone is not admission. Fresh wrappers must match that manager/generation; reused managers and old contexts cannot revive after A→B→A. Per-start watcher/reconciliation/timer tickets and a separate async-start ticket fence replacement. Unknown/revoked stream paths neither allocate nor ACK, including post-send replacement, which does not prove no transport effect. Missing metadata never bootstraps or falls back on this opt-in path. Startup/reload/resume, including ambiguous imports, remain refused even if the port returns true. Source/compiled real SDK lifecycle probes use controlled terminal/busy-boundary data and forbid model requests; they are not loaded/native work acceptance. No public command, bootstrap, trusted continuation/import decision or rollback recovery is enabled; direct callers cannot select numbering.
183
+ The [completion-stream contract](docs/coordinator-delivery.md#completion-streams-adopted-contract) is adopted and under implementation, not active. Keep persistent numbering in the existing delivery owner: no counter reset from missing state, no branch-history reconstruction and no activation before a coupled owner-locked packet/counter/target commit and compatible recovery/presentation. Packet-local index identities and all saved bytes/hints remain unchanged; hiding the display index on a fresh singleton does not activate or alter numbered streams. The pure whole-capture safe-integer range projection reserves nothing and grants no session/delivery authority. Kernel pending-intent storage requires an existing valid owner journal, not missing-state initialization; it supplies no fresh-session bootstrap or public start action. Never silently fall back to packet-local capture after retaining an intent. Staged numbering facts bind a complete frozen member permutation and exact range/scope or unavailability; projection must not re-sort or infer numbers from body labels/current state. Their compiler-bound technical/footer recognition is consistency/correlation only. Kernel-only captureRunCompletionStreamBatch now requires an existing valid stream journal and atomically commits the full fixed snapshot, advanced counter and pending-target activation under the existing lock. Counters are positive exact next values or explicit unavailability; malformed/missing records, mixed scopes and counters behind numbered packets stay refused. All saved numbers replay even if the current counter becomes unavailable; new packets freeze #? on admitted unavailability or whole-range exhaustion. Packet finalization/receipt rotation never clear stream state. Default runtime stream capture/recovery still requires an admitted internal binding. A trusted kernel-only completionStreamSessionAdmission port opts in only to verified fresh new/fork creation with an original manager/current ticket; an event reason, ID or copied DTO alone is not admission. Fresh wrappers must match that manager/generation; reused managers and old contexts cannot revive after A→B→A. Per-start watcher/reconciliation/timer tickets and a separate async-start ticket fence replacement. Unknown/revoked stream paths neither allocate nor ACK, including post-send replacement, which does not prove no transport effect. Missing metadata never bootstraps or falls back on this opt-in path. Startup/reload/resume, including ambiguous imports, remain refused even if the port returns true. Source/compiled real SDK lifecycle probes use controlled terminal/busy-boundary data and forbid model requests; they are not loaded/native work acceptance. No public command, bootstrap, trusted continuation/import decision or rollback recovery is enabled; direct callers cannot select numbering.
176
184
 
177
185
  Apply the project-wide [Agent Experience (AX) standard](docs/agent-experience.md) before changing Inspector, public tool output/errors or agent-bound context. AX is a shared semantic projection for both people and agents, not a Telegram theme or raw JSON dump. The adopted default for runtime-owned metadata is YAML 1.2 block mappings/sequences, not Markdown-list key/value pseudo-objects or embedded object JSON; sequence markers represent real sequences only. Preserve authored text/Markdown unindented outside the metadata header, without a YAML output scalar or added wrapper/fence; runtime-owned multiline metadata strings use proper YAML scalars. The old emitters remain current implementation facts until their owned rollout, not a competing default. Display run: demo without a duplicated namespace, preserve canonical tool targets, literal sources and machine schemas, and use only admitted exact/unknown omission facts. New presentation must preserve source/ownership boundaries, full references, uncertainty, budgets and byte-stable old packets. The standard owns detailed syntax, examples, source-owner bindings and acceptance; BACKLOG owns rollout. Do not claim design adoption is implementation or restart Grow Loop/reload/publication by implication.
178
186
 
package/BACKLOG.md CHANGED
@@ -6,6 +6,7 @@ Task IDs name bounded work, not execution order. The operator authorizes autonom
6
6
 
7
7
  ## Next bounded work
8
8
 
9
+ - **Capability memory:** P1.3 must admit the evidence contract before P1.2c can change automatic retention/exposure. These future slices are separate from the explicit manual workflow; source/package acceptance does not authorize live migration or reload an already running host.
9
10
  - **Separate remaining owners:** F1.7 (one rollout candidate) remains the last open F1 owner; the migrated surfaces do not by themselves prove the loaded rollout or registry notice layout. Registry activation/promotion/deletion notice text stays outside the completed refusal migration. Inspector popup color recognition is verified against current producer results; a later feedback syntax change must update that anchored recognition with it.
10
11
  - **Explicit gates:** U1 requires authorized loaded-host observation/reload; N1.2p needs actual Windows profile evidence, N1.3 an asset-distribution decision, and N1.6 separate activation admission. Original-clock/native-scope, macOS research and composition decisions retain their own B/C/T gates. Existing release permission does not adopt them.
11
12
  - **Evidence reuse:** Reuse the accepted three-OS SOURCE/PACKED quit witnesses for B2.4; extend only the missing descendants/triggers/same-ID/crash cases. N1.9 stays a gated unresolved risk, not an instruction to repeat the completed ownership inquiry.
@@ -24,7 +25,7 @@ Exit: New owned runtime projections use YAML without pseudo-records or serialize
24
25
 
25
26
  Exit: Real loaded rendering is checked separately from source/headless/native proofs. This is independent follow-up, not a claim that the already merged release received loaded-host acceptance. Reload and an operator Run require their own authorization; do not start one merely to manufacture a live Kill case.
26
27
 
27
- - [ ] **U1.1 — Retest fresh completion packets and expansion.** After authorized reload, inspect actual singleton/multiple/omitted-row packets: index-first YAML/kind, left-edge independent Markdown, literal unfinished blocks, exact cut marker, blank separation and no routine top-level completion caption/toast. Expansion and historical fallback must preserve the saved text. Verify: actual terminal/phone observation plus existing delivery/Pi owners.
28
+ - [ ] **U1.1 — Retest fresh completion packets and expansion.** After authorized reload, inspect actual singleton/multiple/omitted-row packets: run/state YAML/kind with packet-local indices only for multiple shown results, left-edge independent Markdown, literal unfinished blocks, exact cut marker, blank separation and no routine top-level completion caption/toast. Expansion and historical fallback must preserve the saved text. Verify: actual terminal/phone observation plus existing delivery/Pi owners.
28
29
  - [ ] **U1.2 — Retest the Run dropdown.** Check shifted compact placement, independent stripes/right padding, narrow/medium/wide layout and unchanged selection/source detail. Preserve the accepted navigation and refresh behavior; do not invent a performance cause or change freshness speculatively. Verify: real Inspector plus existing overlay/Inspector properties.
29
30
  - [ ] **U1.3 — Retest notification and Kill modals.** Check complete selected notification text, no prefix/reader indirection, Esc-only dismissal and scrolling only on physical overflow; check centered compact Kill, default backgrounds/insets, frozen underlay and no key leakage. Cancel returns to the same Inspector without Notice; success relies on selected status and failures remain visible. Verify: actual permitted modal cases, without claiming Kill is scope closure.
30
31
  - [ ] **U1.4 — Retest Inspector chronology.** Check newest-higher numbering across 100→101/102, filter gaps without renumbering, scrolling/detail provenance and visible `#?` for unavailable history. Do not promise permanent lifetime ordinals or reconstruct missing offsets. Verify: [chronology contract](docs/actor-inspector.md#chronological-numbering-requested-replacement) on loaded terminal/phone.
@@ -185,6 +186,18 @@ Exit: Any later cleanup is independently justified and validated against its own
185
186
 
186
187
  - [ ] **R1.2 — Remove temporary diagnostics after native acceptance.** Gate on N1.7; remove only proven temporary/redundant instrumentation, preserve owned regressions, then refresh invalidated focused checks and final candidate-bound packaging/native evidence. A dirty status note or earlier executable result is not final-tarball proof.
187
188
 
189
+ ## 8. Capability memory separate from tool exposure
190
+
191
+ Outcome: Implement the [adopted capability-memory direction](docs/capability-memory.md): a reviewed Recipe can be retained without becoming a tool; sync/async remains execution policy. Success means easier, coherent management: one existing Recipe inspection surface distinguishes retained definitions from exposed tool candidates, with exact reuse references and no confusion with running instances. Existing explicit registration remains valid for both modes; no automatic-policy or live portfolio migration is authorized by local implementation alone.
192
+
193
+ ### P1 — Retention, evidence and deliberate exposure
194
+
195
+ Exit: Retain/defer/expose/withdraw have explicit evidence and replay-safe transitions in existing owners; useful async capabilities remain eligible and unsuitable sync captures are not exposed solely by mode. Current activation, privacy, source identity and lineage contracts survive source/packed acceptance. No second runtime, resolver or tool registry.
196
+
197
+ - [ ] **P1.2c — Integrate automatic lifecycle transitions.** After P1.3, extend the explicit manual path with CAS/recovery-safe retain/defer/expose/withdraw transitions through existing review transactions, without duplicate capability copies or demotion churn. Specify retention bounds and distinguish evidence deferral from safety rejection and admitted expiry/disposal before enabling automatic retention. Verify: draft/tool transaction, projection and recovery properties.
198
+ - [ ] **P1.3 — Ground automatic exposure in admitted evidence.** Define the privacy/provenance/budget contract for any semantic or accepted-outcome evidence before using it; keep structural-only review honest and non-destructive on uncertainty. Do not infer task success from launches, invent names from opaque shape, weaken confidentiality or permit automatic executable rewrites. Verify: review/projection safety properties and controlled same-shape/different-purpose, failed-launch and insufficient-evidence cases.
199
+ - [ ] **P1.4 — Accept and evaluate the automatic policy.** After P1.2c/P1.3, update owning Skills and current user docs and prove the automatic transitions in isolated source/packed portfolios. Reuse the explicit manual workflow witnesses when their source/activation semantics are unchanged; refresh only affected boundaries. Compare accepted useful reuse and selection cost against baseline, not tool counts. Any live migration requires an exact admitted portfolio plan and applicable operator authority; loaded-host claims require separate observation. Verify: registration-truth, agent journeys, installed-package and controlled policy evaluation.
200
+
188
201
  ## Evidence and scope notes
189
202
 
190
203
  The committed baseline of the former backlog is retained in Git at `16a3417`; later operational evidence is linked from the owning design and CI runs. Old candidate logs, review hashes and investigation iterations are evidence, not additional open work. Do not infer completed functionality from a closed author/reviewer process or infer publication/activation from a prototype PASS.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,19 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
- ## Unreleased
5
+ ## 0.58.1: Singleton Completion Indices
6
+
7
+ - `Singleton Completion Index`: New completion packets omit the packet-local display index when exactly one result is shown, including with omitted results. Multiple shown results retain indices; omission/inventory facts, internal identity/correlation, numbered-stream behavior and historical replay remain unchanged.
8
+ - `Inspection Hint Ownership`: YAML and legacy Recipe inspection hints share one exact three-literal classifier. Existing output bytes, verbose omission, budgets and opaque fallback remain unchanged; prefix, whitespace and authored-action lookalikes gain no typed authority.
9
+ - `Capability Contract Docs`: Tool Registry owns the current retention, receipt, storage and usage contract; Capability Memory keeps architectural rationale and future evidence/lifecycle gates. Existing design anchors and safety constraints remain, without a second copy of the operating contract.
10
+ - `Installed Runner Validation`: The installed-package regression joins original runner close and canonical success under the existing shared fixture watchdog instead of racing a five-second file poll. Failed or uncertain fixtures retain evidence; deletion requires fresh runner absence. Runtime deadlines and package execution are unchanged.
11
+
12
+ ## 0.58.0: Explicit Capability Retention
13
+
14
+ - `Capability Retention`: `register_tool expose=false` saves new template/from Recipes under `recipes/library` without activating tools or counting launches. Receipts return exact reusable paths and `activation: not_requested`, not failed activation. Both sync and async capabilities can be retained, spawned or deliberately exposed later; deleting an exposed wrapper preserves its retained source.
15
+ - `Retained Discovery and Usage`: Recipe inspection distinguishes retained definitions from tool candidates and drafts, with exact verbose paths, execution modes and invalid/disabled diagnostics. Inspection is read-only. Retained launches share the existing usage owner while keeping same-named tool history separate; matching content cannot silently transfer lineage across that boundary.
16
+ - `Safe Create-only Storage`: Retention validates sources and size, rejects direct disposable-draft dependencies and preserves name conflicts through atomic no-overwrite publication. Filesystems without hard-link support refuse rather than replace. Partial-effect failures preserve committed bytes and original causes; inspect before retrying. Retained update, delete and draft promotion remain unsupported.
17
+ - `Compatibility and Guidance`: Existing registration, tool discovery, automatic review and live portfolios remain unchanged; no migration or default policy is enabled. Updated Skills distinguish execution mode, retention and tool exposure. Delegated sources remain external dependencies, not self-contained backups; structural review and launch counts do not prove usefulness or accepted success.
6
18
 
7
19
  ## 0.57.1: Rooted Completion Headers
8
20
 
package/README.md CHANGED
@@ -25,6 +25,12 @@ This topology does not require every task to become a subagent. Short work with
25
25
 
26
26
  Design notes for [portable Recipe composition bundles and CodeMode interoperability](docs/composition-transport.md) are recorded separately. JSONL bundle loading is not implemented or included in the current UX/AX release; host CodeMode, when available, calls existing tools rather than replacing the Run kernel.
27
27
 
28
+ ## Capability Memory Direction
29
+
30
+ A reusable Recipe does not have to become a named tool. The [adopted capability-memory direction](docs/capability-memory.md) separates retention from tool exposure: sync/async determines execution, not usefulness, and a subagent-backed tool is a capability launcher rather than a saved live Actor. The aim is useful procedural memory with a selective tool interface, not more registered names.
31
+
32
+ Use `register_tool expose=false` with a new name and a template/from source to save a capability in `recipes/library` without activating a tool. Its retained_recipe/not_requested receipt returns the exact Recipe path for `spawn` or later deliberate `register_tool from` exposure. Creation is exclusive; retained update, delete and draft promotion are not supported. `inspect target=recipes view=summary` distinguishes retained definitions, with exact paths, modes and diagnostics in verbose detail. Automatic retained lifecycle is not implemented. Current tool-root discovery, automatic review and ordinary registration remain unchanged; structural review and launch counts alone do not prove useful successful reuse. [BACKLOG.md](BACKLOG.md) owns implementation and evidence gates; adopting the direction neither migrates existing tools nor reloads a running host.
33
+
28
34
  ## Install
29
35
 
30
36
  Requires Node.js 22.19.0 or newer and Pi 1.0.0 or newer.
@@ -103,7 +109,7 @@ A Run exposes exactly `recipe`, `trace`, and `control` views. Run-specific `insp
103
109
 
104
110
  ### `register_tool`
105
111
 
106
- Persist a maintained Recipe with `register_tool name=<tool> from=<skill>/<recipe> defaults={...}`, or register a trusted command through the separate `template` mode. Definitions live under `~/.pi/agent/recipes`. Treat a tool as callable in the current session only when the result reports `callable_now: true`; persistence, Recipe spawning, and registered-tool invocation are distinct states.
112
+ Persist a maintained Recipe with `register_tool name=<tool> from=<skill>/<recipe> defaults={...}`, or register a trusted command through the separate `template` mode. Definitions live under `~/.pi/agent/recipes`. With `expose=false`, a new template/from definition is retained under `recipes/library` instead; its not_requested receipt makes no tool-callability claim. Treat an exposed tool as callable in the current session only when the result reports `callable_now: true`; persistence, Recipe spawning, and registered-tool invocation are distinct states.
107
113
 
108
114
  ## Recipe
109
115
 
@@ -13,6 +13,7 @@ export interface FileMutationLockOptions {
13
13
  export declare function acquireFileMutationLock(path: string, options?: FileMutationLockOptions): () => void;
14
14
  export declare function withFileMutationLock<T>(path: string, mutate: () => T, options?: FileMutationLockOptions): T;
15
15
  export declare function writeTextAtomic(path: string, content: string, options?: {
16
+ exclusive?: boolean;
16
17
  onBeforeReplace?(): void;
17
18
  }): void;
18
19
  export declare function writeJsonAtomic(path: string, value: unknown): void;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { createHash, randomUUID } from "node:crypto";
8
- import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { existsSync, linkSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
9
9
  import { tmpdir } from "node:os";
10
10
  import { basename, dirname, join, parse, resolve } from "node:path";
11
11
  const FILE_MUTATION_LOCK_TIMEOUT_MS = process.platform === "win32" ? 30000 : 15000;
@@ -312,7 +312,13 @@ export function writeTextAtomic(path, content, options = {}) {
312
312
  try {
313
313
  writeFileSync(tempPath, content, "utf8");
314
314
  options.onBeforeReplace?.();
315
- replaceFileWithRetry(tempPath, path);
315
+ if (options.exclusive === true) {
316
+ // Publish a complete new file atomically without clobbering an uncooperative creator.
317
+ linkSync(tempPath, path);
318
+ unlinkSync(tempPath);
319
+ }
320
+ else
321
+ replaceFileWithRetry(tempPath, path);
316
322
  }
317
323
  catch (error) {
318
324
  try {
package/dist/lib/pi.js CHANGED
@@ -83,7 +83,7 @@ function unheaderedCompletionId(content) {
83
83
  if (core.state === "available")
84
84
  return core.packet.batchId;
85
85
  // Initial compiler headers only: never recover identity by scanning authored bodies.
86
- if (/^(?:index: (?:\d+|"\?")\nrun: [^\n]*\nstate:|_completion:\n (?:codec|batch_id):)/u.test(content))
86
+ if (/^(?:index: (?:\d+|"\?")\nrun: [^\n]*\nstate:|run:[^\r\n]*(?:\r\n|\r|\n)state:|_completion:\n (?:codec|batch_id):)/u.test(content))
87
87
  return { malformed: true };
88
88
  }
89
89
  if (typeof content === "string" && (hasCompletionFrontmatterPrefix(content) || /^(?:#|Run #|— Новый поток|- run:|- #0:\n|- result: omitted \(model byte limit\)\n|- omitted_results:)/u.test(content))) {
@@ -4,7 +4,7 @@
4
4
  * Owns LLM-facing descriptions, prompt snippets, guidelines, and parameter descriptions
5
5
  */
6
6
  export declare const REGISTER_TOOL_DESCRIPTION: string;
7
- export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent Recipes or command templates as agent-callable tools";
7
+ export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Persist command templates or Recipes with register_tool; use expose=false to retain without tool activation";
8
8
  export declare const REGISTER_TOOL_GUIDELINES: string[];
9
9
  export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors Skill routing:\n- Treat active bundled Skills as the operating authority.\n- For non-trivial pi-actors operation, diagnosis, or development, load and read the actors Skill before acting.\n- For work requiring multiple actors or subagents, additionally load and read the swarm Skill.\n- For capability-specific selection or constraints, load the owning capability Skill; actors owns generic mechanics and swarm owns multi-actor methodology.\n- Keep a Skill Recipe distinct from a registered tool and a Recipe spawn distinct from registered-tool invocation; actors owns the proof rules.\n- Treat persistence or registration as distinct from current callability; actors owns activation proof.\n- On failure or disagreement, preserve the logical Recipe identity, stop, and follow actors diagnosis; never bypass the owning Skills with copied contracts, helper paths, shell evaluation, or background-process workarounds.\n- If a capability Skill conflicts with actors about generic mechanics, follow actors and report the stale capability guidance.\n- README and docs are human-facing references, not the normal agent operating path.\n- AGENTS, source, and tests are implementation protocol and evidence; use them when changing or debugging the extension, not as substitutes for operating Skills.";
10
10
  export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
@@ -13,6 +13,7 @@ export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
13
13
  readonly from: "Recipe to specialize by canonical <skill>/<recipe> identity or explicit .json/.md path. Inherits async, args/types, source defaults, artifacts, Control, and runtime origins.";
14
14
  readonly defaults: "Optional caller-owned defaults. Keys and values must satisfy the effective source or command-template argument contract.";
15
15
  readonly draft: "Promote a captured draft Recipe path from ~/.pi/agent/recipes/drafts. This is a source mode; do not combine it with from or template.";
16
+ readonly expose: "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.";
16
17
  readonly async: "Set true for a co-located async template recipe. Omit for ordinary command templates or file-backed recipe references.";
17
18
  readonly template: "Trusted command template with {arg} or {arg=default} placeholders. To specialize a Recipe, use from instead. Omitted updates keep the old template; empty string deletes the tool.";
18
19
  readonly templateArray: "Sequential command-template composition array. Leaves may be strings or objects with template/defaults/timeout/retry/failure/recover.";
@@ -5,12 +5,15 @@
5
5
  */
6
6
  export const REGISTER_TOOL_DESCRIPTION = "Register a persistent custom tool from one maintained Recipe, command template, or captured draft. " +
7
7
  "Use from for Recipe specialization, template for trusted commands, or draft for promotion. " +
8
+ "Set expose=false to retain a new template/from Recipe without tool activation. " +
8
9
  "Definitions persist under ~/.pi/agent/recipes; activation is reported separately.";
9
- export const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent Recipes or command templates as agent-callable tools";
10
+ export const REGISTER_TOOL_PROMPT_SNIPPET = "Persist command templates or Recipes with register_tool; use expose=false to retain without tool activation";
10
11
  export const REGISTER_TOOL_GUIDELINES = [
11
12
  "Use register_tool from=<skill>/<recipe> with defaults={...} to specialize a maintained Recipe without copying its contract.",
12
13
  "Use register_tool template only for trusted command templates, and use register_tool draft only for captured draft promotion.",
13
- "After register_tool succeeds, trust its callable_now and activation result; persistence alone is not callability.",
14
+ "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.",
15
+ "A register_tool retained_recipe receipt with activation=not_requested is deliberate retention, not failed activation; reuse its exact Recipe path through spawn.",
16
+ "When register_tool requests tool exposure, trust callable_now and activation; persistence alone is not callability.",
14
17
  'Set template=null or template="" in register_tool to delete a persisted tool.',
15
18
  "Set update=true in register_tool to overwrite an existing tool registration.",
16
19
  ];
@@ -31,6 +34,7 @@ export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
31
34
  from: "Recipe to specialize by canonical <skill>/<recipe> identity or explicit .json/.md path. Inherits async, args/types, source defaults, artifacts, Control, and runtime origins.",
32
35
  defaults: "Optional caller-owned defaults. Keys and values must satisfy the effective source or command-template argument contract.",
33
36
  draft: "Promote a captured draft Recipe path from ~/.pi/agent/recipes/drafts. This is a source mode; do not combine it with from or template.",
37
+ expose: "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.",
34
38
  async: "Set true for a co-located async template recipe. Omit for ordinary command templates or file-backed recipe references.",
35
39
  template: "Trusted command template with {arg} or {arg=default} placeholders. To specialize a Recipe, use from instead. Omitted updates keep the old template; empty string deletes the tool.",
36
40
  templateArray: "Sequential command-template composition array. Leaves may be strings or objects with template/defaults/timeout/retry/failure/recover.",
@@ -67,6 +67,10 @@ export declare function discoverRecipeSources(sources: RecipesDiscoverySource[])
67
67
  export declare function discoverRecipes(roots: string[]): RecipesDiscoveryResult;
68
68
  export declare function createRecipeIntegrityManifest(result: RecipesDiscoveryResult): RecipeIntegrityManifestEntry[];
69
69
  export declare function getShadowedLaunchDiagnostic(result: RecipesDiscoveryResult, id: string): Record<string, unknown> | undefined;
70
+ export declare function inventoryRetainedRecipes(recipeRoot: string, resolutionContext?: RecipeResolutionContext): {
71
+ recipes: Array<Record<string, unknown>>;
72
+ diagnostics: string[];
73
+ };
70
74
  export declare function listDraftRecipes(root: string): Array<Record<string, unknown>>;
71
75
  export declare function summarizeDiscovery(result: RecipesDiscoveryResult): Record<string, unknown>;
72
76
  export declare function summarizeRegisteredToolArgs(tool: RegisteredTool): {
@@ -550,6 +550,28 @@ function templatePreview(value) {
550
550
  ? `${rendered.slice(0, 117)}...`
551
551
  : rendered;
552
552
  }
553
+ export function inventoryRetainedRecipes(recipeRoot, resolutionContext) {
554
+ const discovery = discoverRecipeSources([
555
+ { root: join(recipeRoot, "library"), resolutionContext },
556
+ ]);
557
+ return {
558
+ recipes: [...discovery.active.values()]
559
+ .map((entry) => ({
560
+ id: entry.id,
561
+ path: entry.path,
562
+ source_kind: "user_retained_recipe",
563
+ tool: false,
564
+ invalid: entry.invalid,
565
+ disabled: entry.disabled,
566
+ ...(entry.config ? { async: entry.config.async === true } : {}),
567
+ ...(entry.config?.description ? { description: entry.config.description } : {}),
568
+ ...(entry.diagnostics.length ? { diagnostics: entry.diagnostics } : {}),
569
+ ...(entry.riskLabels.length ? { risk_labels: entry.riskLabels } : {}),
570
+ }))
571
+ .sort((left, right) => left.id.localeCompare(right.id)),
572
+ diagnostics: discovery.diagnostics,
573
+ };
574
+ }
553
575
  export function listDraftRecipes(root) {
554
576
  return listRecipeFiles(root).map((path) => {
555
577
  for (let attempt = 0; attempt < 3; attempt += 1) {
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import type { CommandTemplateValue } from "./command-templates.ts";
7
7
  import * as CommandTemplates from "./command-templates.ts";
8
+ export declare const MAX_RECIPE_FILE_BYTES: number;
8
9
  export interface TemplateRecipeImportBinding {
9
10
  from?: string;
10
11
  defaults?: Record<string, unknown>;
@@ -9,7 +9,7 @@ import * as CommandTemplates from "./command-templates.js";
9
9
  import * as Paths from "./paths.js";
10
10
  import * as RecipeControl from "./recipe-control.js";
11
11
  import * as Schema from "./schema.js";
12
- const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
12
+ export const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
13
13
  const MAX_RECIPE_IMPORT_DEPTH = 32;
14
14
  const RESERVED_AUTHORED_RECIPE_VALUE_NAMES = new Set([
15
15
  "recipe_dir",
@@ -44,7 +44,7 @@ function readRecipeSource(path) {
44
44
  }
45
45
  function inferRecipeRoot(path) {
46
46
  const parent = dirname(resolve(path));
47
- return basename(parent) === "drafts" ? dirname(parent) : parent;
47
+ return ["drafts", "library"].includes(basename(parent)) ? dirname(parent) : parent;
48
48
  }
49
49
  function pathKey(path, recipeRoot) {
50
50
  const relation = relative(resolve(recipeRoot), resolve(path));
@@ -53,8 +53,9 @@ function pathKey(path, recipeRoot) {
53
53
  }
54
54
  return relation.replaceAll("\\", "/");
55
55
  }
56
- function recipeName(path) {
57
- return basename(path).replace(/\.(?:json|md)$/u, "");
56
+ function recipeName(path, recipeRoot) {
57
+ const name = basename(path).replace(/\.(?:json|md)$/u, "");
58
+ return dirname(resolve(path)) === resolve(recipeRoot, "library") ? `library/${name}` : name;
58
59
  }
59
60
  function lineageFilename(lineageName) {
60
61
  if (!lineageName || lineageName === "." || lineageName === "..") {
@@ -117,8 +118,10 @@ function readIndexedLedger(indexedName, recipeRoot) {
117
118
  const current = readLedger(indexedName, recipeRoot);
118
119
  return current ? { record: current } : undefined;
119
120
  }
120
- function findRelocatedRecipe(index, fingerprint, recipeRoot) {
121
+ function findRelocatedRecipe(index, fingerprint, recipeRoot, newPath) {
121
122
  const candidates = Object.entries(index.paths).flatMap(([oldPath, indexedName]) => {
123
+ if (oldPath.startsWith("library/") !== newPath.startsWith("library/"))
124
+ return [];
122
125
  if (existsSync(join(recipeRoot, oldPath)))
123
126
  return [];
124
127
  const found = readIndexedLedger(indexedName, recipeRoot);
@@ -157,7 +160,7 @@ export function ensureRecipeLineage(path, now = new Date(), recipeRoot = inferRe
157
160
  if (!source)
158
161
  return false;
159
162
  const key = pathKey(path, recipeRoot);
160
- const name = recipeName(path);
163
+ const name = recipeName(path, recipeRoot);
161
164
  const index = readIndex(recipeRoot);
162
165
  const indexedName = index.paths[key];
163
166
  if (indexedName) {
@@ -253,11 +256,11 @@ export function recordRecipeLaunch(path, now = new Date(), kind = "direct", reci
253
256
  return false;
254
257
  const { fingerprint } = source;
255
258
  const key = pathKey(path, recipeRoot);
256
- const desiredName = recipeName(path);
259
+ const desiredName = recipeName(path, recipeRoot);
257
260
  const index = readIndex(recipeRoot);
258
261
  const relocated = index.paths[key]
259
262
  ? undefined
260
- : findRelocatedRecipe(index, fingerprint, recipeRoot);
263
+ : findRelocatedRecipe(index, fingerprint, recipeRoot, key);
261
264
  const indexedName = index.paths[key] ?? relocated?.indexedName;
262
265
  const found = indexedName
263
266
  ? readIndexedLedger(indexedName, recipeRoot)
@@ -298,7 +301,7 @@ export function recordRecipeLaunch(path, now = new Date(), kind = "direct", reci
298
301
  former_names: renamed
299
302
  ? appendBounded(stored?.former_names ?? [], stored.lineage_name)
300
303
  : relocated
301
- ? appendBounded(stored?.former_names ?? [], recipeName(relocated.path))
304
+ ? appendBounded(stored?.former_names ?? [], recipeName(join(recipeRoot, relocated.path), recipeRoot))
302
305
  : stored?.former_names ?? [],
303
306
  former_paths: relocated
304
307
  ? appendBounded(stored?.former_paths ?? [], relocated.path)
@@ -444,7 +447,7 @@ export function moveRecipeUsage(fromPath, toPath, recipeRoot = inferRecipeRoot(f
444
447
  const toKey = pathKey(toPath, recipeRoot);
445
448
  const index = readIndex(recipeRoot);
446
449
  const oldName = index.paths[fromKey];
447
- const newName = recipeName(toPath);
450
+ const newName = recipeName(toPath, recipeRoot);
448
451
  if (!oldName) {
449
452
  return index.paths[toKey] === newName && Boolean(readLedger(newName, recipeRoot));
450
453
  }
@@ -10,6 +10,7 @@ export interface RegisterToolInput {
10
10
  name?: string;
11
11
  description?: string;
12
12
  async?: boolean;
13
+ expose?: boolean;
13
14
  template?: CommandTemplates.CommandTemplateValue | null;
14
15
  from?: string;
15
16
  defaults?: Record<string, unknown>;
@@ -19,13 +20,16 @@ export interface RegisterToolInput {
19
20
  }
20
21
  export interface RegisterToolResultDetails {
21
22
  active_tool?: boolean;
22
- activation?: "current_session" | "unverified";
23
+ activation?: "current_session" | "unverified" | "not_requested";
23
24
  activation_boundary?: string;
24
25
  args?: string[];
25
26
  async?: boolean;
26
27
  callable_now?: boolean;
27
28
  defaults?: Record<string, string>;
28
29
  host_registered?: boolean;
30
+ kind?: "retained_recipe";
31
+ recipe?: string;
32
+ result?: "retained";
29
33
  next_actions?: string[];
30
34
  optional_args?: string[];
31
35
  persisted?: boolean;