feat: add core tool search#79823
Conversation
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path. Real behavior proof Next step before merge Review detailsBest possible solution: Retry the Codex review after fixing the execution failure. Do we have a high-confidence way to reproduce the issue? Unclear. The review failed before ClawSweeper could establish a reproduction path. Is this the best way to solve the issue? Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 421cdd4737dc. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0911bd524
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| search: runtime.search, | ||
| describe: runtime.describe, | ||
| call: runtime.call, |
There was a problem hiding this comment.
Prevent VM sandbox escape via host function injection
The sandbox exposes host-created function objects (runtime.search/describe/call) directly to untrusted VM code, which allows escape via Function constructor access (for example through openclaw.tools.search.constructor(...)) and then access to host globals like process. In practice this breaks the claimed isolation and can allow arbitrary host code execution when model-generated code is influenced by untrusted input.
Useful? React with 👍 / 👎.
| toolCallId: string, | ||
| input: unknown, | ||
| ) => Promise<AgentToolResult<unknown>>; | ||
| const result = await execute(`tool_search_code:${entry.name}`, input ?? {}); |
There was a problem hiding this comment.
Use unique tool call IDs for bridged tool invocations
This forwards a deterministic toolCallId (tool_search_code:${entry.name}) for every call to the same tool, so repeated calls in one code-mode run share an ID. The embedded runner’s client-tool recorder deduplicates by toolCallId, so retries/loops against one client tool can collapse into a single recorded call and lose earlier invocations. Generate a per-invocation ID (e.g., counter/nonce suffix) before dispatching.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2ae6467b6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const execute = entry.tool.execute as ( | ||
| toolCallId: string, | ||
| input: unknown, | ||
| ) => Promise<AgentToolResult<unknown>>; | ||
| const result = await execute(`tool_search_code:${entry.name}`, input ?? {}); |
There was a problem hiding this comment.
Cancel bridged tool executions when code mode times out
The code-mode timeout only kills the child VM process, but bridged tool calls keep running in the host because runtime.call invokes entry.tool.execute(...) without an AbortSignal. If openclaw.tools.call(...) targets a long-running tool, tool_search_code returns timed out while the underlying tool may continue and still produce side effects (writes, network calls, approvals) after the turn has failed. Please thread a cancellation signal into bridged calls so timeout is fail-stop for both child code and host tool execution.
Useful? React with 👍 / 👎.
| const uniqueCatalogs = [...new Set(sessionCatalogs.values())]; | ||
| if (uniqueCatalogs.length === 1) { | ||
| const catalog = uniqueCatalogs[0]; | ||
| if (catalog) { | ||
| return catalog; |
There was a problem hiding this comment.
Fail closed when no session-scoped catalog is found
When lookup by sessionId/sessionKey/agentId misses, resolveCatalog falls back to the only global catalog in memory. Because OpenClawPluginToolContext fields are optional, a call with missing context can accidentally bind to another run’s catalog instead of failing, which can expose or execute tools outside the current session boundary when one catalog is active. This path should fail closed unless a matching session-scoped key is found.
Useful? React with 👍 / 👎.
d2ae646 to
b79d433
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b79d433a7b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const execute = entry.tool.execute as ( | ||
| toolCallId: string, | ||
| input: unknown, | ||
| ) => Promise<AgentToolResult<unknown>>; | ||
| const result = await execute(`tool_search_code:${entry.name}`, input ?? {}); | ||
| return { |
There was a problem hiding this comment.
Preserve direct-tool error semantics in bridged calls
ToolSearchRuntime.call invokes catalog tools via entry.tool.execute(...) and lets thrown errors escape, so a single tool failure aborts tool_search_code/tool_call instead of returning a structured tool error result. In the normal direct-tool path, toToolDefinitions wraps execution and converts non-abort failures into status: "error" results (src/agents/pi-tool-definition-adapter.ts:236-263). This regression means transient validation/runtime errors in any cataloged OpenClaw tool can terminate the whole code-mode run rather than giving the model recoverable tool output.
Useful? React with 👍 / 👎.
b79d433 to
bb900f9
Compare
|
This is a good idea 🚀another way to do this is micro LLM call to 5.4 mini that reads prompt > recommended tool loads and or cache and assigns classes of tools, or regex that recommends tools etc |
|
While live-testing this branch with verbose tool-call display enabled, I noticed a UX regression: every bridged call only renders as:
That hides the actual underlying operation (for example, whether the bridge is searching, describing, or calling Suggested behavior: keep the compact bridge from the model-facing prompt, but annotate runtime/verbose display with something like:
I’m going to try a local patch against the live-tested branch now. |
bb900f9 to
6b44455
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b444552ba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| includeOpenClawTools && resolveToolSearchCodeModeConfig(options?.config).enabled | ||
| ? createToolSearchCodeModeTools({ |
There was a problem hiding this comment.
Recognize tool_search control tools in allowlist planning
Only adding Tool Search Code Mode control tools when includeOpenClawTools is true makes explicit allowlists like tools.allow: ["tool_search_code"] impossible to satisfy: the tool-construction plan classifies that name as non-core, so core tools are not built and this branch never runs. In practice the runtime logs tool_search_code as unknown and can produce an empty callable tool set even with tools.toolSearchCodeMode.enabled=true, which breaks the documented rollout path for restricted allowlists.
Useful? React with 👍 / 👎.
| let effectiveTools = [...tools, ...filteredBundledTools]; | ||
| const toolSearchCodeMode = applyToolSearchCodeModeCatalog({ | ||
| tools: effectiveTools, |
There was a problem hiding this comment.
Preserve core-name conflict checks before catalog compaction
Compacting effectiveTools before client-tool conflict validation removes hidden core tools from the later coreBuiltinToolNames set, so name collisions that were previously rejected can now slip through in code mode (for example a client tool named read). That creates ambiguous name-based resolution in the catalog bridge and can route calls to the wrong implementation depending on catalog ordering, instead of failing early with the existing conflict error.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I want to request changes before this merges.
The PI-side idea makes sense. PI has a large flat tool catalog, so hiding that catalog behind search, describe, call, and code controls is a reasonable compaction strategy. The problem is that the current implementation is not isolated to PI.
Specifically: this PR adds tool_search_code, tool_search, tool_describe, and tool_call from shared createOpenClawCodingTools when tools.toolSearchCodeMode.enabled is true. The Codex app-server adapter also calls that same factory when building Codex dynamic tools, then passes the result into the Codex dynamic tool bridge. So with this config enabled, Codex harness mode can receive the OpenClaw tool-search-code controls too.
That is the part I do not want to merge.
I also checked the Codex source, and this is more than just a duplicate search tool. Codex already has native code mode. codex-rs/code-mode/src/lib.rs exposes the public exec and wait tools. codex-rs/code-mode/src/description.rs describes exec as JavaScript in a fresh V8 isolate with nested tools available on the global tools object, plus ALL_TOOLS for enabled nested tool metadata. codex-rs/core/src/tools/code_mode/mod.rs wires that through Codex's own tool router, and codex-rs/core/tests/suite/code_mode.rs has coverage for code mode calling hidden/deferred dynamic tools. The app-server docs also say deferred dynamic tools stay callable by runtime features such as code_mode and searchable through tool_search.
So the Codex harness already has the elegant version of this idea: native code mode can compose tool calls, including deferred dynamic tools, inside Codex's own runtime and tool router. We should lean into that. OpenClaw should supply product capabilities through Codex dynamic tools; Codex should own the code-mode and tool-search interaction model around those tools.
The current PR risks layering a second OpenClaw JavaScript bridge on top of Codex's native one. Worse, it is a different bridge: tool_search_code runs model-authored JavaScript in a Node subprocess with openclaw.tools.search/describe/call, while Codex code mode runs in Codex's V8 runtime with its own nested-tool host. That gives the model two ways to do the same conceptual thing, with different semantics, different names, and a wider debug/security surface.
The elegant boundary should be:
- PI gets PI-specific catalog compaction because PI lacks Codex's native code/tool-search surface.
- Codex gets OpenClaw dynamic tools for product integrations: messaging, sessions, cron, gateway, media, plugin tools, and similar capabilities.
- Codex does not get an extra OpenClaw
tool_search_codeor generictool_searchwrapper. It should use Codex's nativeexec,wait, deferred dynamic tools, and tool-search semantics.
Please make this feature PI-only. A clean version would either create these controls inside the PI embedded runner, or add an explicit PI-only option to createOpenClawCodingTools that defaults off and is never passed by the Codex app-server adapter.
Please also add a regression test for the Codex path: enabling tools.toolSearchCodeMode must not expose tool_search_code, tool_search, tool_describe, or tool_call through Codex dynamic tools.
Once that separation is clear, I am happy to look again.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f43bce801
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!process.allowedNodeEnvironmentFlags.has("--permission")) { | ||
| throw new ToolInputError("tool_search_code requires a Node runtime with --permission support."); | ||
| } | ||
| return ["--permission", "--input-type=module", "--eval", TOOL_SEARCH_CODE_MODE_CHILD_SOURCE]; |
There was a problem hiding this comment.
Block outbound network access in code-mode child
The child process is launched with only --permission, which does not sandbox networking, so model-authored code can still open sockets/fetch URLs directly from tool_search_code. In this mode, tool outputs returned by openclaw.tools.call(...) can be exfiltrated over the network without going through OpenClaw tool policy/approval surfaces, which breaks the intended isolation boundary for untrusted model code.
Useful? React with 👍 / 👎.
| if (!id || !method) { | ||
| return; | ||
| } | ||
| void runCodeModeBridgeRequest(params.runtime, method, message.args) |
There was a problem hiding this comment.
Stop processing bridge calls after result settlement
The parent continues dispatching bridge messages even after a result message has already settled the tool call, so model code can fire openclaw.tools.call(...) without awaiting it and still trigger host-side tool execution after tool_search_code has returned. This creates post-completion side effects (and potential writes/network calls) that are detached from the reported tool result and run lifecycle.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 501099f9f4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| modelAuthMode: resolveModelAuthMode(model.provider, params.config, undefined, { | ||
| workspaceDir: effectiveWorkspace, | ||
| }), | ||
| includeToolSearchControls: true, |
There was a problem hiding this comment.
Keep Tool Search controls out of compaction until cataloged
compactEmbeddedPiSessionDirectOnce now enables Tool Search control tools for the compaction session, but this code path never runs applyToolSearchCatalog (or otherwise registers a session catalog) before those tools are exposed to the model. If the compaction model emits tool_search_code/tool_search during a compaction turn, resolveCatalog in tool-search.ts fails with “catalog is unavailable,” which can abort compaction instead of recovering context. Either compact/catalog tools in this path as well, or do not include Tool Search controls here.
Useful? React with 👍 / 👎.
TurboTheTurtle
left a comment
There was a problem hiding this comment.
Current-head follow-up on fdb22b8: I would still hold merge for two correctness issues that are visible in the latest diff.
-
src/agents/tool-search.ts: afterrunCodeModeChildsettles aresult, the parent can still process laterbridgemessages from the child. A model body can fireopenclaw.tools.call(...)without awaiting it, return successfully, and still cause host-side tool execution after the reportedtool_search_coderesult. The bridge should stop accepting work once the call is settled, and this should have a regression test with a fire-and-forget bridged call. -
src/agents/pi-embedded-runner/compact.ts:compactEmbeddedPiSessionDirectOncenow passesincludeToolSearchControls: true, but that compaction path does not register a Tool Search catalog before exposing those controls. If a compaction turn emitstool_search_codeor the structured search tools, the tool fails with “catalog is unavailable” and can abort the compaction flow. Either keep these controls out of compaction, or register the same catalog used by normal embedded attempts.
I skipped older feedback that the latest branch appears to have addressed, such as the default-off shared factory behavior and unique bridged tool call ids.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb4d50e097
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const runtime = new ToolSearchRuntime(params.ctx, params.config); | ||
| const logs: string[] = []; | ||
| const value = await runCodeModeChild({ |
There was a problem hiding this comment.
Preserve unique bridged tool IDs across code-tool invocations
Fresh evidence: although toolCallId now includes a sequence suffix, runCodeMode creates a new ToolSearchRuntime for every tool_search_code execution, so the counter restarts at 1 each time. In a single embedded run, repeated tool_search_code calls against the same catalog tool can reuse IDs like tool_search_code:<name>:1, and the client-tool slot recorder deduplicates by toolCallId (src/agents/pi-embedded-runner/run/attempt.ts:1610-1614), which can collapse multiple client-tool invocations into one recorded slot.
Useful? React with 👍 / 👎.
| void runCodeModeBridgeRequest(params.runtime, method, message.args, { | ||
| signal: params.signal, | ||
| onUpdate: params.onUpdate, | ||
| }) |
There was a problem hiding this comment.
Cancel in-flight bridge calls once code mode settles
Fresh evidence for the prior post-settlement side-effect concern: the bridge object is thenable (then/catch/finally), so Promise.resolve(openclaw.tools.call(...)); return ...; still starts a host bridge call without awaiting it. When the child then sends result, this path only stops sending bridge-result messages but does not abort the already-started host execution, so tool side effects can continue after tool_search_code has already returned.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e613f9578
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const toolSearchTools = | ||
| includeOpenClawTools && | ||
| options?.includeToolSearchControls === true && | ||
| resolveToolSearchConfig(options?.config).enabled |
There was a problem hiding this comment.
Decouple Tool Search controls from OpenClaw core-tool gating
Tool Search controls are only created when includeOpenClawTools is true, so runs that intentionally allow only plugin tools (for example tools.allow: ["group:plugins"]) never get tool_search_code/fallback controls and therefore never compact the catalog even with tools.toolSearch enabled. In run/attempt, compaction depends on those controls being present, so this silently disables the feature for a common restricted-allowlist shape instead of honoring the config.
Useful? React with 👍 / 👎.
| const entry = catalog.entries.find( | ||
| (candidate) => candidate.id === needle || candidate.name === needle, | ||
| ); |
There was a problem hiding this comment.
Reject ambiguous tool-name lookups in catalog calls
Name-based resolution accepts either id or name, but findEntry returns the first matching entry when multiple catalog entries share a name (for example client and plugin tools with the same function name). Because this silently picks one implementation by sort order, tool_call/tool_search_code can invoke the wrong tool whenever the model passes a name instead of an id. The lookup should fail on ambiguous names (or require ids) to avoid misrouting execution.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d339eb0305
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| callableToolNames: buildCallableToolNamesForEmptyAllowlistCheck({ | ||
| effectiveToolNames: effectiveTools.map((tool) => tool.name), | ||
| autoAddedToolSearchControlNames, | ||
| toolSearchCatalogToolCount: toolSearch.catalogToolCount, |
There was a problem hiding this comment.
Count compacted client tools before empty-allowlist precheck
The empty explicit allowlist precheck runs before client tools are compacted into the Tool Search catalog, and it only uses toolSearch.catalogToolCount (which at this point counts non-client tools). In runs where the explicit allowlist is present and callable tools are client-only, auto-added Tool Search controls are stripped from callableToolNames and this precheck can incorrectly report “No callable tools remain,” causing prompt submission to be skipped even though client tools become callable later via addClientToolsToToolSearchCatalog.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d295efc444
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }); | ||
| const startupTrace = createGatewayStartupTrace(); | ||
| const startupConfigModulePromise = import("./server-startup-config.js"); | ||
| const reloadHandlersModulePromise = import("./server-reload-handlers.js"); |
There was a problem hiding this comment.
Guard preloaded reload-handler import against unhandled rejection
Starting import("./server-reload-handlers.js") eagerly here creates a promise that has no rejection handler until much later (await reloadHandlersModulePromise), so if that module load fails early (for example due to a module-evaluation error in this path), Node treats it as an unhandled rejection and can terminate the process before gateway startup error handling runs. Please either defer the import to the await site or attach a rejection handler immediately when creating the preloaded promise.
Useful? React with 👍 / 👎.
7a33648 to
b78604a
Compare
|
I think this PR needs one boundary-contract fix before we safely enable Tool Search for ClawBench or other external transcript consumers. I opened the Codex-side OpenClaw PR for the same contract here: #80155. That PR keeps Codex dynamic tool search from changing the verifier-facing transcript shape. The defensive ClawBench patch was only a stopgap; the durable fix belongs in OpenClaw rather than in benchmark adapters. The important contract: Tool Search should be a model-visible prompt compaction layer only. Public OpenClaw surfaces should still look as if the agent called the real tool directly. If the public transcript/session/trajectory surface mainly shows Concrete request:
{
"type": "toolCall",
"id": "target-call-id",
"name": "browser",
"input": { "...": "..." },
"bridgedBy": "tool_search_code",
"parentToolCallId": "bridge-call-id"
}paired with: {
"role": "toolResult",
"toolCallId": "target-call-id",
"toolName": "browser",
"content": []
}It is fine to retain the bridge call internally, but public transcript/session/trajectory consumers need to see the actual target tool call/result, not only the control tool.
The short version: Tool Search can change what OpenClaw sends to the model, but it should not change the external OpenClaw tool-call contract. |
|
Landed via rebase merge.
|
Summary
This PR adds Tool Search as a core OpenClaw agent/tooling capability for large tool catalogs. Instead of sending every OpenClaw, MCP, and client-provided tool schema to the model up front, OpenClaw can expose one compact bridge and let the model search, describe, and call only the tools it actually needs.
Default model-facing surface:
This is deliberately core, not a bundled plugin. The feature rewrites the effective PI tool surface before provider request construction and also needs access to client tools that arrive inside the PI harness path. Plugins can still contribute tools to the catalog; core owns cataloging, compaction, policy preservation, and PI surface shaping.
What This Adds
tool_search_codetool_searchtool_describetool_calltools.toolSearchcode: expose onlytool_search_codetools: expose only structured search/describe/call toolsSimplified Config
Public config is now intentionally small because this has not shipped yet:
That enables the default code bridge.
Structured fallback mode:
Disable it:
Removed from the public surface before ship:
tools.toolSearchCodeModemode: "both"When enabled, Tool Search catalogs every eligible OpenClaw, MCP, and client tool after the normal policy/allowlist/denylist pass. Internal timeout and search limits remain conservative implementation details.
How It Works
During PI run preparation, OpenClaw collects the effective tools for the session as it already does today. If
tools.toolSearchis enabled, core creates the control tools, builds a per-session search catalog, and rewrites the prompt-visible tool surface according tomode.mode: "code"tool_search_codemode: "tools"tool_search_codetool_search,tool_describe, andtool_callEach catalog entry keeps the original tool object and metadata. Calls execute the real OpenClaw tool, not a duplicate implementation. That preserves before-tool-call hooks, tool policies, approvals, loop detection, session routing, and result handling.
The JavaScript bridge exposes only:
The model does not receive direct access to the host process, filesystem, network, environment, or plugin internals through this bridge.
Runtime And Security Boundary
The initial implementation used
node:vmin the gateway process. Review correctly flagged that as insufficient for model-authored JavaScript and async CPU-bound continuations. The current implementation runs code mode in a short-lived child Node process instead.The hardened path:
tool_search_codecallsearch,describe, andcallbridge methodsSIGKILLon timeoutThis covers the prior review findings:
Tool execution still happens in the parent through OpenClaw's normal tool runtime after the bridge request has been validated.
Real behavior proof
tbx_01kr7ds1z93cw4d6t3g6x54qedrunning a real OpenClaw Gateway, mock OpenAI provider, PI harness, and temporary fake plugin with many registered tools.node --import tsx scripts/tool-search-gateway-e2e.ts && pnpm check:changed && pnpm build.tbx_01kr7ds1z93cw4d6t3g6x54qedshowed normal lanestatus: completed,providerDeclaredToolCount: 56, plannedfake_plugin_tool_17, provider raw bytes78330; Tool Search lanestatus: completed,providerDeclaredToolCount: 1, plannedtool_search_code, provider raw bytes35133, session log mentionstool_search_code: 20andfake_plugin_tool_17: 43.fake_plugin_tool_17was called successfully in both normal mode and Tool Search mode, while Tool Search reduced declared provider tools by 55 and provider raw bytes by 43197.The gateway E2E harness sets up a fake plugin catalog with many tools, runs the same request twice, and inspects provider requests plus session logs:
tool_search_codeis exposed initiallyLatest Crabbox/Testbox proof:
tbx_01kr7ds1z93cw4d6t3g6x54qednode --import tsx scripts/tool-search-gateway-e2e.ts && pnpm check:changed && pnpm buildE2E metrics from that run:
fake_plugin_tool_17tool_search_code: 0fake_plugin_tool_17: 28tool_search_codetool_search_code: 20fake_plugin_tool_17: 43This verifies that Tool Search reduces the exposed provider tool surface while still letting the model discover and call the same underlying fake plugin tool through OpenClaw's runtime.
Tests And Verification
Local verification:
Remote verification:
Security regression tests added:
processescape attempts from model-authored codeDocs
New docs page:
docs/tools/tool-search.mdThe docs cover: