返回 CodeWhale
TOOL_LIFECYCLE.md
根目录 / docs / TOOL_LIFECYCLE.md
1 # Historical Tool-Surface Lifecycle Policy (v0.8.53)
2
3 > 阅读简体中文版:[zh_hans/TOOL_LIFECYCLE.md](zh_hans/TOOL_LIFECYCLE.md)
4
5 **Status:** Historical design record, not current runtime documentation. The
6 v0.9.1 canonical action surface and replay-only alias contract are documented in
7 [`RUNTIME_SIMPLIFICATION_DESIGN.md`](RUNTIME_SIMPLIFICATION_DESIGN.md) and
8 [`TOOL_SURFACE.md`](TOOL_SURFACE.md). No catalog code landed in this old cycle — the code
9 work is **deferred**. This document is the umbrella policy for GitHub **#2681**,
10 with **#2682** and **#2683** as concrete instances of the planned diet. It
11 describes *what will be done* and the invariants any future diet PR must hold.
12
13 **Scope of related open work (do not contradict):**
14 - PR **#2684** — subagent role vocabulary, lifecycle signals, eval ergonomics.
15 Legacy subagent-name cleanup + guardrail tests in this policy rebase on #2684.
16 - PR **#2685** — git-history active + RLM/field errors.
17
18 **What actually happened, so you can read the rest as history:** the "hidden
19 compatibility" plan below was *not* what shipped in full. `exec_wait` and
20 `exec_interact` were removed rather than kept dispatchable. The work-progress
21 surface is different: `todo_write` is the sole model-visible name, while
22 `work_update`, `TodoWrite`, `todo`, `checklist_write`, and `checklist_update`
23 stay registered as hidden-compatibility aliases of `TodoWriteTool` so old
24 transcripts replay; `checklist_add`, `checklist_list`, `todo_add`,
25 `todo_update`, and `todo_list` are not registered and are not callable.
26 `tts`/`speech` remain dispatchable. See [`TOOL_SURFACE.md`](TOOL_SURFACE.md)
27 for the shipped contract. Read §4 and §8 as a rejected catalog-diet proposal,
28 not as a guarantee that every listed alias is gone.
29
30 **All file:line citations here were correct at v0.8.52/0.8.53 and are now
31 expired.** They have not been rewritten, because renumbering a historical
32 record makes it look current. Symbols this document cites that no longer exist
33 at all include `ARCEE_FIRST_TURN_NATIVE_TOOLS` and `apply_provider_tool_policy`
34 (both removed by `1bfcced43c`, "fix(engine): remove Arcee tool catalog
35 exception"), and the planned `HIDDEN_COMPATIBILITY_TOOLS` / `DEPRECATED_ALIASES`
36 sets, which were never written. Resolve any symbol here against the tree before
37 acting on it.
38
39 ---
40
41 ## 1. Purpose and the weaker-model problem
42
43 Codewhale ships a large native tool surface. The first-turn *active* partition
44 of that surface is what every model sees before it has run a single
45 `tool_search_*` call. Today that active set contains several **near-duplicate
46 tools** that map to the *same* implementation under different names:
47
48 - `exec_wait` and `exec_shell_wait` are both `ShellWaitTool`
49 (`crates/tui/src/tools/registry.rs:526,529`).
50 - `exec_interact` and `exec_shell_interact` are both `ShellInteractTool`
51 (`registry.rs:527,530`).
52 - `tts` and `speech` are both `SpeechTool`
53 (`registry.rs:787-792`, both deferred).
54 - `todo_write` is the single model-visible `TodoWriteTool` surface;
55 `todo_write`, `TodoWrite`, `todo`, `checklist_write`, and
56 `checklist_update` are hidden compat aliases of it.
57
58 For a strong model, redundant names are harmless noise. For **weaker / smaller
59 models** (the Arcee Trinity lane, `deepseek-v4-flash` child executors, and any
60 non-thinking executor), every additional near-duplicate in the visible set is a
61 real cost:
62
63 - It widens the choice space with options that do *nothing distinct*, increasing
64 wrong-tool selection and oscillation between synonyms.
65 - It spends scarce first-turn catalog budget (Section 5) on zero-information
66 entries.
67 - It dilutes the "one name = one thing" contract that lets a small model reason
68 about the surface at all.
69
70 The lifecycle policy exists to **shrink and discipline the model-visible
71 surface** without ever breaking the ability to replay an old transcript that
72 referenced a now-retired name.
73
74 ### Canonical work-tracking surface for v0.9.1
75
76 The model-visible progress surface is a single tool: `todo_write` (#4132).
77 Agents and Fleet workers use it for concrete To-do / Work progress under the
78 active runtime thread or durable task.
79
80 `task_*` and the Fleet/Workflow ledger remain the durable lifecycle owners.
81 Checklist metadata is the model-visible projection of progress:
82 `task_updates.checklist` carries the current items, completion percentage, and
83 in-progress item.
84
85 **The To-do is the only Work surface.** `update_plan` is conversational
86 reasoning — strategy, context, and route notes for complex initiatives. It is
87 not a progress surface, must not duplicate To-do items, and plan-only state is
88 never rendered as a To-do snapshot.
89
90 **No request re-states the list.** The model learns what is on the To-do the way
91 it learns anything else: from the result its own `todo_write` call returned,
92 which is ordinary persisted history. Nothing is appended to a parent turn-loop
93 step or a sub-agent step. The complete list stays visible in the UI, which is a
94 different surface from the request.
95
96 `crates/tui/src/todo_snapshot.rs` renders the one bounded body — hard-bounded in
97 both item count and characters, in-progress item preserved preferentially, any
98 elision marked — for the three seams that show a snapshot *once*, because a
99 person asked: the `<codewhale:fork_state>` block a newly forked agent is handed,
100 `/relay`, and the in-transcript agent card.
101
102 Two properties of that renderer are load-bearing:
103
104 - **Authority.** The snapshot is read from the `WorkRuntime` graph projection
105 when a runtime owns that list, because `todo_write` stages there and only
106 publishes into the legacy `SharedTodoList` view later. Sessions with no
107 attached runtime read the list directly.
108 - **Per-agent isolation.** Every agent reads *its own* list (`#4810`), so a
109 worker sees its own progress and never a parent's or sibling's. The parent's
110 list reaches a forked child only as the immutable `<codewhale:fork_state>`
111 To-do section, resolved at the spawn seam so a same-turn `todo_write` is
112 included.
113
114 The renderer bounds and frames the snapshot; it does not vet To-do content. It
115 guarantees that item text cannot close the wrapper early, cannot forge the line
116 format with control characters, and cannot exceed the item/character bounds —
117 not that arbitrary item text is safe to follow as instructions.
118
119 The registered hidden compatibility aliases of `todo_write` are `work_update`,
120 `TodoWrite`, `todo`, `checklist_write`, and `checklist_update`
121 (`ToolRegistryBuilder::with_todo_tool`). They remain dispatchable against the
122 same To-do state so old transcripts replay without data loss, but they are not
123 advertised to the model catalog (`TodoWriteTool::model_visible` is true only
124 for the canonical name). `checklist_add`, `checklist_list`, `todo_add`,
125 `todo_update`, and `todo_list` are not registered and are not callable.
126
127 ---
128
129 ## 2. The five lifecycle states
130
131 Every native tool name occupies exactly one lifecycle state.
132
133 | State | Meaning | Visible on first turn? | In `tool_search_*`? | Executes if called? | When used |
134 |---|---|---|---|---|---|
135 | **active** | Canonical, in the first-turn catalog head | **Yes** | n/a (already active) | Yes | The tool a model should reach for by default |
136 | **deferred** | Registered + discoverable, hydrated on demand | No | **Yes** | Yes | Real, useful tools that don't earn a first-turn slot |
137 | **hidden-compatibility** | Registered + dispatchable, but removed from active **and** from search | No | **No** | **Yes — identical behavior, silent** | Old synonym kept only so old transcripts replay; no model should newly discover it |
138 | **deprecated** | Like hidden-compat, but execution **appends a replacement notice to result metadata** | No | **No** | **Yes — works, plus a "use X instead" notice** | A retired name we actively steer callers off of, still safe to replay |
139 | **removed** | Not registered at all | No | No | **No — hard error** | Only after `planned_removal_version`, once replay support is formally dropped |
140
141 ### hidden-compatibility vs deprecated — be precise
142
143 Both states are **invisible** (not active, not in tool search) and both remain
144 **dispatchable** (calling them still works). The *only* difference is the
145 caller-facing signal:
146
147 - **hidden-compatibility:** completely silent. The tool behaves byte-for-byte
148 like its canonical twin. We use this when there is *no behavioral or naming
149 lesson to teach* — the name was a pure alias and we simply don't want models
150 re-learning it. (Example: `exec_wait` is literally `exec_shell_wait`.)
151 - **deprecated:** behaves identically *and succeeds*, but the tool result's
152 **metadata** carries an appended notice like
153 `"deprecated: use <replacement> instead"`. The notice goes **only in the
154 result metadata returned for that call** — never in the cached tool catalog
155 prefix (see Section 8). We use this when there is a canonical replacement we
156 want the caller (and any human reading the transcript) nudged toward.
157
158 Neither state ever changes the *behavior* of the call. Replay always works.
159
160 ---
161
162 ## 3. Representation in code
163
164 The lifecycle is represented as **const name-sets plus an alias/manifest table**
165 in `crates/tui/src/core/engine/tool_catalog.rs`, alongside the existing
166 `DEFAULT_ACTIVE_NATIVE_TOOLS` (`tool_catalog.rs:37-64`) and
167 `ARCEE_FIRST_TURN_NATIVE_TOOLS` (`tool_catalog.rs:106-115`).
168
169 ### 3a. Name-sets and the manifest (sketch)
170
171 ```rust
172 // crates/tui/src/core/engine/tool_catalog.rs (planned)
173
174 /// Tools removed from the active set AND from tool-search, but still
175 /// registered and dispatchable with byte-identical behavior. Silent.
176 pub(super) const HIDDEN_COMPATIBILITY_TOOLS: &[&str] = &[
177 "exec_wait", // == exec_shell_wait (ShellWaitTool)
178 "exec_interact", // == exec_shell_interact (ShellInteractTool)
179 "tts", // == speech (SpeechTool)
180 "work_update", // == todo_write (TodoWriteTool)
181 "TodoWrite", // == todo_write (TodoWriteTool)
182 "todo", // == todo_write (TodoWriteTool)
183 "checklist_write", // == todo_write (TodoWriteTool)
184 "checklist_update", // == todo_write (TodoWriteTool)
185 ];
186
187 /// Deprecated aliases: invisible + dispatchable, with a replacement notice
188 /// appended to RESULT METADATA only (never the cached prefix).
189 pub(super) struct DeprecatedAlias {
190 pub name: &'static str,
191 pub replacement: &'static str,
192 pub note: &'static str,
193 }
194
195 pub(super) const DEPRECATED_ALIASES: &[DeprecatedAlias] = &[
196 // Empty in the #4132 work-surface cutover: the legacy names above are
197 // silent hidden-compatibility aliases of todo_write for transcript replay.
198 ];
199
200 #[inline]
201 pub(super) fn is_hidden_or_deprecated(name: &str) -> bool {
202 HIDDEN_COMPATIBILITY_TOOLS.contains(&name)
203 || DEPRECATED_ALIASES.iter().any(|d| d.name == name)
204 }
205 ```
206
207 ### 3b. The two filter points
208
209 1. **Catalog / tool-search exclusion (tool_catalog.rs).**
210 Deferral is decided by `should_default_defer_tool` (`tool_catalog.rs:66-82`),
211 and the active set is the head built by `build_model_tool_catalog`
212 (`tool_catalog.rs:178-196`). Hidden-compat and deprecated tools must be
213 forced *out of the active head* and *out of the tool-search-discoverable
214 pool*. Concretely, the deferral predicate gains a short-circuit so these
215 names are never active, and the tool-search index builder skips any name for
216 which `is_hidden_or_deprecated(name)` is true. Arcee's narrowed first-turn
217 path (`apply_provider_tool_policy`, `tool_catalog.rs:134-149`) already
218 excludes them by construction since they aren't in
219 `ARCEE_FIRST_TURN_NATIVE_TOOLS`.
220
221 2. **Result-notice append (tool_routing.rs).**
222 Dispatch already routes by tool name in
223 `crates/tui/src/tui/tool_routing.rs` (e.g. the wait/interact unification at
224 `tool_routing.rs:1139-1140`). After a successful dispatch, if the called name
225 is in `DEPRECATED_ALIASES`, the router appends the matching `note` to the
226 **result metadata only**. Hidden-compat names append nothing.
227
228 ### 3c. Why name-sets, not a per-`ToolSpec` enum field
229
230 A per-`ToolSpec` `lifecycle: Lifecycle` field was rejected for three reasons:
231
232 - **Prefix-cache safety.** The tool catalog array is part of DeepSeek's
233 immutable KV prefix (`tool_catalog.rs:169-177`). A per-spec field invites
234 serializing lifecycle state *into* each tool's schema, which is exactly the
235 kind of head mutation that forces a full re-prefill. Name-sets live entirely
236 in the catalog-build logic and never touch the emitted tool JSON.
237 - **Single source of truth + diffability.** The diet for a release is one small,
238 reviewable edit to two or three const arrays in one file, instead of scattered
239 field flips across many tool modules.
240 - **Registration stays orthogonal.** Tools remain registered exactly as today
241 (e.g. `with_shell_tools`, `registry.rs:523-531`). Lifecycle is a *catalog
242 policy* layered on top of registration, not a property baked into the tool.
243
244 ---
245
246 ## 4. Deprecation manifest (the #2681 acceptance-criteria table)
247
248 This was the proposed manifest. Columns are the #2681 AC columns. No entry was
249 "removed" in 0.8.53; replay was to be supported for everything listed.
250
251 > **Superseded in part.** `exec_wait` and `exec_interact` were removed
252 > (`shell_surface_exposes_lowercase_bash_and_hides_legacy_handler` in
253 > `crates/tui/src/tools/registry/tests.rs`); they are not callable. The
254 > work-progress rows are not uniformly removed. `todo_write` is the canonical
255 > model-visible `TodoWriteTool`; `ToolRegistryBuilder::with_todo_tool` also
256 > registers hidden replay aliases `work_update`, `TodoWrite`, `todo`,
257 > `checklist_write`, and `checklist_update`. `checklist_add`, `checklist_list`,
258 > `todo_add`, `todo_update`, and `todo_list` are not registered (engine tests
259 > assert they "must not be callable"). The `"must no longer be callable"`
260 > assertion in `registry/tests.rs` (`rlm_is_the_only_registered_session_surface`)
261 > applies to the old `rlm_*` session names, not to checklist/todo. `tts`
262 > remains dispatchable via `with_speech_tools`. The `replay_supported = Yes`
263 > column is true for `tts` and for the registered todo/checklist aliases; false
264 > for `exec_wait`/`exec_interact` and for the unregistered checklist/todo names.
265
266 | Alias | Replacement (canonical) | Lifecycle state | first_deprecated_version | planned_removal_version | replay_supported |
267 |---|---|---|---|---|---|
268 | `exec_wait` | `exec_shell_wait` | hidden-compatibility | 0.8.53 | TBD (≥ 0.9.x) | Yes |
269 | `exec_interact` | `exec_shell_interact` | hidden-compatibility | 0.8.53 | TBD (≥ 0.9.x) | Yes |
270 | `tts` | `speech` | hidden-compatibility | 0.8.53 | TBD (≥ 0.9.x) | Yes |
271 | `checklist_write` | `todo_write` | hidden-compatibility | 0.9.0 | TBD (≥ 0.9.x) | Yes |
272 | `checklist_add` | `todo_write` | hidden-compatibility | 0.9.0 | TBD (≥ 0.9.x) | Yes |
273 | `checklist_update` | `todo_write` | hidden-compatibility | 0.9.0 | TBD (≥ 0.9.x) | Yes |
274 | `checklist_list` | `todo_write` | hidden-compatibility | 0.9.0 | TBD (≥ 0.9.x) | Yes |
275 | `todo_write` | `todo_write` | hidden-compatibility | 0.8.53 | TBD (≥ 0.9.x) | Yes |
276 | `todo_add` | `todo_write` | hidden-compatibility | 0.8.53 | TBD (≥ 0.9.x) | Yes |
277 | `todo_update` | `todo_write` | hidden-compatibility | 0.8.53 | TBD (≥ 0.9.x) | Yes |
278 | `todo_list` | `todo_write` | hidden-compatibility | 0.8.53 | TBD (≥ 0.9.x) | Yes |
279
280 The `todo_*` aliases first entered hidden compatibility in v0.8.53. v0.9.0
281 changes their canonical replacement to `todo_write`; it does not reset their
282 first-deprecated version.
283
284 **Legacy subagent names — removed, no manifest entry needed.**
285 The model-visible subagent surface is only `agent`. The old lifecycle names and
286 the experimental tool-agent lane were removed rather than kept as hidden
287 compatibility tools.
288
289 `planned_removal_version` is intentionally `TBD`: a name only moves to **removed**
290 once we formally drop replay for transcripts old enough to contain it, which is a
291 separate, deliberate decision per name.
292
293 ---
294
295 ## 5. Active-catalog budget (per mode, per provider)
296
297 The active set is the first-turn cost. Do not duplicate the exact
298 `DEFAULT_ACTIVE_NATIVE_TOOLS` count here: adjacent PRs in the v0.8.53 batch may
299 add or remove active tools, and the source of truth is always
300 `tool_catalog.rs`. This document defines the diet policy and invariants, not a
301 second catalog snapshot.
302
303 ### Per provider
304
305 | Provider | First-turn active source | Budget policy |
306 |---|---|---|
307 | Default (DeepSeek et al.) | `DEFAULT_ACTIVE_NATIVE_TOOLS` | Remove duplicate aliases from the active head when their canonical twins stay active; any net growth needs an explicit budget decision. |
308 | Arcee (Trinity) | `ARCEE_FIRST_TURN_NATIVE_TOOLS` | Provider-specific read-only WAF workaround; unchanged by the default diet unless explicitly reviewed. |
309
310 The default diet removes `exec_wait` and `exec_interact` from the active head
311 (they become hidden-compat; their canonical twins `exec_shell_wait` /
312 `exec_shell_interact` stay). `tts` and the legacy `todo_*` aliases remain out
313 of the active set. The canonical `todo_write` tool became eager in v0.9.6 as an
314 explicit budget decision so ordinary progress tracking never requires a
315 discovery turn.
316
317 ### Per mode (Plan / Agent / YOLO)
318
319 The native active head is the **same set across modes** by design — mode does not
320 add or remove native tools from `DEFAULT_ACTIVE_NATIVE_TOOLS`
321 (`should_default_defer_tool` ignores `_mode` for native tools,
322 `tool_catalog.rs:66-68`). Mode affects **MCP** deferral instead:
323 `apply_mcp_tool_deferral` keeps MCP tools deferred unless `mode == Yolo`
324 (`tool_catalog.rs:162-167`).
325
326 | Mode | Native active budget | MCP tools active? |
327 |---|---|---|
328 | Plan | same native head | No (deferred) |
329 | Agent | same native head | No (deferred) |
330 | YOLO | same native head | Yes (a known, intentional widening) |
331
332 **Budget rule:** the native active head must stay byte-identical across Plan ↔
333 Agent ↔ YOLO (Section 8). Any growth of the head requires retiring something
334 else or an explicit budget bump in this doc.
335
336 ---
337
338 ## 6. The canonical-surface rule
339
340 > **Every model-visible (active or deferred-discoverable) tool must have one
341 > clear niche. If a tool is superseded, it gets a named replacement and moves to
342 > hidden-compatibility or deprecated — it does not stay visible.**
343
344 ### Canonical vs compatibility summary for the confusing clusters
345
346 | Cluster | Canonical (keep visible) | Compatibility / retired | Notes |
347 |---|---|---|---|
348 | **Shell wait** | `exec_shell_wait` | `exec_wait` → hidden-compat | Same `ShellWaitTool` (`registry.rs:526,529`); router already unifies (`tool_routing.rs:1139`) |
349 | **Shell interact** | `exec_shell_interact` | `exec_interact` → hidden-compat | Same `ShellInteractTool` (`registry.rs:527,530`) |
350 | **Work progress / checklist / todo** | `todo_write` | registered hidden-compat: `work_update`, `TodoWrite`, `todo`, `checklist_write`, `checklist_update`; not registered: `checklist_add`/`list`, `todo_add`/`update`/`list` | Same `TodoWriteTool`; registered aliases replay old transcripts only |
351 | **Speech / tts** | `speech` | `tts` → hidden-compat | Same `SpeechTool` (`registry.rs:787-792`) |
352 | **Subagent lifecycle** | `agent` | old lifecycle names and tool-agent lane removed | Single async launcher. (The "child agents are leaf workers" note here did not ship — see §7.) |
353 | **Edit family** | `apply_patch`, `edit_file`, `write_file`, `fim_edit` | none — **all distinct niches** | NOT touched (per #2681 non-goals); doc-only canonical guidance |
354 | **Search family** | `grep_files` (content), `file_search` (filename), `project_map` (structure) | none — **distinct niches** | NOT touched; no FTS5/BM25/semantic index exists today |
355
356 **Non-goals (explicitly NOT diet targets in this cycle, per #2681):**
357 `apply_patch` / `edit_file` / `write_file` / `fim_edit`;
358 `grep_files` / `file_search` / `project_map`;
359 `fetch_url` / `web.run` / `web_search`;
360 `task_shell_*`; `handle_read` / `retrieve_tool_result`. These have distinct
361 niches and receive **canonical guidance only** — no lifecycle change.
362
363 The RLM surface (`rlm_open` / `rlm_eval` / `rlm_configure` / `rlm_close` /
364 `rlm_session_objects`, `crates/tui/src/tools/rlm.rs`) is likewise out of scope;
365 `handle_read` retrieves var handles, and `finalize` / `FINAL` is an in-kernel
366 Python function, **not a tool** — so there is nothing to retire there.
367
368 ---
369
370 ## 7. Subagent cutover decision: one visible launcher
371
372 The old lifecycle trio and tool-agent lane are removed, not hidden compatibility
373 tools.
374
375 **Decision: expose only `agent`.**
376
377 - `agent` starts one focused background child and returns the agent id plus
378 transcript handle.
379 - Child results arrive as completion events. The parent should keep working
380 instead of polling a lifecycle tool.
381 - Child tool catalogs exclude the removed subagent *lifecycle* tools.
382 (**Not as shipped:** this bullet originally continued "so children are leaf
383 workers and cannot recursively summon more agents." That is not what landed.
384 Children receive `agent` and can recurse to the configured depth — see
385 `with_full_agent_surface_options` and `can_spawn_child` in
386 `tools/subagent/mod.rs`, and [`SUBAGENTS.md`](SUBAGENTS.md).)
387 - Detailed inspection goes through `handle_read` on the returned transcript
388 handle.
389
390 This is a lifecycle simplification, not a provider gate.
391
392 ---
393
394 ## 8. Prefix-cache safety + replay guarantee
395
396 ### Prefix-cache rules every diet PR MUST follow
397
398 The tools array is part of DeepSeek's immutable KV prefix. The catalog-head
399 byte-stability invariant (`tool_catalog.rs:169-196`) is binding:
400
401 1. **Never mutate the active head non-deterministically.** The first-turn active
402 block must be **byte-identical run-to-run** and across Plan ↔ Agent ↔ YOLO.
403 2. **A diet is a one-time deterministic edit.** Removing a name from
404 `DEFAULT_ACTIVE_NATIVE_TOOLS` shifts the head exactly once; after that it must
405 be stable. Land such edits as their own focused change.
406 3. **Notices live in result metadata, never the prefix.** Deprecated replacement
407 notes are appended at dispatch time in `tool_routing.rs` to the *call result*
408 only. **Nothing** about hidden/deprecated state may be serialized into a tool
409 schema, description, or the catalog array.
410 4. **Preserve ordering and partitioning.** `build_model_tool_catalog` sorts each
411 partition by name and keeps built-ins as a contiguous prefix ahead of MCP
412 tools (`tool_catalog.rs:186-194`). Diet edits must not break this.
413 5. **Hidden/deprecated tools are excluded *before* the head is built**, so their
414 removal is the only head change — they do not appear in the prefix at all.
415
416 ### Old-transcript replay guarantee (not adopted)
417
418 The blanket guarantee below was proposed, not shipped as written. `exec_wait`
419 and `exec_interact` were removed and fail as unknown tools. Work-progress
420 replay is narrower: `todo_write` plus the hidden aliases registered by
421 `with_todo_tool` (`work_update`, `TodoWrite`, `todo`, `checklist_write`,
422 `checklist_update`) still dispatch; `checklist_add`, `checklist_list`,
423 `todo_add`, `todo_update`, and `todo_list` do not. `tts` remains
424 dispatchable. `apply_patch` is a separate replay-only edit alias; see
425 [`TOOL_SURFACE.md`](TOOL_SURFACE.md).
426
427 > For every name in the deprecation manifest with `replay_supported = Yes`, the
428 > tool stays **registered and dispatchable with identical behavior**. Replaying
429 > an old transcript that calls `exec_wait`, `exec_interact`, `tts`, or any
430 > `todo_*` produces the same result it always did. Deprecated names additionally
431 > attach a result-metadata notice; hidden-compat names are silent. A name is only
432 > ever made non-dispatchable (**removed**) after a deliberate, per-name decision
433 > to drop replay support at `planned_removal_version`.
434
435 ---
436
437 ## 9. Required tests
438
439 Any diet PR (and the umbrella #2681 work) must add/keep:
440
441 1. **Duplicate-active-alias guard.** A test asserting that no name in
442 `HIDDEN_COMPATIBILITY_TOOLS` or `DEPRECATED_ALIASES` appears in
443 `DEFAULT_ACTIVE_NATIVE_TOOLS` or `ARCEE_FIRST_TURN_NATIVE_TOOLS`, and that no
444 two active entries resolve to the same underlying tool implementation.
445
446 2. **Tool-search exclusion test.** Assert that hidden-compat and deprecated names
447 are absent from the tool-search-discoverable pool while remaining present in
448 the registry (dispatchable).
449
450 3. **Replay / dispatch tests.** For each manifest name, calling it still
451 executes and returns the same result as its canonical twin. Deprecated names
452 additionally assert the replacement note is present **in result metadata** and
453 absent from the catalog/prefix. Hidden-compat names assert **no** added
454 notice.
455
456 4. **Golden active-block byte test.** A snapshot test pinning the byte
457 serialization of the first-turn active tool block, asserting it is identical
458 across Plan / Agent / YOLO (native head) and stable run-to-run — enforcing the
459 `tool_catalog.rs:169-196` invariant. The golden updates **only** as a
460 reviewed, deliberate one-time edit when the diet lands.
461
462 5. **Subagent guardrail test.** Assert only `agent` is registered as a
463 model-visible subagent tool and that hidden/legacy names from
464 `subagent/mod.rs` are not advertised.
465
466 6. **Leaf-worker test.** Assert subagent tool catalogs exclude `agent` and
467 retired legacy lifecycle names.
468
468 lines MARKDOWN