跳到主要内容

研究 → 规划 → 实现模式

大多数人使用 AI 智能体时会直接进入执行:“重构这段代码”、“移除这个功能”、“添加这个新功能”。这有时效果很好,尤其是较小的改动或代码库,但在复杂改动上往往会垮掉。

RPI(Research、Plan、Implement) 由 HumanLayer 提出,提供了另一种与 AI 智能体协作的方式。这种方法用速度换取清晰、可预测和正确性。

本教程通过一次真实演示说明 RPI 如何工作。读完之后,你应该能在自己的代码库上运行同样的工作流。

前提条件​

1. 导入 RPI 配方

复制下面的片段并粘贴到终端。这会下载主要的 RPI 配方及其子配方,并保存到全局配方目录。

mkdir -p ~/.config/goose/recipes/subrecipes

curl -sL https://raw.githubusercontent.com/aaif-goose/goose/main/documentation/src/pages/recipes/data/recipes/rpi-research.yaml -o ~/.config/goose/recipes/rpi-research.yaml
curl -sL https://raw.githubusercontent.com/aaif-goose/goose/main/documentation/src/pages/recipes/data/recipes/rpi-plan.yaml -o ~/.config/goose/recipes/rpi-plan.yaml
curl -sL https://raw.githubusercontent.com/aaif-goose/goose/main/documentation/src/pages/recipes/data/recipes/rpi-implement.yaml -o ~/.config/goose/recipes/rpi-implement.yaml
curl -sL https://raw.githubusercontent.com/aaif-goose/goose/main/documentation/src/pages/recipes/data/recipes/rpi-iterate.yaml -o ~/.config/goose/recipes/rpi-iterate.yaml

curl -sL https://raw.githubusercontent.com/aaif-goose/goose/main/documentation/src/pages/recipes/data/recipes/subrecipes/rpi-codebase-locator.yaml -o ~/.config/goose/recipes/subrecipes/rpi-codebase-locator.yaml
curl -sL https://raw.githubusercontent.com/aaif-goose/goose/main/documentation/src/pages/recipes/data/recipes/subrecipes/rpi-codebase-analyzer.yaml -o ~/.config/goose/recipes/subrecipes/rpi-codebase-analyzer.yaml
curl -sL https://raw.githubusercontent.com/aaif-goose/goose/main/documentation/src/pages/recipes/data/recipes/subrecipes/rpi-pattern-finder.yaml -o ~/.config/goose/recipes/subrecipes/rpi-pattern-finder.yaml
2. 添加自定义斜杠命令

配方导入后,要在会话中快速调用它们,请为以下每个配方添加自定义斜杠命令:

配方斜杠命令
RPI Research Codebaseresearch_codebase
RPI Create Plancreate_plan
RPI Implement Planimplement_plan
RPI Iterateiterate_plan

RPI 工作流​

在 goose 中,我们使用结构化的 RPI 工作流和配方,系统性地处理复杂的代码库变更。该工作流由斜杠命令组成,引导 goose 经过有纪律的工作阶段:

  1. research_codebase – 记录今天存在的内容。不发表意见。
  2. create_plan - 设计变更,包含清晰的阶段和成功标准。
  3. implement_plan - 逐步执行计划并验证。
  4. iterate_plan – (可选)必要时调整计划。
┌─────────────────────────────────────────────────────────────────────────────-┐
│ RPI WORKFLOW │
├─────────────────────────────────────────────────────────────────────────────-┤
│ │
│ /research_codebase "topic" │
│ │ │
│ ├──► Spawns parallel sub-agents: │
│ │ • find_files (rpi-codebase-locator) │
│ │ • analyze_code (rpi-codebase-analyzer) │
│ │ • find_patterns (rpi-pattern-finder) │
│ │ │
│ └──► Output: thoughts/research/YYYY-MM-DD-HHmm-topic.md │
│ │
│ /create_plan "feature/task" │
│ │ │
│ ├──► Reads research docs │
│ ├──► Asks clarifying questions │
│ ├──► Proposes design options │
│ │ │
│ └──► Output: thoughts/plans/YYYY-MM-DD-HHmm-description.md │
│ │
│ /implement_plan "plan path" │
│ │ │
│ ├──► Executes phase by phase │
│ ├──► Runs verification after each phase │
│ ├──► Updates checkboxes in plan │
│ │ │
│ └──► Working code │
│ │
│ /iterate_plan "plan path" + feedback │
│ │ │
│ ├──► Researches only what changed │
│ ├──► Updates the plan surgically │
│ │ │
│ └──► Updated plan │
│ │
└─────────────────────────────────────────────────────────────────────────────-┘

所有 RPI 输出都放在可预测的位置:

thoughts/
├── research/
│ └── YYYY-MM-DD-HHmm-topic.md
└── plans/
└── YYYY-MM-DD-HHmm-description.md

任务​

在本教程中,我想从一个大型代码库中移除一个现有功能。

这不是小改动。该功能涉及:

  • 核心 Rust 代码
  • TypeScript
  • 配置
  • 测试
  • 文档

这正是智能体经常吃力的任务类型,不是因为它们做不到,而是因为工作跨越了太多上下文,无法安全地“直接去做”。

所以我们不直接跳到实现,而是使用 RPI。

会话 1:研究​

实现之前先规划,已经成为广泛接受的做法。然而,没有研究的规划会导致事后反噬的假设。因此在 RPI 中,我们从研究开始。

我用 /research_codebase 命令开始提示,后面跟着用自然语言写出的主题

/research_codebase "look through the cloned goose repo and research how the LLM Tool Discovery is implemented"

这条命令调用 RPI Research Codebase 配方,它的职责非常严格:

  • 记录存在的内容
  • 不建议改动
  • 不批评
  • 不规划

使用这个配方时,goose 会自动派生三个并行子智能体:

  • find_files:使用代码库定位器找出相关文件所在的位置。
  • analyze_code:完整阅读这些文件,并记录它们如何工作。
  • find_patterns:在仓库其他地方寻找类似功能或约定。

这些子智能体独立运行并汇报。你不必自己编排。

一次方向修正

goose 开始研究后,我注意到它在研究泛泛的“工具发现”。但我只想移除一个叫做 Tool Selection Strategy 的特定功能。于是我停止了 goose,并用更准确的主题重新进行研究。

这不是失败。事实上,这正是研究存在的原因。如果我让 goose“移除 LLM Tool Discovery 功能”,它可能也会移除我们其他的工具发现方法。幸运的是,尽早发现这类错误代价很低,也容易恢复。

/research_codebase 会话的输出是一份详细的研究文档:

./thoughts/research/2025-12-22-llm-tool-selection-strategy.md
---
date: 2025-12-22T23:43:05-06:00
git_commit: 2f876725d3c08f821358e1391a7daadf468193d8
branch: remove-llm-tool-discovery
repository: goose
topic: "LLM Tool Selection Strategy (Tool Discovery Feature)"
tags: [research, codebase, tools, router, llm-selection, experimental]
status: complete
---

# Research: LLM Tool Selection Strategy

## Research Question
How is the "Tool Selection Strategy" feature implemented? This is the experimental feature that uses "LLM-based intelligence to select the most relevant tools based on the user query context."

## Summary

The **Tool Selection Strategy** is an experimental feature (preview) that dynamically filters which tools are presented to the LLM based on the user's query. Instead of sending all tools from all extensions to the LLM, it:

1. Provides a single `router__llm_search` tool to the main LLM
2. When invoked, uses a secondary LLM call to search indexed tools and return only relevant ones
3. Tracks recently used tools to include them automatically

This saves context window space when many extensions are enabled.

**Configuration**: `GOOSE_ENABLE_ROUTER` (boolean, default: false)

## Detailed Findings

### 1. Feature Toggle - Configuration

The feature is controlled by the `GOOSE_ENABLE_ROUTER` config parameter:

**File: `crates/goose/src/agents/tool_route_manager.rs:79-86`**
```rust
pub async fn is_router_enabled(&self) -> bool {
if *self.router_disabled_override.lock().await {
return false;
}

let config = Config::global();
if let Ok(config_value) = config.get_param::<String>("GOOSE_ENABLE_ROUTER") {
return config_value.to_lowercase() == "true";
}

// Default to false if neither is set
false
}
```

**UI Toggle: `ui/desktop/src/components/settings/tool_selection_strategy/ToolSelectionStrategySection.tsx`**
- Displays "Disabled" (default) and "Enabled" radio options
- Updates `GOOSE_ENABLE_ROUTER` config via upsert
- Calls `/agent/update_router_tool_selector` endpoint to reinitialize

### 2. Core Components

#### 2.1 ToolRouteManager
**File: `crates/goose/src/agents/tool_route_manager.rs`**

Central manager that:
- Holds the `RouterToolSelector` instance
- Checks if router is enabled/functional
- Dispatches search tool calls
- Provides tools for router mode

```rust
pub struct ToolRouteManager {
router_tool_selector: Mutex<Option<Arc<Box<dyn RouterToolSelector>>>>,
router_disabled_override: Mutex<bool>, // For recipes that need all tools
}
```

Key methods:
- `is_router_enabled()` - Checks config
- `is_router_functional()` - Enabled AND selector initialized
- `dispatch_route_search_tool()` - Handles `router__llm_search` calls
- `list_tools_for_router()` - Returns search tool + recently used tools

#### 2.2 RouterToolSelector Trait & LLMToolSelector
**File: `crates/goose/src/agents/router_tool_selector.rs`**

```rust
#[async_trait]
pub trait RouterToolSelector: Send + Sync {
async fn select_tools(&self, params: JsonObject) -> Result<Vec<Content>, ErrorData>;
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ErrorData>;
async fn remove_tool(&self, tool_name: &str) -> Result<(), ErrorData>;
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ErrorData>;
async fn get_recent_tool_calls(&self, limit: usize) -> Result<Vec<String>, ErrorData>;
}
```

