@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.
- package/AGENTS.md +11 -3
- package/BACKLOG.md +14 -1
- package/CHANGELOG.md +13 -1
- package/README.md +7 -1
- package/dist/lib/file-state.d.ts +1 -0
- package/dist/lib/file-state.js +8 -2
- package/dist/lib/pi.js +1 -1
- package/dist/lib/prompts.d.ts +2 -1
- package/dist/lib/prompts.js +6 -2
- package/dist/lib/recipes-discovery.d.ts +4 -0
- package/dist/lib/recipes-discovery.js +22 -0
- package/dist/lib/recipes-references.d.ts +1 -0
- package/dist/lib/recipes-references.js +1 -1
- package/dist/lib/recipes-usage.js +12 -9
- package/dist/lib/registry.d.ts +5 -1
- package/dist/lib/registry.js +199 -7
- package/dist/lib/run-delivery.d.ts +1 -1
- package/dist/lib/run-delivery.js +19 -9
- package/dist/lib/tools-inspect.js +6 -3
- package/dist/lib/tools-register.js +1 -0
- package/dist/lib/tools-response.js +20 -16
- package/dist/skills/actors/SKILL.md +11 -2
- package/dist/skills/actors/references/persistent-tools.md +20 -2
- package/dist/skills/actors/references/runs.md +1 -1
- package/dist/skills/recipe-memory/SKILL.md +6 -0
- package/docs/README.md +1 -0
- package/docs/agent-experience.md +5 -6
- package/docs/capability-memory.md +121 -0
- package/docs/coordinator-delivery.md +2 -2
- package/docs/inspection.md +3 -1
- package/docs/output-projection-map.md +1 -1
- package/docs/releasing.md +5 -1
- package/docs/tool-registry.md +37 -1
- package/lib/file-state.ts +7 -3
- package/lib/pi.ts +1 -1
- package/lib/prompts.ts +7 -2
- package/lib/recipes-discovery.ts +26 -0
- package/lib/recipes-references.ts +1 -1
- package/lib/recipes-usage.ts +11 -8
- package/lib/registry.ts +195 -10
- package/lib/run-delivery.ts +22 -11
- package/lib/tools-inspect.ts +6 -3
- package/lib/tools-register.ts +1 -0
- package/lib/tools-response.ts +20 -13
- package/package.json +1 -1
- package/skills/actors/SKILL.md +11 -2
- package/skills/actors/references/persistent-tools.md +20 -2
- package/skills/actors/references/runs.md +1 -1
- package/skills/recipe-memory/SKILL.md +6 -0
package/dist/lib/registry.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Zones: registry mutations, persistence, runtime activation
|
|
4
4
|
* Owns register/update/delete validation, persistence, runtime side effects, and result payloads
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
7
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
6
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, unlinkSync } from "node:fs";
|
|
7
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
8
|
import * as CommandTemplates from "./command-templates.js";
|
|
9
9
|
import * as Config from "./config.js";
|
|
10
10
|
import * as ExecutionOutput from "./execution-output.js";
|
|
@@ -560,6 +560,14 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
|
|
|
560
560
|
? params
|
|
561
561
|
: {};
|
|
562
562
|
assertRegisterToolInputModes(rawInput);
|
|
563
|
+
if (Object.hasOwn(rawInput, "expose")) {
|
|
564
|
+
const exposure = rawInput.expose;
|
|
565
|
+
if (typeof exposure !== "boolean")
|
|
566
|
+
throw new RetainedRecipeRefusal("expose", "expose must be Boolean.");
|
|
567
|
+
// Select under the existing name reservation; acquire retention locks only after it releases.
|
|
568
|
+
if (exposure === false)
|
|
569
|
+
return () => retainRecipe(rawInput, ctx, deps);
|
|
570
|
+
}
|
|
563
571
|
let input = rawInput;
|
|
564
572
|
if (!input.name)
|
|
565
573
|
return listTools(deps);
|
|
@@ -702,12 +710,196 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
|
|
|
702
710
|
},
|
|
703
711
|
};
|
|
704
712
|
}
|
|
713
|
+
class RetainedRecipeRefusal extends Error {
|
|
714
|
+
reason;
|
|
715
|
+
constructor(field, reason, cause) {
|
|
716
|
+
super(ExecutionOutput.formatToolText(registryMetadataText({ field, result: reason })), { cause });
|
|
717
|
+
this.reason = reason;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function retainedDirectory(path) {
|
|
721
|
+
const stat = lstatSync(path, { bigint: true });
|
|
722
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
723
|
+
throw new RetainedRecipeRefusal("storage", "Retained storage must be a regular non-symlink directory.");
|
|
724
|
+
}
|
|
725
|
+
return { path, realpath: realpathSync.native(path), dev: stat.dev, ino: stat.ino };
|
|
726
|
+
}
|
|
727
|
+
function recheckRetainedDirectory(frame) {
|
|
728
|
+
const current = retainedDirectory(frame.path);
|
|
729
|
+
if (current.realpath !== frame.realpath || current.dev !== frame.dev || current.ino !== frame.ino) {
|
|
730
|
+
throw new RetainedRecipeRefusal("storage", "Retained storage identity changed.");
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function retainedSlotAbsent(path, ownTargetPublished = false) {
|
|
734
|
+
for (const candidate of ownTargetPublished ? [path.replace(/\.json$/u, ".md")] : [path, path.replace(/\.json$/u, ".md")]) {
|
|
735
|
+
try {
|
|
736
|
+
lstatSync(candidate);
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
let code;
|
|
740
|
+
try {
|
|
741
|
+
code = error?.code;
|
|
742
|
+
}
|
|
743
|
+
catch (observationError) {
|
|
744
|
+
throw new AggregateError([error, observationError], "Retained slot observation failed.", { cause: error });
|
|
745
|
+
}
|
|
746
|
+
if (code === "ENOENT")
|
|
747
|
+
continue;
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
throw new RetainedRecipeRefusal("name", "Retained Recipe already exists; this path is create-only.");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function retainedFile(path) {
|
|
754
|
+
const stat = lstatSync(path, { bigint: true });
|
|
755
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
756
|
+
throw new RetainedRecipeRefusal("storage", "Retained target must be a regular non-symlink file.");
|
|
757
|
+
}
|
|
758
|
+
if (stat.size > BigInt(RecipesReferences.MAX_RECIPE_FILE_BYTES))
|
|
759
|
+
throw new RetainedRecipeRefusal("storage", "Retained target exceeds the source size limit.");
|
|
760
|
+
return { dev: stat.dev, ino: stat.ino, size: stat.size, mtime: stat.mtimeNs, ctime: stat.ctimeNs };
|
|
761
|
+
}
|
|
762
|
+
function withinRetainedSource(root, path) {
|
|
763
|
+
const relation = relative(resolve(root), resolve(path));
|
|
764
|
+
return relation === "" || (!isAbsolute(relation) && !/^\.\.(?:[\\/]|$)/u.test(relation));
|
|
765
|
+
}
|
|
766
|
+
function assertRetainedSourceLifetime(source, root, field) {
|
|
767
|
+
const drafts = join(root, "drafts");
|
|
768
|
+
if (withinRetainedSource(drafts, source) ||
|
|
769
|
+
(existsSync(drafts) && existsSync(source) && withinRetainedSource(realpathSync.native(drafts), realpathSync.native(source)))) {
|
|
770
|
+
throw new RetainedRecipeRefusal(field, "Retained creation cannot reference a managed disposable draft.");
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function assertRetainedTemplateLifetimes(value, path, root, context) {
|
|
774
|
+
if (typeof value === "string") {
|
|
775
|
+
const source = RecipesReferences.resolveRecipeReferencePath(value, dirname(path), context.activeSkills);
|
|
776
|
+
if (source)
|
|
777
|
+
assertRetainedSourceLifetime(source, root, "template");
|
|
778
|
+
}
|
|
779
|
+
else if (Array.isArray(value)) {
|
|
780
|
+
for (const child of value)
|
|
781
|
+
assertRetainedTemplateLifetimes(child, path, root, context);
|
|
782
|
+
}
|
|
783
|
+
else if (value && typeof value === "object") {
|
|
784
|
+
const node = value;
|
|
785
|
+
assertRetainedTemplateLifetimes(node.template, path, root, context);
|
|
786
|
+
assertRetainedTemplateLifetimes(node.recover, path, root, context);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function retainRecipe(input, ctx, deps) {
|
|
790
|
+
assertRegisterToolInputModes(input);
|
|
791
|
+
if (typeof input.name !== "string" || !input.name.trim()) {
|
|
792
|
+
throw new RetainedRecipeRefusal("name", "Retained creation requires a name.");
|
|
793
|
+
}
|
|
794
|
+
const name = Identity.normalizeToolName(input.name);
|
|
795
|
+
if (!name || deps.reservedToolNames.has(name)) {
|
|
796
|
+
throw new RetainedRecipeRefusal("name", "Invalid or reserved retained Recipe name.");
|
|
797
|
+
}
|
|
798
|
+
const blocker = deps.getToolNameBlocker(name);
|
|
799
|
+
if (blocker)
|
|
800
|
+
throw new Error(ExecutionOutput.formatToolText(blocker));
|
|
801
|
+
if (input.update || Object.hasOwn(input, "draft")) {
|
|
802
|
+
throw new RetainedRecipeRefusal("expose", "Retained creation does not support update or draft promotion.");
|
|
803
|
+
}
|
|
804
|
+
const template = getInputTemplate(input.template);
|
|
805
|
+
if (template === null || template === "") {
|
|
806
|
+
throw new RetainedRecipeRefusal("template", "Retained deletion is not supported.");
|
|
807
|
+
}
|
|
808
|
+
if (template === undefined && !input.from) {
|
|
809
|
+
throw new RetainedRecipeRefusal("source", "Retained creation requires template or from.");
|
|
810
|
+
}
|
|
811
|
+
const root = resolve(getRecipeRoot(deps)), library = join(root, "library"), path = join(library, `${name}.json`);
|
|
812
|
+
const context = getRegistrationResolutionContext(ctx, deps);
|
|
813
|
+
const from = input.from === undefined ? undefined : normalizeFromReference(input.from, context);
|
|
814
|
+
if (from) {
|
|
815
|
+
const source = from.endsWith(".json") || from.endsWith(".md") ? from
|
|
816
|
+
: RecipesReferences.resolveRecipeReferencePath(from, context.cwd, context.activeSkills);
|
|
817
|
+
if (source)
|
|
818
|
+
assertRetainedSourceLifetime(source, root, "from");
|
|
819
|
+
}
|
|
820
|
+
const authored = buildAuthoredRecipe({ ...input, ...(from ? { from } : {}) }, undefined, undefined);
|
|
821
|
+
if (!from)
|
|
822
|
+
assertRetainedTemplateLifetimes(authored.template, path, root, context);
|
|
823
|
+
const bytes = `${JSON.stringify(authored, null, 2)}\n`;
|
|
824
|
+
if (Buffer.byteLength(bytes) > RecipesReferences.MAX_RECIPE_FILE_BYTES) {
|
|
825
|
+
throw new RetainedRecipeRefusal("source", "Retained Recipe exceeds the 1 MiB source limit.");
|
|
826
|
+
}
|
|
827
|
+
const candidate = RecipesDiscovery.admitUserRecipe(path, context, authored);
|
|
828
|
+
if (!candidate.validated || !candidate.tool) {
|
|
829
|
+
throw new RetainedRecipeRefusal("source", "Retained Recipe admission failed; check the source and caller argument contract.", candidate.diagnostics);
|
|
830
|
+
}
|
|
831
|
+
let stage = "storage";
|
|
832
|
+
try {
|
|
833
|
+
return withFileMutationLock(root, () => {
|
|
834
|
+
mkdirSync(root, { recursive: true });
|
|
835
|
+
const rootFrame = retainedDirectory(root);
|
|
836
|
+
return withFileMutationLock(library, () => {
|
|
837
|
+
recheckRetainedDirectory(rootFrame);
|
|
838
|
+
mkdirSync(library, { recursive: true });
|
|
839
|
+
const libraryFrame = retainedDirectory(library);
|
|
840
|
+
const relation = relative(rootFrame.realpath, libraryFrame.realpath);
|
|
841
|
+
if ((process.platform === "win32" ? relation.toLowerCase() : relation) !== "library") {
|
|
842
|
+
throw new RetainedRecipeRefusal("storage", "Retained library escapes its Recipe root.");
|
|
843
|
+
}
|
|
844
|
+
const recheck = () => { recheckRetainedDirectory(rootFrame); recheckRetainedDirectory(libraryFrame); };
|
|
845
|
+
return withFileMutationLock(path, () => {
|
|
846
|
+
recheck();
|
|
847
|
+
retainedSlotAbsent(path);
|
|
848
|
+
stage = "persistence";
|
|
849
|
+
writeTextAtomic(path, bytes, { exclusive: true, onBeforeReplace: () => { recheck(); retainedSlotAbsent(path); } });
|
|
850
|
+
stage = "persisted_admission";
|
|
851
|
+
recheck();
|
|
852
|
+
retainedSlotAbsent(path, true);
|
|
853
|
+
const before = retainedFile(path);
|
|
854
|
+
if (readFileSync(path, "utf8") !== bytes)
|
|
855
|
+
throw new RetainedRecipeRefusal("storage", "Persisted retained content changed.");
|
|
856
|
+
const admitted = RecipesDiscovery.admitUserRecipe(path, context);
|
|
857
|
+
if (!admitted.validated || !admitted.tool)
|
|
858
|
+
throw new RetainedRecipeRefusal("source", "Persisted retained Recipe admission failed.", admitted.diagnostics);
|
|
859
|
+
const after = retainedFile(path);
|
|
860
|
+
if (Object.keys(before).some(key => before[key] !== after[key]) ||
|
|
861
|
+
readFileSync(path, "utf8") !== bytes)
|
|
862
|
+
throw new RetainedRecipeRefusal("storage", "Persisted retained content changed.");
|
|
863
|
+
recheck();
|
|
864
|
+
retainedSlotAbsent(path, true);
|
|
865
|
+
stage = "receipt";
|
|
866
|
+
const args = RecipesDiscovery.summarizeRegisteredToolArgs(admitted.tool);
|
|
867
|
+
const source = from ? from.endsWith(".json") || from.endsWith(".md") ? `<explicit-file:${basename(from)}>` : from : "template";
|
|
868
|
+
const recipe = registrySelected("recipe", path), selectedName = registrySelected("tool", name);
|
|
869
|
+
const selectedSource = registrySelected("source", source);
|
|
870
|
+
const required = ToolsResponse.selectYamlField("required_args", args.required, REGISTRY_METADATA_BUDGET);
|
|
871
|
+
const optional = ToolsResponse.selectYamlField("optional_args", args.optional, REGISTRY_METADATA_BUDGET);
|
|
872
|
+
const facts = { kind: "retained_recipe", result: "retained", persisted: true, resolved: true,
|
|
873
|
+
validated: true, activation: "not_requested", async: admitted.async };
|
|
874
|
+
const text = growRegistryDocument(() => ({ ...selectedName.selected, ...facts, ...recipe.selected, ...selectedSource.selected,
|
|
875
|
+
...required.selected, ...optional.selected, next: { tool: "inspect", target: "recipes", view: "summary", verbose: true } }), [recipe, selectedName, selectedSource, required, optional]);
|
|
876
|
+
const result = {
|
|
877
|
+
content: [textContent(ExecutionOutput.formatToolText(text))],
|
|
878
|
+
details: { ...facts, tool: name, recipe: path, source, required_args: args.required, optional_args: args.optional,
|
|
879
|
+
next_actions: ["inspect target=recipes view=summary verbose=true"] },
|
|
880
|
+
};
|
|
881
|
+
stage = "release";
|
|
882
|
+
return result;
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
catch (error) {
|
|
888
|
+
const reason = error instanceof RetainedRecipeRefusal ? { reason: error.reason } : {};
|
|
889
|
+
throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ operation: "retain", stage, result: "unconfirmed",
|
|
890
|
+
effects: "unknown; inspect retained inventory before retry", ...reason,
|
|
891
|
+
next: { tool: "inspect", target: "recipes", view: "summary", verbose: true } })), { cause: error });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
705
894
|
export async function executeRegisterTool(params, ctx, deps) {
|
|
706
895
|
const input = params;
|
|
896
|
+
let result;
|
|
707
897
|
if (!input.name)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
898
|
+
result = executeRegisterToolUnlocked(params, ctx, deps);
|
|
899
|
+
else {
|
|
900
|
+
const name = Identity.normalizeToolName(input.name);
|
|
901
|
+
result = !name ? executeRegisterToolUnlocked(params, ctx, deps)
|
|
902
|
+
: withFileMutationLock(getToolRecipePath(deps, name), () => executeRegisterToolUnlocked(params, ctx, deps));
|
|
903
|
+
}
|
|
904
|
+
return typeof result === "function" ? result() : result;
|
|
713
905
|
}
|
|
@@ -66,7 +66,7 @@ export interface RunCompletionBatch {
|
|
|
66
66
|
/** Retained singleton capture; absence preserves older indexed bytes. Never a new-capture selector. */
|
|
67
67
|
readonly single_result?: true;
|
|
68
68
|
/** Captured record-list shape; no public format/version choice or source authority. */
|
|
69
|
-
readonly result_layout?: "records" | "run_headers" | "headings" | "headings_flat_omissions" | "headings_outcomes" | "headings_shared_results" | "headings_shared_kinds" | "frontmatter_v1" | "frontmatter_v2" | "completion_yaml_core_v1" | "completion_yaml_files_v1";
|
|
69
|
+
readonly result_layout?: "records" | "run_headers" | "headings" | "headings_flat_omissions" | "headings_outcomes" | "headings_shared_results" | "headings_shared_kinds" | "frontmatter_v1" | "frontmatter_v2" | "completion_yaml_core_v1" | "completion_yaml_files_v1" | "completion_yaml_files_v2";
|
|
70
70
|
/** Immutable scope/order/range data, not a caller-selected format or allocation authority. */
|
|
71
71
|
readonly numbering?: RunCompletionNumbering;
|
|
72
72
|
/** Formatting version/data only; no recipient ownership, read or publication capability. */
|
package/dist/lib/run-delivery.js
CHANGED
|
@@ -278,8 +278,11 @@ function parseInventorySource(value, batch) {
|
|
|
278
278
|
function isFrontmatterCompletionLayout(layout) {
|
|
279
279
|
return layout === "frontmatter_v1" || layout === "frontmatter_v2";
|
|
280
280
|
}
|
|
281
|
+
function isRootedCompletionLayout(layout) {
|
|
282
|
+
return layout === "completion_yaml_files_v1" || layout === "completion_yaml_files_v2";
|
|
283
|
+
}
|
|
281
284
|
function isYamlCompletionLayout(layout) {
|
|
282
|
-
return layout === "completion_yaml_core_v1" || layout
|
|
285
|
+
return layout === "completion_yaml_core_v1" || isRootedCompletionLayout(layout);
|
|
283
286
|
}
|
|
284
287
|
function isSharedCompletionLayout(layout) {
|
|
285
288
|
return layout === "headings_shared_results" || layout === "headings_shared_kinds" || isYamlCompletionLayout(layout) || isFrontmatterCompletionLayout(layout);
|
|
@@ -688,7 +691,7 @@ export function requestRunCompletionStream(input) {
|
|
|
688
691
|
});
|
|
689
692
|
}
|
|
690
693
|
export function admitRunCompletionBatch(input) {
|
|
691
|
-
return captureCompletionBatch(input, false, "
|
|
694
|
+
return captureCompletionBatch(input, false, "completion_yaml_files_v2");
|
|
692
695
|
}
|
|
693
696
|
/** Kernel-only opt-in; ordinary Pi capture stays fenced until session/lifecycle integration is admitted. */
|
|
694
697
|
export function captureRunCompletionStreamBatch(input) {
|
|
@@ -696,7 +699,7 @@ export function captureRunCompletionStreamBatch(input) {
|
|
|
696
699
|
}
|
|
697
700
|
/** Private ordinary candidate composition; stream capture retains its separately gated codec. */
|
|
698
701
|
export function captureRunCompletionYamlCandidateBatch(input) {
|
|
699
|
-
return captureCompletionBatch(input, false, "
|
|
702
|
+
return captureCompletionBatch(input, false, "completion_yaml_files_v2");
|
|
700
703
|
}
|
|
701
704
|
function captureCompletionBatch(input, numbered, layout = "frontmatter_v2") {
|
|
702
705
|
const now = (input.now ?? new Date()).toISOString();
|
|
@@ -1697,13 +1700,14 @@ function completionRootedFiles(row, member, locations) {
|
|
|
1697
1700
|
}
|
|
1698
1701
|
function prepareCompletionYamlProjection(safe, candidates, references, ordered) {
|
|
1699
1702
|
const packet = { batchId: safe.batch_id, records: [] };
|
|
1700
|
-
const rooted = safe.result_layout
|
|
1703
|
+
const rooted = isRootedCompletionLayout(safe.result_layout);
|
|
1704
|
+
const localSingleton = safe.result_layout === "completion_yaml_files_v2" && !safe.numbering && candidates.length === 1;
|
|
1701
1705
|
if (!safe.batch_id.isWellFormed())
|
|
1702
1706
|
return { state: "unsupported" };
|
|
1703
1707
|
for (const [index, { row, location }] of candidates.entries()) {
|
|
1704
1708
|
const member = ordered[index];
|
|
1705
1709
|
const displayed = safe.numbering ? safe.numbering.kind === "stream" ? safe.numbering.first_index + index : "?" : index;
|
|
1706
|
-
const metadata = { index: displayed };
|
|
1710
|
+
const metadata = localSingleton ? {} : { index: displayed };
|
|
1707
1711
|
for (const piece of row.pieces) {
|
|
1708
1712
|
if (piece.block)
|
|
1709
1713
|
continue;
|
|
@@ -1763,7 +1767,9 @@ function prepareCompletionYamlProjection(safe, candidates, references, ordered)
|
|
|
1763
1767
|
}
|
|
1764
1768
|
packet.records.push({ metadata, parts });
|
|
1765
1769
|
}
|
|
1766
|
-
const service = rooted ? {
|
|
1770
|
+
const service = rooted ? {
|
|
1771
|
+
header_layout: safe.result_layout === "completion_yaml_files_v2" ? "rooted_files_v2" : "rooted_files_v1",
|
|
1772
|
+
} : {};
|
|
1767
1773
|
const omitted = safe.members.length - candidates.length;
|
|
1768
1774
|
if (omitted) {
|
|
1769
1775
|
service.omitted_results = omitted;
|
|
@@ -2302,7 +2308,7 @@ export function readRunCompletionPresentation(content, value) {
|
|
|
2302
2308
|
hint.content_sha256 !== createHash("sha256").update(content).digest("hex"))
|
|
2303
2309
|
return full;
|
|
2304
2310
|
const segments = [];
|
|
2305
|
-
const rooted = core.packet.service?.header_layout === "rooted_files_v1";
|
|
2311
|
+
const rooted = core.packet.service?.header_layout === "rooted_files_v1" || core.packet.service?.header_layout === "rooted_files_v2";
|
|
2306
2312
|
const facts = Object.fromEntries(Object.entries(core.packet.service ?? {}).filter(([key]) => key !== "header_layout"));
|
|
2307
2313
|
for (const [index, record] of core.packet.records.entries()) {
|
|
2308
2314
|
if (index)
|
|
@@ -2551,6 +2557,8 @@ export function encodeCompletionYamlCarrier(input) {
|
|
|
2551
2557
|
(Object.hasOwn(packet, "service") && (!completionYamlMapping(packet.service) || !Object.keys(packet.service).length)) ||
|
|
2552
2558
|
(!packet.records.length && !Object.hasOwn(packet, "service")))
|
|
2553
2559
|
return unavailable;
|
|
2560
|
+
const localSingleton = packet.records.length === 1 && completionYamlMapping(packet.service) &&
|
|
2561
|
+
packet.service.header_layout === "rooted_files_v2" && !Object.hasOwn(packet.service, "numbering");
|
|
2554
2562
|
const rows = [];
|
|
2555
2563
|
for (let index = 0; index < Math.max(1, packet.records.length); index++) {
|
|
2556
2564
|
const row = packet.records[index];
|
|
@@ -2568,14 +2576,16 @@ export function encodeCompletionYamlCarrier(input) {
|
|
|
2568
2576
|
!completionYamlMapping(row.metadata) || !Array.isArray(row.parts))
|
|
2569
2577
|
return unavailable;
|
|
2570
2578
|
const meta = row.metadata;
|
|
2571
|
-
|
|
2579
|
+
const validIndex = localSingleton ? !Object.hasOwn(meta, "index")
|
|
2580
|
+
: meta.index === "?" || (typeof meta.index === "number" && Number.isSafeInteger(meta.index) && meta.index >= 0);
|
|
2581
|
+
if (!validIndex ||
|
|
2572
2582
|
typeof meta.run !== "string" || !meta.run.length || typeof meta.state !== "string" || !meta.state.length ||
|
|
2573
2583
|
(Object.hasOwn(meta, "kind") && meta.kind !== "primary" && meta.kind !== "diagnostic") ||
|
|
2574
2584
|
Object.hasOwn(meta, completionYamlServiceKey) ||
|
|
2575
2585
|
// JS enumerates array-index keys before semantic core fields; do not silently reorder the header.
|
|
2576
2586
|
Object.keys(meta).some(key => /^(0|[1-9]\d*)$/u.test(key) && Number(key) < 4294967295))
|
|
2577
2587
|
return unavailable;
|
|
2578
|
-
for (const key of ["index", "run", "state", ...(Object.hasOwn(meta, "kind") ? ["kind"] : []),
|
|
2588
|
+
for (const key of [...(localSingleton ? [] : ["index"]), "run", "state", ...(Object.hasOwn(meta, "kind") ? ["kind"] : []),
|
|
2579
2589
|
...Object.keys(meta).filter(key => !["index", "run", "state", "kind"].includes(key))]) {
|
|
2580
2590
|
Object.defineProperty(header, key, { value: meta[key], enumerable: true });
|
|
2581
2591
|
}
|
|
@@ -364,9 +364,6 @@ function inspectRecipes(requestedView, input, deps, context) {
|
|
|
364
364
|
if (view === "reviews") {
|
|
365
365
|
return ReviewDiagnostics.readAutomaticReviewDiagnostics({ recipeRoot });
|
|
366
366
|
}
|
|
367
|
-
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
368
|
-
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
369
|
-
]);
|
|
370
367
|
const resolutionContext = context && typeof context === "object" && "recipeResolutionContext" in context
|
|
371
368
|
? context.recipeResolutionContext
|
|
372
369
|
: undefined;
|
|
@@ -399,7 +396,11 @@ function inspectRecipes(requestedView, input, deps, context) {
|
|
|
399
396
|
skill: diagnostic.skill,
|
|
400
397
|
...(diagnostic.stem ? { stem: diagnostic.stem } : {}),
|
|
401
398
|
}));
|
|
399
|
+
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
400
|
+
{ root: recipeRoot, defaultTool: true, mutableUsage: true, resolutionContext },
|
|
401
|
+
]);
|
|
402
402
|
const discoverySummary = RecipesDiscovery.summarizeDiscovery(discovered);
|
|
403
|
+
const retained = RecipesDiscovery.inventoryRetainedRecipes(recipeRoot, resolutionContext);
|
|
403
404
|
const summary = {
|
|
404
405
|
...discoverySummary,
|
|
405
406
|
...(registryStatus
|
|
@@ -425,6 +426,8 @@ function inspectRecipes(requestedView, input, deps, context) {
|
|
|
425
426
|
error: `Ambiguous active Skill Recipe namespace ${name}`,
|
|
426
427
|
})),
|
|
427
428
|
drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
|
|
429
|
+
retained: retained.recipes,
|
|
430
|
+
retained_diagnostics: retained.diagnostics,
|
|
428
431
|
};
|
|
429
432
|
return {
|
|
430
433
|
...summary,
|
|
@@ -26,6 +26,7 @@ export function createRegisterToolDefinition(deps) {
|
|
|
26
26
|
description: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description),
|
|
27
27
|
defaults: looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.defaults),
|
|
28
28
|
draft: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.draft),
|
|
29
|
+
expose: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.expose),
|
|
29
30
|
from: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.from),
|
|
30
31
|
name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
|
|
31
32
|
template: unionSchema([
|
|
@@ -512,6 +512,10 @@ export function recipeRegistryNextActions(summary, view) {
|
|
|
512
512
|
if (summary.skill_recipe_catalog_partial === true) {
|
|
513
513
|
actions.push("inspect target=recipes view=imports verbose=true");
|
|
514
514
|
}
|
|
515
|
+
if ((Array.isArray(summary.retained) && summary.retained.length > 0) ||
|
|
516
|
+
(Array.isArray(summary.retained_diagnostics) && summary.retained_diagnostics.length > 0)) {
|
|
517
|
+
actions.push("inspect target=recipes view=summary verbose=true");
|
|
518
|
+
}
|
|
515
519
|
if (drafts.length > 0) {
|
|
516
520
|
actions.push("inspect target=recipes view=summary verbose=true");
|
|
517
521
|
const firstPath = typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
|
|
@@ -525,16 +529,20 @@ export function compactYamlDocument(value) {
|
|
|
525
529
|
const text = growYamlDocument(() => value, [], Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
|
|
526
530
|
return `\n${text.replace(/\n$/u, "")}`;
|
|
527
531
|
}
|
|
532
|
+
function recipeInspectionHint(action) {
|
|
533
|
+
if (action === "inspect target=recipes view=doctor")
|
|
534
|
+
return { view: "doctor" };
|
|
535
|
+
if (action === "inspect target=recipes view=imports verbose=true")
|
|
536
|
+
return { view: "imports", verbose: true };
|
|
537
|
+
if (action === "inspect target=recipes view=summary verbose=true")
|
|
538
|
+
return { view: "summary", verbose: true };
|
|
539
|
+
return undefined;
|
|
540
|
+
}
|
|
528
541
|
/** Only the closed read-only Recipe inspect literals become typed next; other advice stays opaque content. */
|
|
529
542
|
export function nextActionMetadata(action, byteBudget) {
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
["inspect target=recipes view=summary verbose=true", "summary", true],
|
|
534
|
-
];
|
|
535
|
-
const match = recognized.find(([literal]) => literal === action);
|
|
536
|
-
if (match) {
|
|
537
|
-
const typed = { next: { tool: "inspect", target: "recipes", view: match[1], ...(match[2] ? { verbose: true } : {}) } };
|
|
543
|
+
const hint = recipeInspectionHint(action);
|
|
544
|
+
if (hint) {
|
|
545
|
+
const typed = { next: { tool: "inspect", target: "recipes", ...hint } };
|
|
538
546
|
return { full: typed, selected: typed };
|
|
539
547
|
}
|
|
540
548
|
const instruction = selectYamlField("instruction", action, byteBudget);
|
|
@@ -542,19 +550,15 @@ export function nextActionMetadata(action, byteBudget) {
|
|
|
542
550
|
}
|
|
543
551
|
/** Only these existing read-only inspection hints become action nodes; other advice stays content. */
|
|
544
552
|
export function formatInspectionHint(action) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (action === "inspect target=recipes view=imports verbose=true")
|
|
548
|
-
return formatInspectNextAction("recipes", "imports", true);
|
|
549
|
-
if (action === "inspect target=recipes view=summary verbose=true")
|
|
550
|
-
return formatInspectNextAction("recipes", "summary", true);
|
|
551
|
-
return formatMarkdownField("instruction", action);
|
|
553
|
+
const hint = recipeInspectionHint(action);
|
|
554
|
+
return hint ? formatInspectNextAction("recipes", hint.view, hint.verbose) : formatMarkdownField("instruction", action);
|
|
552
555
|
}
|
|
553
556
|
export function compactRecipeRegistry(summary) {
|
|
554
557
|
const count = (key, source) => Array.isArray(source) ? source.length : 0;
|
|
555
558
|
const active = count("active", summary.active), shadowed = count("shadowed", summary.shadowed);
|
|
556
559
|
const invalid = count("invalid", summary.invalid), disabled = count("disabled", summary.disabled);
|
|
557
560
|
const diagnostics = count("diagnostics", summary.diagnostics), drafts = count("drafts", summary.drafts);
|
|
561
|
+
const retained = count("retained", summary.retained);
|
|
558
562
|
const recommendations = count("recommendations", summary.recommendations);
|
|
559
563
|
const skillCatalogPartial = summary.skill_recipe_catalog_partial === true;
|
|
560
564
|
const currentPolicy = Array.isArray(summary.active)
|
|
@@ -564,7 +568,7 @@ export function compactRecipeRegistry(summary) {
|
|
|
564
568
|
? summary.next_actions
|
|
565
569
|
: [];
|
|
566
570
|
const hint = nextActions.length ? nextActionMetadata(nextActions[0], Limits.RUN_DELIVERY_MODEL_MAX_BYTES) : undefined;
|
|
567
|
-
const render = () => ({ recipes: "registry", active, drafts, shadowed, invalid, disabled,
|
|
571
|
+
const render = () => ({ recipes: "registry", active, drafts, retained, shadowed, invalid, disabled,
|
|
568
572
|
current_policy: currentPolicy, skill_catalog_partial: skillCatalogPartial, recommendations, diagnostics,
|
|
569
573
|
...(hint ? hint.selected : {}) });
|
|
570
574
|
return hint
|
|
@@ -19,6 +19,9 @@ Make a maintained capability a persistent agent-callable tool
|
|
|
19
19
|
Keep the same capability but narrow caller defaults
|
|
20
20
|
→ use register_tool from=<skill>/<recipe> defaults={...}
|
|
21
21
|
|
|
22
|
+
Retain a new named capability without exposing a tool
|
|
23
|
+
→ use register_tool expose=false with from or template
|
|
24
|
+
|
|
22
25
|
Register a trusted command directly
|
|
23
26
|
→ use register_tool template="..."
|
|
24
27
|
|
|
@@ -54,6 +57,12 @@ Run Control ≠ actor chat
|
|
|
54
57
|
|
|
55
58
|
A Skill Recipe is a maintained component addressed by `<skill>/<recipe>`. `spawn` creates a Run from a Recipe. `register_tool from=<skill>/<recipe>` persists compact logical delegation and activates from the resolved effective contract without copying, symlinking, or ambient re-resolution. A tool is callable in the current session only when activation evidence says `callable_now: true`.
|
|
56
59
|
|
|
60
|
+
Sync/async is an execution contract, not a usefulness or tool-exposure rule. A sync tool awaits its result; an async tool returns a Run launch receipt, with the eventual result delivered separately. A subagent-backed tool is a capability launcher, not a persisted live subagent. Register a stable, meaningfully parameterized capability when its name and contract save repeated planning; prefer one-off `spawn` over a redundant generic launcher or a new tool for every mission.
|
|
61
|
+
|
|
62
|
+
Use `register_tool expose=false` with a new name and one `template` or `from` source to retain a capability without requesting tool activation. Its `kind: retained_recipe`, `activation: not_requested` receipt is deliberate retention, not failed registration; it omits global callability flags because a same-named tool may already exist. Reuse the exact returned Recipe path through `spawn`, or deliberately expose it later with ordinary `register_tool from=<exact path>`. Retained update, delete and draft promotion are unsupported; do not hand-edit the live registry to bypass those guards.
|
|
63
|
+
|
|
64
|
+
Automatic retained lifecycle is not implemented. Current user-root tool discovery and automatic review actions are unchanged. `inspect target=recipes view=summary` distinguishes retained definitions under `recipes/library` from root tool candidates; verbose detail gives exact paths, resolved mode and invalid/disabled diagnostics. Storage is not proof of review, callability or a running Actor. `expose` is registration input, not a Recipe field or automatic-review action; do not invent retain/defer review commands. See [persistent tools](./references/persistent-tools.md) for current selection and activation proof.
|
|
65
|
+
|
|
57
66
|
`actors` owns generic Recipe/tool/Run mechanics. The owning capability Skill owns capability-specific selection and constraints. `swarm` owns multi-actor decomposition and integration methodology.
|
|
58
67
|
|
|
59
68
|
## Presentation Direction and Current Output
|
|
@@ -64,7 +73,7 @@ Rollout is partial: compact launch/attachment receipts use block YAML; other ope
|
|
|
64
73
|
|
|
65
74
|
Compact launches use bare block-YAML mappings for the Run/state/launch facts and nested observed locations, explicitly identify actual existing-Run reuse, and preserve multiline scalar contents. One leading response-separator LF supplies invocation spacing; it is outside the pure metadata emitter and is not a `---` wrapper. Model/thinking provenance groups are labeled model and thinking, without a redundant _policy suffix; structured data and saved text stay unchanged. A complete direct file-location row needs no repeated session-map hint; absent/unavailable/omitted rows retain one optional next node naming inspect, target=runtime, view=runs and verbose=true. It never executes automatically. Exact locations, verbose details and saved completion formats stay unchanged.
|
|
66
75
|
|
|
67
|
-
Fresh automatic completions have one AX capture path with no format selector: YAML frontmatter begins
|
|
76
|
+
Fresh automatic completions have one AX capture path with no format selector: YAML frontmatter begins run, state, then kind:primary for admitted primary/final Actor response or kind:diagnostic for explicit diagnostics. Markdown follows unindented outside the closed metadata block, without #N heading, result:/preview: wrapper or whole-body literal fence. Prepend index only when two or more results are shown; exactly one shown result omits it even if other results were omitted. Omission/inventory facts remain explicit. Display index is not member_index or generation; default indices are packet-local. This new-capture rule neither restyles saved messages nor changes numbered-stream presentation. Whole-result/reference omissions and non-done omitted states stay explicit. Known display loss appears at the actual cut as [N characters omitted], counting Unicode scalar characters of admitted retained text/field, never raw/private loss; no extra clipping ellipsis or body clipping flags/counts in YAML. Unknown upstream completeness/privacy remains separately visible at the body boundary, not a fabricated count or a known positive loss. Untracked fallback has no kind and cannot become primary. Full text fitting its share has no display-cut marker; upstream uncertainty still prevents a completeness claim. New packets share48KiB UTF-8 including references/framing across at most64candidate rows, with frozen equal shares and no unused-share redistribution or fixed16KiB result/2KiB preview cap. Captured members remain bounded at256. Text is admitted work output/final response or explicit error/failure diagnostics, never launch commands. Successful composition/retry/Pi text-print can retain text_basis:actor_response; scope and exact references remain technical, while original coverage facts remain structured retained data. The model receives one complete content with whole source/generation/location references or truthful omission/inventory, not a standing full-map instruction. The TUI renders compiler headers/markers as Text and each body as independent native Markdown; only whole validated header reference/location rows and framing disappear in compact view. Body-authored YAML, next/tool/batch and omission lookalikes remain content. Content-bound hashes/ranges/blocks are display consistency, not authentication, source/read/action or acceptance authority. Invalid/missing hints and expansion reveal full original text, without Run-source IO. Old saved codecs, budgets, labels, excerpts, counts and hints replay exactly with no refresh, repair, ACK/resend or restyling—including earlier #N packets and the first frontmatter encoding. Fresh unavailable indices use quoted YAML "?"; an old saved unquoted value is not silently corrected. Routine successful delivery adds no separate completions/delivery-accepted toast; actual failures and handled-generation fences remain. These source/package rules do not prove that an already loaded session was reloaded. Fresh explicit agent Trace previews append boundary … and [omitted: N chars] from normalized retained turn, with a separate upstream [omitted: unknown]. N counts only display loss after existing session privacy, not discarded original/source text. Short well-formed summaries stay unchanged, including authored older-looking markers; malformed retained text yields Agent turn [omitted: unknown] (malformed retained text), never a guessed count or repaired scalar. This is read-time display only, not a rewrite/version of saved packets or sources. Other legacy clipping is not yet uniformly covered. Follow retained references selectively without inventing completeness or permission. Agent Trace ids reference recorded entry provenance, not aggregate traversal order or chronological event numbers. Native entry keys survive sibling growth; bounded legacy/ambiguous/private-metadata fallbacks are not permanent identities, source immutability or new read/work permission.
|
|
68
77
|
|
|
69
78
|
Current completion index fields are packet-local, not persistent historical indices; earlier saved #N headings keep their original meaning. Session completion streams are adopted work in progress, not an available action; do not invent a start-stream command, initialize a missing counter or reinterpret old headings. A kernel-only path commits numbered snapshots/counters/activation atomically. Its internal runtime bridge requires trusted fresh-session opt-in plus current manager/generation binding; the default entrypoint does not opt in and no public tool/command exposes it. Unknown state does not fall back to local numbering on that path. Startup/reload/resume, including ambiguous imports, cannot resume a stream automatically; stored numbers remain data, not a new session grant. A pending target or projected scope is not an active stream. Missing state never authorizes bootstrap, and unknown/conflicting delivery never authorizes bypass or reallocation. Saved numbers stay exact; an admitted unavailable counter or unfitting whole range freezes an unknown index (quoted "?" in fresh YAML, #? in older layouts) rather than resetting or partially numbering a packet.
|
|
70
79
|
|
|
@@ -168,7 +177,7 @@ When a pi-actors operation fails:
|
|
|
168
177
|
3. Report resolver, registry, activation, Run, Trace, or Control truth exactly.
|
|
169
178
|
4. Retry only after the owning state is healthy.
|
|
170
179
|
|
|
171
|
-
Stop if spawn and registry resolve the same Recipe differently.
|
|
180
|
+
Stop if spawn and registry resolve the same Recipe differently. When tool exposure was requested, stop if registration persists but is not callable. A `retained_recipe` receipt with `activation: not_requested` instead permits ordinary Recipe reuse, not a claim of tool invocation. Stop if an operation cannot be proven through pi-actors surfaces.
|
|
172
181
|
|
|
173
182
|
Never recover by copying maintained Recipe args, defaults, Control, artifacts, or helper commands. Never hard-code a `{skill_dir}` replacement path. Never introduce `bash -lc`, `eval`, direct bundled-helper execution, or shell backgrounding to bypass resolution. Never call `spawn` and claim a tool call. Use [diagnostics](./references/diagnostics.md) for the safe next action.
|
|
174
183
|
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Use a persistent tool when the agent should call a trusted capability by name across turns or sessions. Use `spawn` instead for one-off Recipe execution.
|
|
4
4
|
|
|
5
|
+
## Select the capability, not the execution mode
|
|
6
|
+
|
|
7
|
+
Both sync and async Recipes can be tools. Sync invocation awaits the result; async invocation returns a launch receipt and the eventual result arrives separately. Neither synchrony nor a model-backed implementation establishes usefulness. A subagent-backed tool launches a capability, not a saved live subagent. Synchronous tools execute inline; non-singleton async calls launch Run executions, while singleton attachment follows its existing compatibility contract.
|
|
8
|
+
|
|
9
|
+
Prefer a stable purpose/name, caller-owned parameterized inputs, a clear result/acceptance boundary, understood effects and failure/cancellation limits, and meaningful leverage over existing tools or one-off `spawn`. Do not register every transient mission or a generic arbitrary-prompt alias merely to add a name. A successful registration is not permission for new effects, and a launch receipt is not successful task completion.
|
|
10
|
+
|
|
11
|
+
Current user-root Recipes are tool candidates by location; Skill components do not become tools automatically. Explicit create-only retention is supported through `register_tool expose=false`; it is separate from tool exposure. Automatic retained lifecycle is not implemented. Read-only `inspect target=recipes view=summary` reports retained counts for `recipes/library`; use `verbose=true` for exact `retained` paths, resolved mode, invalid/disabled state and diagnostics. Reuse a valid enabled entry through `spawn recipe=<exact path>` with required caller values, or deliberately register it with `from=<exact path>`. A stored definition is not review, activation or Run evidence; use `tool:<name>` for activation and `runtime view=runs` for running instances. A `library/<stem>` usage-lineage label is private accounting identity, not a callable name or Skill Recipe reference; reuse the exact file path from inspection. Do not populate or migrate the live registry manually. `expose` is a registration input, not a Recipe field; do not invent review actions or bypass unsupported retained mutations. Existing explicit `register_tool` works for both modes; automatic review remains under its existing policy. Launch counts and value-free structural review alone do not establish semantic purpose, a good name, or accepted useful reuse.
|
|
12
|
+
|
|
5
13
|
## Choose one source mode
|
|
6
14
|
|
|
7
15
|
```text
|
|
@@ -32,9 +40,19 @@ Resolution uses Pi's authoritative active-session Skill snapshot across every Sk
|
|
|
32
40
|
|
|
33
41
|
Use `description` to narrow agent-facing intent when useful. Every supplied default must name a caller-owned source arg and satisfy its type or enum. Never default runtime-owned origins.
|
|
34
42
|
|
|
43
|
+
## Retain without exposing a tool
|
|
44
|
+
|
|
45
|
+
Use `register_tool name=<name> expose=false from=<skill>/<recipe>` or a trusted `template` source to create `recipes/library/<name>.json`. A description is still required for authored command templates; source defaults/args/origins still belong to the maintained Recipe. This is create-only: retained `update=true`, null/empty deletion, `draft` mode and direct Recipe dependencies on managed disposable drafts are rejected, including Recipe-valued template leaves. A duplicate retained JSON/Markdown stem is a conflict; no older file is overwritten.
|
|
46
|
+
|
|
47
|
+
The `kind: retained_recipe`, `activation: not_requested` receipt confirms persistence/admission, not host activation or review. It returns the exact Recipe path and required/optional argument names, without global callability flags or default values. A same-named tool is left untouched. Use the exact returned path with `spawn`; if compact output omits the whole path, follow its read-only verbose Recipe inspection hint rather than guessing it. Retention records no launch and makes no self-contained-backup promise for referenced sources.
|
|
48
|
+
|
|
49
|
+
To expose a retained capability deliberately, call ordinary `register_tool name=<tool> from=<exact retained path>`, prove activation below, then call the actual tool. Deleting that admitted wrapper through the ordinary deletion path leaves the retained source; it does not stop active Runs. This is not a migration or safe-retention rule for old standalone tools.
|
|
50
|
+
|
|
51
|
+
Publication uses a complete atomic no-overwrite file link; unsupported filesystems refuse without an overwrite fallback. A storage, admission or lock-release failure can follow committed bytes. Inspect the retained inventory and preserve the original cause; do not blindly repeat the request or delete an uncertain file.
|
|
52
|
+
|
|
35
53
|
## Prove registration
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
When tool exposure was requested, read registration as a state transition, not one success word:
|
|
38
56
|
|
|
39
57
|
```text
|
|
40
58
|
resolved
|
|
@@ -46,7 +64,7 @@ resolved
|
|
|
46
64
|
→ callable_now
|
|
47
65
|
```
|
|
48
66
|
|
|
49
|
-
|
|
67
|
+
For tool exposure, when `callable_now` is true, call the actual generated tool and verify `launch_kind: "tool"` when provenance matters. When false, stop at the reported activation boundary and use tool/Recipe diagnosis. A Recipe spawn is not an activation test or tool invocation substitute.
|
|
50
68
|
|
|
51
69
|
Use:
|
|
52
70
|
|
|
@@ -24,7 +24,7 @@ inspect target=run:<id> view=trace
|
|
|
24
24
|
inspect target=run:<id> view=control
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Fresh root completions use
|
|
27
|
+
Fresh root completions use YAML frontmatter (run, state, admitted kind), followed by raw left-edge Markdown. Prepend the packet-local index only when two or more results are shown. Exactly one shown result omits its index even with omitted results; preserve omission/inventory metadata, internal member/generation/correlation and every saved message. This rule does not apply to numbered streams. New source captures put all runtime metadata above that body, with no service/file footer. files.groups gives one literal root plus relative entry paths; files.read_first names zero-based group/entry indices for the first useful source, not an automatic read. role/purpose guidance is English; Actor Markdown and path/name bytes are unchanged. External paths use separate roots; unknown bases remain explicit rather than guessed from cwd or Run id. Known cut counts alone may follow the cut body; unknown-loss warnings and packet omission/inventory facts stay in the header. Retained captures preserve their old bytes and presentation. No external #N heading, result:/preview: wrapper, list nesting, whole-body fence, Batch caption, members wrapper or routine outer counter is added. Primary/final Actor response uses kind:primary; explicit error/failure diagnostic text uses kind:diagnostic, never a launch command. Untracked fallback has no kind and keeps unknown original completeness/privacy explicit outside YAML. Known cut loss is [N characters omitted] at the cut, with no extra ellipsis or body clipping counters/flags in metadata. Unknown upstream loss remains separately unknown and does not assert a known positive omission. Exact source/generation/parent/location references remain in complete model content, with whole references or explicit omission, captured inventory recovery and non-done omitted states. The compact TUI independently renders literal headers/markers and native Markdown bodies from validated content-bound blocks; only whole header reference/location rows and framing are hidden. Expansion and invalid/missing hints reveal original text. Hashes/layout/markers establish consistency/correlation only, never authentication, read/action permission, acceptance or native truth. Authored YAML/next/tool/batch/omission lookalikes stay body content. Fresh packets share48KiB UTF-8 including service data, equally between up to64candidate rows, with256captured members and no redistribution of unused shares or fixed16KiB/2KiB text cap. Full admitted text fitting its share without upstream uncertainty has no cut marker; otherwise use the scalar/CRLF-safe prefix and exact retained-source loss. Allocation/text is frozen at admission; rendering/recovery does not reread sources or reallocate. Unsupported Pi event/control output is withheld. Source basis stays in reference metadata; retained structured coverage facts are not exposed as YAML clipping fields. Pending/queued/presented older formats—including #N records and the first frontmatter draft—keep exact bytes, hints and budgets. Encoding names are storage replay data, not public product choices, source permissions or work authority. No standing full-map instruction or automatic research/read follows; use selective same-session investigation when needed.
|
|
28
28
|
|
|
29
29
|
Current completion index values restart within each packet; they are not session-wide historical references. Completion streams are adopted but not enabled, and no start-stream command is available yet. Do not infer an active stream or reset a counter from missing metadata or a pending target, and never relabel older saved packets. The kernel-only allocator now commits fixed scope/order/indices, the counter and first-packet target activation together, and an internal fresh-session bridge can use it after current manager/generation admission. The default runtime does not opt in; no public action exposes it. Event reason or a matching ID alone is insufficient. Old callbacks/wrappers cannot act after replacement; fresh wrappers must belong to the current original manager. Startup/reload/resume and ambiguous imports remain closed to stream capture/ACK, without changing saved packets or inferring a counter from history. Its whole-range exhaustion or admitted counter unavailability yields a frozen unknown index (quoted "?" in fresh YAML; #? in older saved layouts), never partial indices or an automatic fresh stream. Old packets retain their original scope while a pending switch waits; uncertainty does not permit reallocation. Stream state outlives packet cleanup/receipt rotation. Malformed/missing state remains refused, not a zero counter or proof of non-connection.
|
|
30
30
|
|
|
@@ -7,6 +7,12 @@ description: Use only for internal automatic Recipe-memory review, diagnosis, or
|
|
|
7
7
|
|
|
8
8
|
Use this Skill only when automatic draft-memory or active-tool review needs diagnosis or bounded recovery. Normal agents must not spawn, register, specialize, or invoke `recipe-memory/draft-review` or `recipe-memory/tool-review`; they are package-owned internal reviewer components.
|
|
9
9
|
|
|
10
|
+
## Evidence limits
|
|
11
|
+
|
|
12
|
+
Automatic reviewers receive value-free contract shape, risk labels, usage counts, and batch-local equality groups, not executable content, descriptions, paths, or canonical names. Structural evidence and launch counts alone do not establish semantic purpose, good tool names, successful reuse, or accepted outcomes. Promotion preserves unchanged captured executable content; it does not generalize a one-off mission.
|
|
13
|
+
|
|
14
|
+
Explicit create-only retention is owned by `actors` through `register_tool expose=false`, not by these internal reviewers. Automatic retained lifecycle is not implemented. Current draft promote/discard and tool-review behavior are unchanged. Missing evidence is unknown; this guidance adds no retain/defer action, retry permission, automatic rewrite, or live migration. Do not bypass confidentiality or edit review state to simulate the future policy.
|
|
15
|
+
|
|
10
16
|
## Diagnose first
|
|
11
17
|
|
|
12
18
|
```text
|
package/docs/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Living index of all documentation in the `/docs` directory.
|
|
|
18
18
|
|
|
19
19
|
## Planned Contracts
|
|
20
20
|
|
|
21
|
+
- [capability-memory.md](./capability-memory.md) — Adopted separation of retained Recipes and deliberate tool exposure, with implemented read-only library inventory and isolated usage identity; explicit create-only persistence is implemented without tool activation, while retained update/delete and automatic lifecycle/migration remain separate
|
|
21
22
|
- [windows-process-observation.md](./windows-process-observation.md) — User-approved recovery preparation: passing inert Windows x64 MI child experiment, reviewed unadmitted raw codec and artifact preparation contract; broader profile evidence, unsigned asset distribution and default activation remain separate gates
|
|
22
23
|
- [run-supervision.md](./run-supervision.md) — Accepted future supervision contract and implemented AX/evidence slices, with same-logical-session file research and candidate-bound acceptance; the next selective AX/runtime release does not enable deadlines, independent owner-loss supervision or public completion streams
|
|
23
24
|
- [inactive-foundations-inventory.md](./inactive-foundations-inventory.md) — Reachability and package evidence for packaged-but-unactivated supervision, stream-numbering and native foundations, distinguishing reviewed runtime dependencies from optional ones; no removal decision, nothing activated
|