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