**LLMToolSelector** implementation:
- Stores tool strings indexed by extension name
- Uses an LLM provider to search tools based on query
- Tracks last 100 tool calls for "recently used" feature

#### 2.3 The Search Tool Definition
**File: `crates/goose/src/agents/router_tools.rs`**

```rust
pub const ROUTER_LLM_SEARCH_TOOL_NAME: &str = "router__llm_search";

pub fn llm_search_tool() -> Tool {
Tool::new(
ROUTER_LLM_SEARCH_TOOL_NAME.to_string(),
r#"Searches for relevant tools based on the user's messages.
Format a query to search for the most relevant tools...
Extension name is not optional, it is required.
The returned result will be a list of tool names, descriptions, and schemas..."#,
// Schema requires: extension_name (string), query (string), optional k (integer)
)
}
```

#### 2.4 Tool Indexing Manager
**File: `crates/goose/src/agents/tool_router_index_manager.rs`**

Handles indexing/removing tools when extensions are added/removed:

```rust
impl ToolRouterIndexManager {
pub async fn update_extension_tools(
selector: &Arc<Box<dyn RouterToolSelector>>,
extension_manager: &ExtensionManager,
extension_name: &str,
action: &str, // "add" or "remove"
) -> Result<()>
}
```

### 3. Flow: How Tool Selection Works

```
┌─────────────────────────────────────────────────────────────────┐
│ INITIALIZATION │
├─────────────────────────────────────────────────────────────────┤
│ 1. User enables "Tool Selection Strategy" in settings │
│ 2. GOOSE_ENABLE_ROUTER = "true" saved to config │
│ 3. /agent/update_router_tool_selector called │
│ 4. ToolRouteManager.update_router_tool_selector(): │
│ a. Creates LLMToolSelector with provider │
│ b. Indexes all tools from all enabled extensions │
│ c. Stores selector in router_tool_selector mutex │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ TOOL LISTING (Router Mode) │
├─────────────────────────────────────────────────────────────────┤
│ When agent.list_tools() is called with router enabled: │
│ │
│ Instead of returning ALL tools, returns: │
│ 1. router__llm_search tool │
│ 2. Recently used tools (last 20 calls) │
│ 3. Platform tools (extension manager, etc.) │
│ │
│ This dramatically reduces context sent to LLM │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ RUNTIME: User Query │
├─────────────────────────────────────────────────────────────────┤
│ 1. User: "list files in current directory" │
│ 2. LLM sees router__llm_search tool in available tools │
│ 3. LLM invokes: router__llm_search( │
│ extension_name: "developer", │
│ query: "list files directory" │
│ ) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SEARCH EXECUTION │
├─────────────────────────────────────────────────────────────────┤
│ Agent.dispatch_tool_call() routes to: │
│ ToolRouteManager.dispatch_route_search_tool() │
│ → LLMToolSelector.select_tools() │
│ │
│ LLMToolSelector: │
│ 1. Gets indexed tool strings for extension │
│ 2. Renders router_tool_selector.md prompt template │
│ 3. Calls LLM provider with tool list + query │
│ 4. Parses response for "Tool: X\nDescription: Y\nSchema: Z" │
│ 5. Returns matching tools as Content │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ LLM RECEIVES RESULTS │
├─────────────────────────────────────────────────────────────────┤
│ Main LLM receives tool definitions in response │
│ Can now invoke the actual tool (e.g., developer__shell) │
│ Tool call is recorded for "recently used" feature │
└─────────────────────────────────────────────────────────────────┘
```

### 4. System Prompt Integration

**File: `crates/goose/src/agents/prompt_manager.rs`**

When router is enabled, the system prompt includes special instructions:

```rust
tool_selection_strategy: self.router_enabled.then(llm_search_tool_prompt),
```

**File: `crates/goose/src/agents/router_tools.rs:41-57`**
```rust
pub fn llm_search_tool_prompt() -> String {
format!(
r#"# LLM Tool Selection Instructions
Important: the user has opted to dynamically enable tools, so although an extension could be enabled, \
please invoke the llm search tool to actually retrieve the most relevant tools to use according to the user's messages.
...
By dynamically enabling tools, you (goose) as the agent save context window space and allow the user to dynamically retrieve the most relevant tools.
"#,
// Lists platform extension tools that are always available
)
}
```

This is injected into `system.md` via `{{tool_selection_strategy}}`.

### 5. LLM Search Prompt Template

**File: `crates/goose/src/prompts/router_tool_selector.md`**
```markdown
You are a tool selection assistant. Your task is to find the most relevant tools based on the user's query.

Given the following tools:
{{ tools }}

Find the most relevant tools for the query: {{ query }}

Return the tools in this exact format for each tool:
Tool: <tool_name>
Description: <tool_description>
Schema: <tool_schema>
```

### 6. Server Endpoint

**File: `crates/goose-server/src/routes/agent.rs`**

```rust
#[utoipa::path(
post,
path = "/agent/update_router_tool_selector",
...
)]
async fn update_router_tool_selector(
State(state): State<Arc<AppState>>,
Json(payload): Json<UpdateRouterToolSelectorRequest>,
) -> Result<Json<String>, StatusCode> {
let agent = state.get_agent_for_route(payload.session_id).await?;
agent
.update_router_tool_selector(None, Some(true)) // reindex_all = true
.await
.map_err(...)?;

Ok(Json("Tool selection strategy updated successfully".to_string()))
}
```

### 7. Recipe Override

Recipes can disable the router to ensure all tools are available:

**File: `crates/goose/src/agents/tool_route_manager.rs:31-34`**
```rust
pub async fn disable_router_for_recipe(&self) {
*self.router_disabled_override.lock().await = true;
*self.router_tool_selector.lock().await = None;
}
```

## Code References

### Core Implementation
- `crates/goose/src/agents/tool_route_manager.rs` - Main manager, config check, dispatch
- `crates/goose/src/agents/router_tool_selector.rs` - `RouterToolSelector` trait, `LLMToolSelector` impl
- `crates/goose/src/agents/router_tools.rs` - `router__llm_search` tool definition, prompt function
- `crates/goose/src/agents/tool_router_index_manager.rs` - Tool indexing on extension add/remove

### Integration Points
- `crates/goose/src/agents/agent.rs` - `dispatch_tool_call()` routes `ROUTER_LLM_SEARCH_TOOL_NAME`
- `crates/goose/src/agents/prompt_manager.rs` - `with_router_enabled()`, injects prompt
- `crates/goose-server/src/routes/agent.rs` - `/agent/update_router_tool_selector` endpoint

### UI
- `ui/desktop/src/components/settings/tool_selection_strategy/ToolSelectionStrategySection.tsx` - Settings toggle

### Prompts
- `crates/goose/src/prompts/router_tool_selector.md` - LLM search prompt template
- `crates/goose/src/prompts/system.md` - `{{tool_selection_strategy}}` placeholder

## Key Design Patterns

1. **Two-stage LLM calls**: Main LLM calls search tool → Search LLM finds relevant tools → Main LLM uses them
2. **Extension-based indexing**: Tools indexed by extension name for filtered searches
3. **Recently used caching**: Last 20 tool calls automatically included (no search needed)
4. **Override mechanism**: Recipes can disable router to get all tools
5. **Lazy initialization**: Selector only created when enabled AND provider available

## Limitations Noted in UI

- "Only tested with Claude models currently" (from UI description)
- Experimental/preview feature

## Open Questions

1. **Performance**: What's the latency impact of the secondary LLM call for tool search?
2. **Accuracy**: How well does the search LLM match tools to queries in practice?
3. **Token savings**: What's the actual context window savings for typical extension counts?
4. **Model compatibility**: Why only tested with Claude? What breaks with other models?

这是一个很大的结构化文件,包含:

  • Git 元数据
  • 文件和行号引用
  • 流程描述
  • 关键组件
  • 未决问题

把它看作该功能今日面貌的技术地图。还没有任何改动,这是有意的。唯一目标是形成共同理解。

作为循环中的人,一定要审阅研究结果!它会为计划提供依据,所以你要确保它准确。

会话 2:规划​

研究完成后,进入规划。

会话

每个阶段都在新会话中进行很重要,这样大语言模型才能高度聚焦于手头的任务。每个会话一个目标!

/create_plan a removal of the Tool Selection Strategy feature

RPI Create Plan 配方首先阅读 goose 创建的研究文档。

然后它做了三件关键的事:

  1. 提出澄清问题

    例如:

    • 完全移除还是弃用?
    • 配置清理应该如何表现?
    • 是否应该重新生成 OpenAPI 产物?
    • 相关测试在哪里?
  2. 给出设计选项

    在有多种合理做法时,goose 把它们列出来,并请我选择。

  3. 产出分阶段的实现计划

输出是一份详细计划:

thoughts/plans/2025-12-23-remove-tool-selection-strategy.md
# Remove Tool Selection Strategy Feature - Implementation Plan

## Overview
Complete removal of the "Tool Selection Strategy" feature (also known as "LLM Tool Router" or "Smart Tool Routing"). This experimental feature used a secondary LLM call to dynamically select which tools to present to the main LLM based on the user's query.

## Current State Analysis
The feature is controlled by `GOOSE_ENABLE_ROUTER` config parameter (default: false). It consists of:
- Core implementation files (4 Rust modules + 1 prompt template)
- Integration points in agent, prompt manager, and server routes
- UI settings section in desktop app
- CLI configuration dialog
- Documentation pages

### Key Discoveries:
- Feature is disabled by default and marked as "experimental/preview"
- Only tested with Claude models
- Has telemetry tracking in posthog
- Snapshot tests in `prompt_manager.rs` use `.with_router_enabled(true)` and will need updating
- `agent.rs` test references `tool_route_manager` but only for context setup, not testing router functionality

## Desired End State
- All Tool Selection Strategy code removed from codebase
- No `GOOSE_ENABLE_ROUTER` config handling (ignored if present in user config)
- No `router__llm_search` tool
- No `/agent/update_router_tool_selector` API endpoint
- No UI settings for tool selection strategy
- No CLI configuration for router strategy
- Documentation removed/updated
- All tests pass, linting passes

## What We're NOT Doing
- Cleaning up existing `GOOSE_ENABLE_ROUTER` entries from user config files (will be ignored)
- Adding deprecation warnings
- Keeping any stub code

## Implementation Approach
Remove in dependency order: core files first, then integration points, then UI/CLI, then documentation. This minimizes compilation errors during the process.

---

## Phase 1: Remove Core Implementation Files

### Overview
Delete the core Rust modules that implement the router functionality.

### Changes Required:

#### 1. Delete core router files
**Files to delete:**
- `crates/goose/src/agents/router_tool_selector.rs`
- `crates/goose/src/agents/router_tools.rs`
- `crates/goose/src/agents/tool_route_manager.rs`
- `crates/goose/src/agents/tool_router_index_manager.rs`
- `crates/goose/src/prompts/router_tool_selector.md`

#### 2. Update module declarations
**File**: `crates/goose/src/agents/mod.rs`
**Changes**: Remove module declarations for deleted files

```rust
// REMOVE these lines:
mod router_tool_selector;
mod router_tools;
mod tool_route_manager;
mod tool_router_index_manager;
```

### Success Criteria:

#### Automated Verification:
- [x] Files deleted
- [x] `cargo build -p goose` compiles (will fail until Phase 2 completes)

**Implementation Note**: Phase 1 will cause compilation errors. Proceed immediately to Phase 2.

---

## Phase 2: Update Agent Integration

### Overview
Remove all router-related code from `agent.rs` and related files.

### Changes Required:

#### 1. Update agent.rs
**File**: `crates/goose/src/agents/agent.rs`
**Changes**:

Remove imports:
```rust
// REMOVE:
use crate::agents::router_tools::ROUTER_LLM_SEARCH_TOOL_NAME;
use crate::agents::tool_route_manager::ToolRouteManager;
use crate::agents::tool_router_index_manager::ToolRouterIndexManager;
```

Remove from Agent struct:
```rust
// REMOVE field:
pub tool_route_manager: Arc<ToolRouteManager>,
```

Remove from Agent::new():
```rust
// REMOVE:
tool_route_manager: Arc::new(ToolRouteManager::new()),
```

Remove method `disable_router_for_recipe`:
```rust
// REMOVE entire method:
pub async fn disable_router_for_recipe(&self) {
self.tool_route_manager.disable_router_for_recipe().await;
}
```

Remove from `dispatch_tool_call` - the `ROUTER_LLM_SEARCH_TOOL_NAME` branch:
```rust
// REMOVE this else-if branch:
} else if tool_call.name == ROUTER_LLM_SEARCH_TOOL_NAME {
match self
.tool_route_manager
.dispatch_route_search_tool(tool_call.arguments.unwrap_or_default())
.await
{
Ok(tool_result) => tool_result,
Err(e) => return (request_id, Err(e)),
}
}
```

Remove from `add_extension` - the router indexing logic:
```rust
// REMOVE this block:
// If LLM tool selection is functional, index the tools
if self.tool_route_manager.is_router_functional().await {
let selector = self.tool_route_manager.get_router_tool_selector().await;
if let Some(selector) = selector {
let selector = Arc::new(selector);
if let Err(e) = ToolRouterIndexManager::update_extension_tools(
&selector,
&self.extension_manager,
&extension.name(),
"add",
)
.await
{
return Err(ExtensionError::SetupError(format!(
"Failed to index tools for extension {}: {}",
extension.name(),
e
)));
}
}
}
```

Remove `list_tools_for_router` method:
```rust
// REMOVE entire method:
pub async fn list_tools_for_router(&self) -> Vec<Tool> {
...
}
```

Remove from `remove_extension` - the router de-indexing logic:
```rust
// REMOVE this block:
// If LLM tool selection is functional, remove tools from the index
if self.tool_route_manager.is_router_functional().await {
let selector = self.tool_route_manager.get_router_tool_selector().await;
if let Some(selector) = selector {
ToolRouterIndexManager::update_extension_tools(
&selector,
&self.extension_manager,
name,
"remove",
)
.await?;
}
}
```

Remove `update_router_tool_selector` method:
```rust
// REMOVE entire method:
pub async fn update_router_tool_selector(
&self,
provider: Option<Arc<dyn Provider>>,
reindex_all: Option<bool>,
) -> Result<()> {
...
}
```

