@llblab/pi-actors 0.57.0 → 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.
Files changed (47) hide show
  1. package/AGENTS.md +9 -1
  2. package/BACKLOG.md +14 -1
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +9 -1
  5. package/dist/lib/file-state.d.ts +1 -0
  6. package/dist/lib/file-state.js +8 -2
  7. package/dist/lib/prompts.d.ts +2 -1
  8. package/dist/lib/prompts.js +6 -2
  9. package/dist/lib/recipes-discovery.d.ts +4 -0
  10. package/dist/lib/recipes-discovery.js +22 -0
  11. package/dist/lib/recipes-references.d.ts +1 -0
  12. package/dist/lib/recipes-references.js +1 -1
  13. package/dist/lib/recipes-usage.js +12 -9
  14. package/dist/lib/registry.d.ts +5 -1
  15. package/dist/lib/registry.js +199 -7
  16. package/dist/lib/run-delivery.d.ts +1 -1
  17. package/dist/lib/run-delivery.js +146 -32
  18. package/dist/lib/tools-inspect.js +6 -3
  19. package/dist/lib/tools-register.js +1 -0
  20. package/dist/lib/tools-response.js +6 -1
  21. package/dist/skills/actors/SKILL.md +11 -2
  22. package/dist/skills/actors/references/persistent-tools.md +20 -2
  23. package/dist/skills/actors/references/runs.md +1 -1
  24. package/dist/skills/recipe-memory/SKILL.md +6 -0
  25. package/docs/README.md +1 -0
  26. package/docs/agent-experience.md +42 -3
  27. package/docs/capability-memory.md +166 -0
  28. package/docs/coordinator-delivery.md +2 -2
  29. package/docs/inspection.md +3 -1
  30. package/docs/output-projection-map.md +4 -4
  31. package/docs/releasing.md +4 -0
  32. package/docs/tool-registry.md +20 -1
  33. package/lib/file-state.ts +7 -3
  34. package/lib/prompts.ts +7 -2
  35. package/lib/recipes-discovery.ts +26 -0
  36. package/lib/recipes-references.ts +1 -1
  37. package/lib/recipes-usage.ts +11 -8
  38. package/lib/registry.ts +195 -10
  39. package/lib/run-delivery.ts +130 -31
  40. package/lib/tools-inspect.ts +6 -3
  41. package/lib/tools-register.ts +1 -0
  42. package/lib/tools-response.ts +8 -1
  43. package/package.json +1 -1
  44. package/skills/actors/SKILL.md +11 -2
  45. package/skills/actors/references/persistent-tools.md +20 -2
  46. package/skills/actors/references/runs.md +1 -1
  47. 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.
@@ -91,7 +93,7 @@ For ordinary Run-file investigation, follow the accepted [same-session research
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_core_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. Preserve structured original coverage facts and unknown upstream/fallback completeness/privacy outside metadata; fallback has no primary kind. 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. Validated compiler header rows alone may hide whole reference/location data in compact view; 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_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.
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,6 +168,12 @@ 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.
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.
@@ -18,7 +19,7 @@ Outcome: JSON structured inputs, block-YAML runtime-output metadata and unchange
18
19
 
19
20
  Exit: New owned runtime projections use YAML without pseudo-records or serialized JSON objects: bare when metadata-only, `---`-delimited headers only when a separate text body follows. Literal content, meaning, source/privacy bounds and saved messages remain intact. Service-carrier adoption has its own evidence gate; do not delete current framing to simulate completion.
20
21
 
21
- - [ ] **F1.7 — Accept the rollout on a loaded host.** Source/headless audit is done: one shared framing owner keeps exactly one host-separation blank line on migrated paths, no runtime-authored object dump or pseudo-record remains in the migrated set, history/literal bodies are unchanged, and source/compiled/package parity passes. Remainder is the permitted loaded terminal/phone observation of one rollout candidate, which needs the same separate authorization as U1; keep capture codecs and live claims distinct and claim no automatic default activation.
22
+ - [ ] **F1.7 — Accept the rollout on a loaded host.** Source/headless audit is done: one shared framing owner keeps exactly one host-separation blank line on migrated paths, no runtime-authored object dump or pseudo-record remains in the migrated set, history/literal bodies are unchanged, and source/compiled/package parity passes. Remainder is the permitted loaded terminal/phone observation of one rollout candidate, including rooted completion headers, English read-first guidance and no post-Markdown runtime footer, which needs the same separate authorization as U1; keep capture codecs and live claims distinct and claim no automatic default activation.
22
23
 
23
24
  ### U1 — Loaded UI/AX acceptance
24
25
 
@@ -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
@@ -4,6 +4,17 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.58.0: Explicit Capability Retention
8
+
9
+ - `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.
10
+ - `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.
11
+ - `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.
12
+ - `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.
13
+
14
+ ## 0.57.1: Rooted Completion Headers
15
+
16
+ - `Completion Headers`: New captures keep all runtime metadata above the Actor Markdown, with no service/file footer. File references share literal roots with relative entries, English purposes and a read-first pointer. External roots and unresolved bases stay explicit; declarations do not prove production. Known cut counts remain at the cut. Original Markdown, source/generation/privacy fences, shared budgets and exact historical replay are preserved.
17
+
7
18
  ## 0.57.0: YAML Runtime Output
8
19
 
9
20
  - `Control and Inspection`: Compact Control receipts and inspect views/refusals use block YAML with native lists, whole-value omissions and conservative delivery/handling facts. Generated tool schemas use YAML when renderable, retaining the legacy JSON fallback and explicit verbose JSON. Recipe view=registry aliases summary. Large argument groups and next actions stay bounded without breaking verbose inspection.
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
 
@@ -228,6 +234,8 @@ Core files:
228
234
 
229
235
  The runtime preserves owner filtering, process-identity verification, lifecycle locking, shutdown kill independent of actor-local Control capacity, terminal reconciliation, bounded captures, owned Pi sessions, path containment, and redaction. Trace/Control quotas do not constrain user-declared artifacts, repositories, media sources, complete captures, or actor-owned workload state. Restart clears generation-local Trace, Control, endpoint, execution, and terminal evidence; archive preserves the bounded tree, while prune preserves only requested artifacts.
230
236
 
237
+ New completion captures in 0.57.1 keep runtime metadata above the Actor Markdown, with no service/file footer. [Rooted completion files](docs/agent-experience.md#rooted-completion-files) show one explicit path base per group, relative entries, English purpose labels and a read-first pointer. External paths have separate roots; unresolved bases stay explicit. Existing captured messages keep their original layout.
238
+
231
239
  ## Actor Inspector
232
240
 
233
241
  Open the owner-filtered TUI:
@@ -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 {
@@ -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;