1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Canonical list of built-in tools that ship deferred
//! by default. Deferred tools are excluded from
//! `ToolRegistry::to_tool_defs_non_deferred()` (the slice every
//! provider shim — Anthropic / MiniMax / OpenAI / Gemini / DeepSeek
//! / xAI / Mistral — emits in the request body) and instead surface
//! through `ToolSearch` discovery + the
//! `<available-deferred-tools>` synthetic block. The model fetches
//! a deferred tool's full schema on demand via
//! `ToolSearch(select:<name>)`.
//!
//! Adding a tool to [`BUILT_IN_DEFERRED_TOOLS`] is the only step
//! required for it to participate in the `ToolSearch` budget — no
//! per-call-site change needed. The sweep
//! [`mark_built_in_deferred`] runs at agent boot, idempotent vs
//! gated tools (entries not registered in this boot are silently
//! skipped because [`ToolRegistry::set_meta`] only writes the
//! side-channel meta map).
//!
//! Provider-agnostic: deferral lives at the registry layer, not in
//! any provider shim. Switching providers does not change which
//! tools are deferred.
//!
//! IRROMPIBLE refs:
//! - `claude-code-leak/src/Tool.ts:438-449` — `shouldDefer` /
//! `alwaysLoad` semantics. Deferred tools are sent with
//! `defer_loading: true`; `alwaysLoad: true` is the per-tool
//! opt-out (we don't need it today, no built-in requires turn-1
//! appearance).
//! - `claude-code-leak/src/tools/ToolSearchTool/prompt.ts:62-108`
//! — `isDeferredTool` decision tree the consumer uses to pick
//! the deferred subset. Carve-outs (`alwaysLoad`, `isMcp`,
//! `name == TOOL_SEARCH`, KAIROS-mode Brief / SendUserFile,
//! FORK_SUBAGENT-mode Agent) live there; we mirror only the
//! `name == TOOL_SEARCH` carve-out today (ToolSearch itself
//! must always load — the model needs it to discover the rest).
//! - `claude-code-leak/src/services/api/claude.ts:1136-1253` —
//! token-budget rationale: deferred schemas omitted from the
//! request, `<available-deferred-tools>` block injects names +
//! 1-line descriptions instead. Big surfaces (e.g. ~30 MCP
//! tools) save thousands of tokens per turn.
//! - Per-tool `shouldDefer: true` precedents in leak:
//! * `src/tools/TodoWriteTool/TodoWriteTool.ts:51`
//! * `src/tools/NotebookEditTool/NotebookEditTool.ts:94`
//! * `src/tools/RemoteTriggerTool/RemoteTriggerTool.ts:50`
//! * `src/tools/LSPTool/LSPTool.ts:136`
//! * `src/tools/TeamCreateTool/TeamCreateTool.ts:78`
//! * `src/tools/TeamDeleteTool/TeamDeleteTool.ts:36`
//! * `src/tools/TaskListTool/TaskListTool.ts:52` — precedent for
//! list/status read-only tools (we apply it to `TeamList` /
//! `TeamStatus`).
//! * `src/tools/SendMessageTool/SendMessageTool.ts:533` —
//! precedent for messaging tools (we apply it to
//! `TeamSendMessage`).
//! * `src/tools/ListMcpResourcesTool/ListMcpResourcesTool.ts:50`
//! * `src/tools/ReadMcpResourceTool/ReadMcpResourceTool.ts:59`
//! - `research/`: no relevant prior art — OpenClaw is channel-side
//! and has no `ToolSearch` / deferred-tool concept.
use ;
/// Canonical list of `(tool_name, search_hint)` for built-in
/// tools that ship deferred. The hint feeds `ToolSearch` keyword
/// ranking — when present it scores higher than the verbose
/// description (mirrors leak's `searchHint:` field on the tool
/// definition, e.g. `TaskListTool.ts:35`).
///
/// Out of scope (deferred to follow-up slices):
/// - `EnterPlanMode` / `ExitPlanMode` (plan-mode flow
/// control mid-turn warrants separate UX consideration).
/// - 5 cron tools (surface differs from leak's 3-tool
/// shape; defer until cron UX settles).
/// - `WebSearch` / `WebFetch` (web-tools surface still
/// in flux).
pub const BUILT_IN_DEFERRED_TOOLS: & = &;
/// Apply `ToolMeta::deferred_with_hint(hint)` to every tool in
/// [`BUILT_IN_DEFERRED_TOOLS`] that is registered on `registry`.
///
/// Idempotent in two senses:
/// 1. Tools that aren't registered in this boot (gated off via
/// `agent.team.enabled = false`, `agent.lsp.enabled = false`,
/// etc.) are silently skipped — `set_meta` only writes the
/// side-channel meta map and doesn't require a handler.
/// 2. Calling N times has the same effect as calling once — the
/// last write wins and all writes carry identical content.
///
/// Call once at agent boot, AFTER all `tools.register(...)` calls
/// and BEFORE the registry is handed to the runtime. Calling
/// before registration still works (meta lands in the side
/// channel) but can be surprising — the documented call site is
/// post-registration.