Remove from `reply_internal` stream - the `record_tool_requests` call:
```rust
// REMOVE:
self.tool_route_manager
.record_tool_requests(&requests_to_record)
.await;
```

#### 2. Update extension.rs (PlatformExtensionContext)
**File**: `crates/goose/src/agents/extension.rs`
**Changes**: Remove `tool_route_manager` field from `PlatformExtensionContext` if present

Search for any references to `tool_route_manager` in this file and remove them.

#### 3. Update extension_manager_extension.rs
**File**: `crates/goose/src/agents/extension_manager_extension.rs`
**Changes**: Remove any references to `tool_route_manager`

### Success Criteria:

#### Automated Verification:
- [x] `cargo build -p goose` compiles (may still fail until Phase 3)

---

## Phase 3: Update Prompt Manager

### Overview
Remove router-related prompt building logic.

### Changes Required:

#### 1. Update prompt_manager.rs
**File**: `crates/goose/src/agents/prompt_manager.rs`
**Changes**:

Remove import:
```rust
// REMOVE:
use crate::agents::router_tools::llm_search_tool_prompt;
```

Remove from `SystemPromptContext`:
```rust
// REMOVE field:
#[serde(skip_serializing_if = "Option::is_none")]
tool_selection_strategy: Option<String>,
```

Remove from `SystemPromptBuilder`:
```rust
// REMOVE field:
router_enabled: bool,
```

Remove `with_router_enabled` method:
```rust
// REMOVE entire method:
pub fn with_router_enabled(mut self, enabled: bool) -> Self {
self.router_enabled = enabled;
self
}
```

Update `build` method - remove router_enabled from context:
```rust
// REMOVE from SystemPromptContext construction:
tool_selection_strategy: self.router_enabled.then(llm_search_tool_prompt),
```

Update builder initialization:
```rust
// REMOVE from SystemPromptBuilder initialization:
router_enabled: false,
```

#### 2. Update system.md template
**File**: `crates/goose/src/prompts/system.md`
**Changes**: Remove the `{{tool_selection_strategy}}` placeholder line

```markdown
<!-- REMOVE this line: -->
{{tool_selection_strategy}}
```

#### 3. Update snapshot tests
**File**: `crates/goose/src/agents/prompt_manager.rs` (tests section)
**Changes**: Remove `.with_router_enabled(true)` from tests

In `test_one_extension`:
```rust
// REMOVE:
.with_router_enabled(true)
```

In `test_typical_setup`:
```rust
// REMOVE:
.with_router_enabled(true)
```

#### 4. Update/regenerate snapshots
**Files to update:**
- `crates/goose/src/agents/snapshots/goose__agents__prompt_manager__tests__one_extension.snap`
- `crates/goose/src/agents/snapshots/goose__agents__prompt_manager__tests__typical_setup.snap`

Run `cargo test -p goose prompt_manager` with `INSTA_UPDATE=1` to regenerate snapshots, or manually remove the "LLM Tool Selection Instructions" section from each snapshot.

### Success Criteria:

#### Automated Verification:
- [x] `cargo build -p goose` compiles
- [x] `cargo test -p goose` passes (after snapshot updates)

---

## Phase 4: Update Server Routes

### Overview
Remove the `/agent/update_router_tool_selector` endpoint.

### Changes Required:

#### 1. Update agent.rs routes
**File**: `crates/goose-server/src/routes/agent.rs`
**Changes**:

Remove request struct:
```rust
// REMOVE:
#[derive(Deserialize, utoipa::ToSchema)]
pub struct UpdateRouterToolSelectorRequest {
session_id: String,
}
```

Remove handler function:
```rust
// REMOVE entire function:
#[utoipa::path(
post,
path = "/agent/update_router_tool_selector",
...
)]
async fn update_router_tool_selector(
...
) -> Result<Json<String>, StatusCode> {
...
}
```

Remove route from router:
```rust
// REMOVE from routes() function:
.route(
"/agent/update_router_tool_selector",
post(update_router_tool_selector),
)
```

### Success Criteria:

#### Automated Verification:
- [x] `cargo build -p goose-server` compiles
- [x] `cargo test -p goose-server` passes

---

## Phase 5: Update CLI Configuration

### Overview
Remove the router configuration dialog from CLI.

### Changes Required:

#### 1. Update configure.rs
**File**: `crates/goose-cli/src/commands/configure.rs`
**Changes**:

Remove the router strategy menu item from `configure_settings_dialog`:
```rust
// REMOVE this item:
.item(
"goose_router_strategy",
"Router Tool Selection Strategy",
"Experimental: configure a strategy for auto selecting tools to use",
)
```

Remove the match arm:
```rust
// REMOVE:
"goose_router_strategy" => {
configure_goose_router_strategy_dialog()?;
}
```

Remove the entire `configure_goose_router_strategy_dialog` function:
```rust
// REMOVE entire function:
pub fn configure_goose_router_strategy_dialog() -> anyhow::Result<()> {
...
}
```

