@aeondave/pi-skill-optimizer 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BENCHMARK.md CHANGED
@@ -62,7 +62,7 @@ The benchmark asks:
62
62
  2. Is the stable base byte-identical across unrelated human requests?
63
63
  3. Does bounded prefetch improve first-turn recall without changing that base?
64
64
  4. Can `skill_search` recover relevant tail skills through bounded pagination?
65
- 5. Can `tool_search` defer schemas without making required tools unavailable?
65
+ 5. Can `optimizer_tool_search` defer schemas without making required tools unavailable?
66
66
  6. Do output and history reductions preserve protected evidence and exact recovery?
67
67
  7. Is every accepted serialized transformation smaller?
68
68
  8. What do providers report for tokens, cache reads, cache writes, and cost?
@@ -174,7 +174,7 @@ Blocking invariants:
174
174
 
175
175
  - core, configured, and already used tools remain active;
176
176
  - inactive schemas are absent from the initial provider request;
177
- - `tool_search` remains active while searchable definitions remain;
177
+ - `optimizer_tool_search` remains active while searchable definitions remain;
178
178
  - activation makes selected tools callable in the same session;
179
179
  - activation is additive and cannot strand an active provider tool loop;
180
180
  - schema indexing work and response pagination remain bounded;
@@ -188,7 +188,7 @@ Reported metrics:
188
188
  tool_recall
189
189
  initial_tool_schema_bytes
190
190
  activated_tool_schema_bytes
191
- tool_search_calls
191
+ optimizer_tool_search_calls
192
192
  activation_failures
193
193
  ```
194
194
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes are documented here.
4
4
 
5
+ ## 3.1.0 - 2026-10-03
6
+
7
+ ### Breaking
8
+
9
+ - Rename the optimizer's model-callable `tool_search` to `optimizer_tool_search`. Explicit tool calls must use the new name. Pi's built-in `tool_search` now loads alongside the optimizer without a replacement warning.
10
+
11
+ ### Fixed
12
+
13
+ - Preserve an active built-in `tool_search` during optimizer replanning.
14
+ - Retain tools activated later by Pi or another extension, and honor `alwaysTools` for initially inactive `deferred` or `codemode` tools while respecting hidden and inactive direct tools.
15
+ - Capture an explicitly requested benchmark catalog from Pi 1.0's `before_agent_start` skill options.
16
+
17
+ ### Changed
18
+
19
+ - Require Pi 1.0.0 or newer for this release.
20
+ - Run typechecking and host integration tests against Pi 1.0.0 development packages.
21
+ - Add a reproducible Pi CLI black-box smoke test for the packed artifact.
22
+
5
23
  ## 3.0.1 - 2026-09-30
6
24
 
7
25
  ### Fixed
package/README.md CHANGED
@@ -14,7 +14,7 @@ No embedding service or extra model call for ordinary routing. No changes to you
14
14
 
15
15
  ## Install
16
16
 
17
- Requires **Pi 0.87.1+** and **Node.js 26.3+**. Upgrade both before installing v3.
17
+ Requires **Pi 1.0.0+** and **Node.js 26.3+**. Upgrade both before installing v3.1.
18
18
 
19
19
  ```bash
20
20
  pi install npm:@aeondave/pi-skill-optimizer
@@ -26,19 +26,18 @@ Restart Pi, then check that it loaded:
26
26
  /skill-optimizer
27
27
  ```
28
28
 
29
- Pi 0.99.0+ also includes a built-in `tool-search` extension. This optimizer provides
30
- its own `tool_search`, so disable the built-in under **Built-in extensions** in
31
- `pi config` to avoid the startup conflict warning. Use the global setting for a
32
- personal install, or `pi config -l` for a project-only install. The optimizer
33
- continues to find registered `deferred` and `codemode` tools. See the
34
- [tool-search configuration](docs/CONFIGURATION.md#replacing-the-built-in-tool-search).
29
+ On Pi 1.0.0, the optimizer's search tool is `optimizer_tool_search`. Pi's
30
+ built-in `tool_search` can load alongside it without a name conflict. If you
31
+ previously disabled `builtin:tool-search` to avoid the warning, you can remove
32
+ that setting after updating the optimizer. See the
33
+ [tool-search configuration](docs/CONFIGURATION.md#tool-search-and-pi-100).
35
34
 
36
35
  That's enough to start. Optionally run `/skill-optimizer init` to generate routing hints for your library. **Init uses your current Pi model and may incur cost**; normal discovery does not.
37
36
 
38
37
  | Need | Command |
39
38
  | --- | --- |
40
39
  | Install only in this project | `pi install -l npm:@aeondave/pi-skill-optimizer` |
41
- | Pin the current release | `pi install npm:@aeondave/pi-skill-optimizer@3.0.1` |
40
+ | Pin the current release | `pi install npm:@aeondave/pi-skill-optimizer@3.1.0` |
42
41
  | Update an unpinned install | `pi update npm:@aeondave/pi-skill-optimizer` |
43
42
 
44
43
  Restart after updating. Pinned installs stay pinned until you explicitly install another version.
@@ -65,7 +64,7 @@ AUTO keeps a compact, stable index of **all eligible names**, adds relevant desc
65
64
  | Area | What AUTO does | What stays available |
66
65
  | --- | --- | --- |
67
66
  | Skills | Shortens catalog descriptions; prefetches relevant ones | Every eligible name and exact full-instruction loading |
68
- | Tools | Defers less relevant schemas behind `tool_search` | Protected, configured, already-used and activated tools |
67
+ | Tools | Defers less relevant schemas behind `optimizer_tool_search` | Protected, configured, already-used and activated tools |
69
68
  | Large results | Applies deterministic reduction; lossless columnar JSON where suitable | Complete originals through `retrieve_output` during archive retention |
70
69
  | Repeated results | Replaces later exact duplicates with recovery references | The first result, errors, protected evidence and mixed media |
71
70
 
@@ -83,7 +82,7 @@ You can leave AUTO running with its defaults.
83
82
 
84
83
  An optional setup hint appears at the first eligible prompt when initialization is missing or needs attention. It never starts init for you or adds instructions to the model's context.
85
84
 
86
- Pi gets three tools: `skill_search`, `tool_search` and `retrieve_output`. Skill search is paginated and exact loads accept only registered names. Output recovery uses opaque identifiers, not exposed archive paths.
85
+ Pi gets three optimizer tools: `skill_search`, `optimizer_tool_search` and `retrieve_output`. Pi may also provide its built-in `tool_search`. Skill search is paginated and exact loads accept only registered names. Output recovery uses opaque identifiers, not exposed archive paths.
87
86
 
88
87
  Skills can be useful even when you never mention them. Init can tag architecture,
89
88
  testing and behavioral guidance by activity, so a matched specialist can bring
@@ -30,9 +30,9 @@ expand dependencies or treat a behavioral tag as universal. See
30
30
 
31
31
  ## Dynamic tools
32
32
 
33
- AUTO keeps protected and predicted tools active and leaves the rest behind `tool_search`. Search is bounded and paginated. A returned result is activated through Pi's runtime API without replacing already active tools. Core, configured, and previously used tools remain callable, so an in-progress tool loop cannot be stranded. Weak or absent routing signal fails open to every permitted tool; safety wins, but that request may retain the full schema cost.
33
+ AUTO keeps protected and predicted tools active and leaves the rest behind `optimizer_tool_search`. Search is bounded and paginated. A returned result is activated through Pi's runtime API without replacing already active tools. Core, configured, and previously used tools remain callable, so an in-progress tool loop cannot be stranded. Pi's built-in `tool_search` remains active when the host enables it. Weak or absent routing signal fails open to every permitted tool; safety wins, but that request may retain the full schema cost.
34
34
 
35
- Selection is recalculated before each agent run, not just the first request. Used tools and explicit search results stay active for the session. The discoverable set starts from Pi's initially active tools plus the optimizer's own resolvers; tools that were initially unavailable are not silently added. This is schema routing, not authorization: another extension's call-time permission checks remain authoritative.
35
+ Selection is recalculated before each agent run, not just the first request. Used tools and explicit search results stay active for the session. The discoverable set starts from Pi's initially active tools plus the optimizer's own resolvers. Newly activated host tools are retained; inactive `deferred` and `codemode` tools can be selected through search or `alwaysTools`. Inactive direct and hidden tools are not silently added. This is schema routing, not authorization: another extension's call-time permission checks remain authoritative.
36
36
 
37
37
  See Pi's [extension and dynamic-tool API](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md).
38
38
 
@@ -7,18 +7,18 @@
7
7
  | Tool | Purpose |
8
8
  | --- | --- |
9
9
  | `skill_search` | Paginated skill search plus exact `load`, contained `resource`, and explicit `location` actions |
10
- | `tool_search` | Search deferred tool definitions and activate bounded matches additively |
10
+ | `optimizer_tool_search` | Search deferred tool definitions and activate bounded matches additively |
11
11
  | `retrieve_output` | Recover archived output or a deduplicated history result by opaque identifier |
12
12
 
13
13
  Exact skill loading accepts only registered names. Resource and location resolution use canonical paths and containment checks to reject traversal and symlink escape. Search cursors bind to the query, catalog and ranked name order; a changed profile or usage prior that reorders results requires a new search, rather than duplicating or skipping a page. This is an exact local resolver guarantee, not a guarantee that the model will select the right skill; see the separate [ranking diagnostics](RANKING.md) and [historical model benchmark](../BENCHMARK.md).
14
14
 
15
15
  `excludeSkills` removes names from discovery and search, not from explicit exact loading. It is a routing preference, not an access-control boundary. The three resolver tools return their content without another output-reduction pass, so full skill instructions and recovered originals remain readable.
16
16
 
17
- ### Replacing the built-in tool search
17
+ ### Tool search and Pi 1.0.0
18
18
 
19
- The extension registers `tool_search` itself, so it replaces Pi's built-in `builtin:tool-search` extension (the built-in is marked replaceable and the tool name decides). While both are in the load set, Pi prints a startup warning that the built-in was not loaded. The optimizer indexes eligible registered tools, including ones it deferred from Pi's active set and Pi-registered `deferred` or `codemode` tools. Matching inactive results activate additively; unrelated fallback results remain inactive. Pi's built-in MCP extension can still activate `tool_search` by name when a deferred server connects.
19
+ The extension registers `optimizer_tool_search`, leaving Pi's built-in `tool_search` available under its own name. The optimizer indexes eligible registered tools, including ones it deferred from Pi's active set and Pi-registered `deferred` or `codemode` tools. Matching inactive results activate additively; unrelated fallback results remain inactive. An already active built-in `tool_search` stays active during optimizer replanning. Pi's built-in MCP extension can activate its own `tool_search` when a deferred server connects.
20
20
 
21
- To silence the warning, disable the built-in where the optimizer runs: toggle `tool-search` off under Built-in extensions in `pi config`, or set `"extensions": ["-builtin:tool-search"]` in the applicable `settings.json`. A personal install needs the global setting in `~/.pi/agent/settings.json`; a project-only install can use `.pi/settings.json`. For development with `pi -e ./src/index.ts`, set the toggle locally or globally. Re-enable the built-in if you remove the optimizer.
21
+ If an older optimizer release led you to add `-builtin:tool-search` in `~/.pi/agent/settings.json` or `.pi/settings.json`, remove that entry after updating if you want Pi's built-in search available again. The built-in is inactive by default unless enabled in Pi's `defaultTools` or activated by Pi for deferred MCP tools; the optimizer's search stays available independently. Existing explicit calls to the optimizer's old `tool_search` name must use `optimizer_tool_search` after this update.
22
22
 
23
23
  ## Commands
24
24
 
@@ -55,7 +55,7 @@ Global configuration lives at `~/.pi/agent/skill-optimizer/config.json`. A proje
55
55
  | `providers` | `[]` | Provider allowlist; empty means all |
56
56
  | `toolPrefetchMax` | `5` | Maximum proactive tool activation |
57
57
  | `toolSearchPageSize` | `8` | Default tool-search page size |
58
- | `alwaysTools` | `[]` | Tools that remain active |
58
+ | `alwaysTools` | `[]` | Keep initially active tools and named `deferred` or `codemode` tools active; hidden tools remain unavailable |
59
59
  | `historyDedupMinBytes` | `4096` | Minimum result size for history deduplication |
60
60
  | `usageHalfLifeDays` | `30` | Usage-evidence decay half-life |
61
61
  | `usageMaxEntries` | `2048` | Persistent usage-evidence bound |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeondave/pi-skill-optimizer",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Less repeated context for Pi: local skill routing, on-demand tools, and recoverable output reduction.",
@@ -43,12 +43,13 @@
43
43
  ],
44
44
  "scripts": {
45
45
  "typecheck": "tsc --noEmit",
46
- "test": "node --import tsx --test test/request.test.ts test/history.test.ts test/skills.test.ts test/skill-loader.test.ts test/tools.test.ts test/optimize.test.ts test/profile.test.ts test/activation.test.ts test/generate.test.ts test/init.test.ts test/init-readiness.test.ts test/config.test.ts test/fuzz.test.ts test/output.test.ts test/output-recovery.test.ts test/runtime.test.ts test/host-runtime.test.ts test/stats.test.ts test/usage.test.ts test/persistence.test.ts test/corpus.test.ts test/evaluation.test.ts test/luna.test.ts test/release-metadata.test.ts",
46
+ "test": "node --import tsx --test test/request.test.ts test/history.test.ts test/skills.test.ts test/skill-loader.test.ts test/tools.test.ts test/optimize.test.ts test/profile.test.ts test/activation.test.ts test/generate.test.ts test/init.test.ts test/init-readiness.test.ts test/config.test.ts test/fuzz.test.ts test/output.test.ts test/output-recovery.test.ts test/runtime.test.ts test/host-runtime.test.ts test/stats.test.ts test/usage.test.ts test/persistence.test.ts test/corpus.test.ts test/capture-catalog.test.ts test/evaluation.test.ts test/luna.test.ts test/release-metadata.test.ts",
47
47
  "measure": "node --import tsx scripts/measure.ts",
48
48
  "bench": "node --import tsx scripts/bench.ts",
49
49
  "bench:perf": "node --import tsx scripts/perf-bench.ts",
50
50
  "bench:ranking": "node --import tsx scripts/ranking-bench.ts",
51
51
  "bench:output": "node --import tsx scripts/output-bench.ts",
52
+ "smoke:blackbox": "node --import tsx scripts/black-box-smoke.ts",
52
53
  "corpus:build": "node --import tsx scripts/build-real-corpus.ts",
53
54
  "bench:real": "node --import tsx scripts/evaluate-real-corpus.ts"
54
55
  },
@@ -69,8 +70,8 @@
69
70
  }
70
71
  },
71
72
  "devDependencies": {
72
- "@earendil-works/pi-ai": "^0.87.1",
73
- "@earendil-works/pi-coding-agent": "^0.87.1",
73
+ "@earendil-works/pi-ai": "^1.0.0",
74
+ "@earendil-works/pi-coding-agent": "^1.0.0",
74
75
  "@types/node": "^26.6.3",
75
76
  "tsx": "^4.23.13",
76
77
  "typebox": "^1.3.34",
@@ -80,6 +81,6 @@
80
81
  "extensions": [
81
82
  "./src/index.ts"
82
83
  ],
83
- "image": "https://raw.githubusercontent.com/AeonDave/pi-skill-optimizer/v3.0.1/assets/banner.png"
84
+ "image": "https://raw.githubusercontent.com/AeonDave/pi-skill-optimizer/v3.1.0/assets/banner.png"
84
85
  }
85
86
  }
package/src/index.ts CHANGED
@@ -110,7 +110,8 @@ const FLUSH_INTERVAL_MS = 15_000;
110
110
  const LOAD_RETRY_MS = 1_000;
111
111
  const MAX_SEEN_EVIDENCE = 4_096;
112
112
  const MAX_HISTORY_ARTIFACTS = 128;
113
- const PROTECTED_RUNTIME_TOOLS = ["skill_search", "tool_search", "retrieve_output"] as const;
113
+ const PROTECTED_RUNTIME_TOOLS = ["skill_search", "optimizer_tool_search", "retrieve_output"] as const;
114
+ const RESOLVER_RESULT_TOOLS = new Set<string>([...PROTECTED_RUNTIME_TOOLS, "tool_search"]);
114
115
 
115
116
  function isHostSearchableTool(tool: object): boolean {
116
117
  return "exposure" in tool && (tool.exposure === "deferred" || tool.exposure === "codemode");
@@ -574,16 +575,30 @@ export default function skillOptimizer(pi: ExtensionAPI) {
574
575
 
575
576
  const rebuildToolCatalog = (state: RuntimeSnapshot): ToolDiscoveryCatalog => {
576
577
  const allTools = pi.getAllTools();
578
+ const activeNames = new Set(pi.getActiveTools().map((name) => name.toLowerCase()));
579
+ const alwaysToolPrefixes = state.config.alwaysTools
580
+ .map((pattern) => pattern.trim().toLowerCase())
581
+ .filter((pattern) => pattern.length > 0);
577
582
  permittedToolNames ??= new Set([
578
- ...pi.getActiveTools().map((name) => name.toLowerCase()),
583
+ ...activeNames,
579
584
  ...PROTECTED_RUNTIME_TOOLS,
580
585
  ]);
581
586
  for (const tool of allTools) {
582
- if (!isHostHiddenTool(tool)) continue;
583
587
  const name = tool.name.toLowerCase();
584
- permittedToolNames.delete(name);
585
- retainedToolNames.delete(name);
586
- deactivatedToolNames.delete(name);
588
+ if (isHostHiddenTool(tool)) {
589
+ permittedToolNames.delete(name);
590
+ retainedToolNames.delete(name);
591
+ deactivatedToolNames.delete(name);
592
+ continue;
593
+ }
594
+ if (activeNames.has(name)) {
595
+ const newlyActive = !permittedToolNames.has(name);
596
+ const reactivated = deactivatedToolNames.delete(name);
597
+ if (newlyActive || reactivated) retainedToolNames.add(name);
598
+ permittedToolNames.add(name);
599
+ } else if (isHostSearchableTool(tool) && alwaysToolPrefixes.some((pattern) => name.startsWith(pattern))) {
600
+ permittedToolNames.add(name);
601
+ }
587
602
  }
588
603
  const discoverable = allTools.filter((tool) =>
589
604
  !isHostHiddenTool(tool) && permittedToolNames?.has(tool.name.toLowerCase()),
@@ -684,8 +699,8 @@ export default function skillOptimizer(pi: ExtensionAPI) {
684
699
  });
685
700
 
686
701
  pi.registerTool({
687
- name: "tool_search",
688
- label: "Tool Search",
702
+ name: "optimizer_tool_search",
703
+ label: "Optimizer Tool Search",
689
704
  description: "Search local tool definitions and activate matching inactive tools without disabling current tools.",
690
705
  promptSnippet: "Search and activate additional tools on demand",
691
706
  parameters: TOOL_SEARCH_PARAMS,
@@ -1177,7 +1192,7 @@ export default function skillOptimizer(pi: ExtensionAPI) {
1177
1192
 
1178
1193
  pi.on("tool_result", async (event, ctx) => {
1179
1194
  usedTools.add(event.toolName.toLowerCase());
1180
- if (PROTECTED_RUNTIME_TOOLS.includes(event.toolName as typeof PROTECTED_RUNTIME_TOOLS[number])) return;
1195
+ if (RESOLVER_RESULT_TOOLS.has(event.toolName)) return;
1181
1196
  if (isDisabled(ctx.cwd) || event.isError) return;
1182
1197
  const config = getRuntimeConfig(ctx);
1183
1198
  const providers = config.providers.length > 0 ? config.providers : undefined;
@@ -1373,7 +1388,7 @@ export default function skillOptimizer(pi: ExtensionAPI) {
1373
1388
  ` enabled: ${isDisabled(ctx.cwd) ? "no" : "yes"}`,
1374
1389
  " skills mode: AUTO (stable TSV + skill_search resolver)",
1375
1390
  ` catalog budget:${lastBudget.usedChars}/${lastBudget.requestedChars} chars, ${lastBudget.nameOnlyCount} name-only`,
1376
- ` tools: ${pi.getActiveTools().length}/${pi.getAllTools().length} active; tool_search additive`,
1391
+ ` tools: ${pi.getActiveTools().length}/${pi.getAllTools().length} active; optimizer_tool_search additive`,
1377
1392
  ` output: ${getOutputConfig(state.config).mode}; RTK ${rtkExtensionPresent() ? "detected per result" : "not detected"}`,
1378
1393
  ` scope: ${state.config.providers.join(", ") || "all providers"}`,
1379
1394
  ` init readiness: ${retryAfter !== 0 ? "unavailable (profile could not be read)" : readiness.skillCount === 0 ? "not available (no model-invocable skills)" : initReadinessSummary(readiness)}`,
package/src/tools.ts CHANGED
@@ -23,7 +23,7 @@ export const CORE_TOOLS = new Set(
23
23
  "read", "bash", "edit", "write", "multiedit", "str_replace_editor", "notebookedit",
24
24
  "glob", "grep", "ls", "find", "todo", "todowrite", "todoread",
25
25
  "subagent", "fork", "task", "mcp", "skill", "exitplanmode", "enterplanmode",
26
- "webfetch", "websearch", "askuserquestion", "powershell",
26
+ "webfetch", "websearch", "askuserquestion", "powershell", "tool_search",
27
27
  ].map((name) => name.toLowerCase()),
28
28
  );
29
29