| 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 |