### Success Criteria:

#### Automated Verification:
- [x] `cargo build -p goose-cli` compiles
- [x] `cargo test -p goose-cli` passes

---

## Phase 6: Update Telemetry ✅ COMPLETE

### Overview
Remove router-related telemetry.

### Changes Required:

#### 1. Update posthog.rs
**File**: `crates/goose/src/posthog.rs`
**Changes**:

Remove the router telemetry:
```rust
// REMOVE this block:
if let Ok(router_enabled) = config.get_param::<bool>("GOOSE_ENABLE_ROUTER") {
event
.insert_prop("setting_router_enabled", router_enabled)
.ok();
}
```

### Success Criteria:

#### Automated Verification:
- [x] `cargo build -p goose` compiles

---

## Phase 7: Update Desktop UI ✅ COMPLETE

### Overview
Remove the Tool Selection Strategy settings section from the desktop app.

### Changes Required:

#### 1. Delete UI component directory
**Directory to delete**: `ui/desktop/src/components/settings/tool_selection_strategy/`

#### 2. Update ChatSettingsSection.tsx
**File**: `ui/desktop/src/components/settings/chat/ChatSettingsSection.tsx`
**Changes**:

Remove import:
```typescript
// REMOVE:
import { ToolSelectionStrategySection } from '../tool_selection_strategy/ToolSelectionStrategySection';
```

Remove the Card containing ToolSelectionStrategySection:
```tsx
{/* REMOVE entire Card: */}
<Card className="pb-2 rounded-lg">
<CardHeader className="pb-0">
<CardTitle className="">Tool Selection Strategy (preview)</CardTitle>
<CardDescription>
Experimental: configure how Goose selects tools for your requests, useful when there are
many tools. Only tested with Claude models currently.
</CardDescription>
</CardHeader>
<CardContent className="px-2">
<ToolSelectionStrategySection />
</CardContent>
</Card>
```

#### 3. Regenerate OpenAPI types
Run: `just generate-openapi`

This will update `ui/desktop/openapi.json` and `ui/desktop/src/api/types.gen.ts` to remove the `UpdateRouterToolSelectorRequest` type.

### Success Criteria:

#### Automated Verification:
- [x] `cd ui/desktop && npm run lint` passes
- [x] `cd ui/desktop && npm run typecheck` passes
- [x] OpenAPI types regenerated

#### Manual Verification:
- [ ] Desktop app Settings > Chat page loads without errors
- [ ] No "Tool Selection Strategy" section visible

---

## Phase 8: Update Documentation ✅ COMPLETE

### Overview
Remove documentation for the removed feature.

### Changes Required:

#### 1. Delete tool-router.md
**File to delete**: `documentation/docs/guides/managing-tools/tool-router.md`

#### 2. Update managing-tools index
**File**: `documentation/docs/guides/managing-tools/index.md`
**Changes**:

Remove the Tool Selection Strategy card:
```tsx
{/* REMOVE: */}
<Card
title="Tool Selection Strategy"
description="Optimize tool selection with dynamic routing that loads only the tools you need, reducing context overhead and improving performance."
link="/docs/guides/managing-tools/tool-router"
/>
```

#### 3. Update environment-variables.md
**File**: `documentation/docs/guides/environment-variables.md`
**Changes**:

Remove the `GOOSE_ENABLE_ROUTER` row from the table:
```markdown
<!-- REMOVE this row: -->
| `GOOSE_ENABLE_ROUTER` | Enables [intelligent tool selection strategy](/docs/guides/managing-tools/tool-router) | "true", "false" | "false" |
```

Remove from the example section:
```bash
# REMOVE:
# Enable intelligent tool selection
export GOOSE_ENABLE_ROUTER=true
```

#### 4. Check for other documentation references
Search for any other references to "tool selection", "router", or "GOOSE_ENABLE_ROUTER" in documentation and remove them.

### Success Criteria:

#### Automated Verification:
- [x] No remaining references to GOOSE_ENABLE_ROUTER in documentation

#### Manual Verification:
- [ ] No references to Tool Selection Strategy in docs

---

## Phase 9: Update Tests ✅ COMPLETE (merged into earlier phases)

### Overview
Update any remaining tests that reference the removed functionality.

### Changes Required:

#### 1. Update agent.rs tests
**File**: `crates/goose/tests/agent.rs`
**Changes**:

In `extension_manager_tests::setup_agent_with_extension_manager`, remove the `tool_route_manager` from context setup:
```rust
// REMOVE from PlatformExtensionContext:
tool_route_manager: Some(Arc::downgrade(&agent.tool_route_manager)),
```

#### 2. Update PlatformExtensionContext references
Search for any other test files that set up `PlatformExtensionContext` with `tool_route_manager` and remove that field.

### Success Criteria:

#### Automated Verification:
- [x] `cargo test` passes for all crates
- [x] `./scripts/clippy-lint.sh` passes

---

## Phase 10: Final Cleanup and Verification ✅ COMPLETE

### Overview
Final verification that all changes are complete and correct.

### Changes Required:

#### 1. Search for any remaining references
Run these searches to ensure nothing was missed:
```bash
rg "tool_route" --type rust
rg "router_tool" --type rust
rg "RouterToolSelector" --type rust
rg "ROUTER_LLM_SEARCH" --type rust
rg "llm_search_tool" --type rust
rg "GOOSE_ENABLE_ROUTER" --type rust
rg "tool_selection_strategy" --type rust
rg "ToolSelectionStrategy" --type ts --type tsx
```

#### 2. Run full test suite
```bash
cargo fmt
cargo build
cargo test
./scripts/clippy-lint.sh
```

#### 3. Run UI checks
```bash
cd ui/desktop
npm run lint
npm run build
npm test
```

### Success Criteria:

#### Automated Verification:
- [x] All searches return no results (except in thoughts/research)
- [x] `cargo fmt` - no changes
- [x] `cargo build` - succeeds
- [x] `cargo test` - all tests pass
- [x] `./scripts/clippy-lint.sh` - passes
- [x] UI lint/typecheck - passes

#### Manual Verification:
- [ ] Start goose CLI - works normally
- [ ] Start goose desktop - works normally
- [ ] Settings page loads without errors
- [ ] No console errors related to removed feature

---

## Testing Strategy

### Unit Tests:
- Snapshot tests in `prompt_manager.rs` need regeneration (Phase 3)
- Agent tests need `tool_route_manager` references removed (Phase 9)

### Integration Tests:
- Full `cargo test` after all phases
- Desktop app manual testing after Phase 7

### Regression Testing:
- Ensure normal tool calling still works
- Ensure extension add/remove still works
- Ensure all goose modes (auto, approve, smart_approve, chat) still work

---

## Rollback Plan
If issues are discovered:
1. Git revert the changes
2. The feature was disabled by default, so no user impact from keeping it

---

## Notes
- The research document at `thoughts/research/2025-12-22-llm-tool-selection-strategy.md` should be kept for historical reference
- Users with `GOOSE_ENABLE_ROUTER=true` in their config will simply have the setting ignored

该计划包括:

  • 10 个明确阶段
  • 确切的文件路径
  • 展示要移除什么的代码片段
  • 自动化成功标准
  • 手动验证步骤
  • 用于跟踪进度的复选框

此时,计划成为事实来源。这里的关键转变是,我们从理解走向决策,但仍然不碰代码。

计划足够明确,别人也可以执行它。这不是偶然。记住,实现会在一个全新的会话中进行,所以计划必须有足够的上下文才能真正执行。

同样,作为人,你需要在这里介入,审阅计划并确认它扎实。如果有任何不对的地方,不必从头开始,可以运行 RPI Iterate Plan 计划(/iterate_plan),并说明哪里有问题。goose 随后会阅读现有计划,只研究需要重新思考的部分,提出有针对性的更新,并相应编辑计划。

会话 3:实现​

只有在研究和规划都完成后,才应进入实现。传入计划文档。

/implement_plan thoughts/plans/2025-12-23-remove-tool-selection-strategy.md

RPI Implement Plan 配方故意很乏味。事实上,goose 运行它的时候我睡着了。实现应该感觉像机械操作。如果它感觉有创造性,说明上游缺了什么。但既然你有一份扎实的计划,我建议在 goose 工作时去做点别的(除非计划中有手动步骤)。

它会完整阅读计划,按顺序执行各阶段,每个阶段后运行验证,并在进行过程中直接更新计划文件中的复选框。

最后这一点非常有帮助,因为我的上下文窗口中途满了,而 goose 能够压缩上下文,并凭借计划中的状态更新从离开的地方继续。

最终结果​

跨越 32 个文件的 10 个工作阶段中,研究阶段用了 9 分钟,规划阶段用了 4 分钟,实现阶段用了 39 分钟。所以总共不到一小时……确切地说是 52 分钟。这包括 goose 的工作和测试,以及我回答问题。

绝对不是一个快的过程。但是!当我提交这个 PR时,构建通过了,单独的代码审查智能体一条评论都没有。工作就是做得这么好。

如果没有 AI,我自己做很可能要花好几个小时,因为这个功能复杂且深度集成。而如果让 AI 直接跳到实现,我毫不怀疑它一定会偏离并搞砸某些东西。

所以,虽然 RPI 比让 AI 马上开工更慢,但质量是一流的。这是非常值得的交换。

何时使用 RPI​

对于基本任务,RPI 可能过重。尤其因为它不是一个很快的过程。不过,如果你需要完成跨越多个文件的复杂任务,它是很好的选择。

你可以把 RPI 用于:

  • 重构
  • 迁移
  • 功能添加
  • 大型升级
  • 事故清理
  • 文档大修

亲自试试吧!