diff --git a/docs/roadmap/07-think-tool.md b/docs/roadmap/07-think-tool.md new file mode 100644 index 00000000000..b145e917f87 --- /dev/null +++ b/docs/roadmap/07-think-tool.md @@ -0,0 +1,99 @@ +# Think / Plan Tool + +## Branch: `feature/think-tool` + +## Problem + +Local models (7B-32B running on LM Studio) make impulsive tool calls — they +jump straight to editing without understanding the problem. Larger API models +do this too but less often. There's no mechanism to force structured reasoning +before acting. + +Delta has `Kind.Think` in its enum but no actual tool backing it. + +## What it is + +A no-op tool that accepts the model's reasoning and returns it back into context. +No side effects. The model calls it to think before it acts. + +``` +Model → think({ reasoning: "The user wants X. I see files A, B, C are involved. + The dependency chain is A→B→C. I should modify B first because..." }) +← Returns the same text back into context +``` + +## Why it works + +Research on process reward models shows that models which checkpoint reasoning +mid-task make fewer cascading errors. For weaker models this is even more +pronounced — a 7B model that plans before acting outperforms the same model +that jumps straight to edits. + +## Implementation + +### Tool declaration + +```typescript +{ + name: 'think', + description: 'Use this tool to plan your approach before taking action. ' + + 'Write out your reasoning about the problem, what files are involved, ' + + 'what changes are needed, and in what order. This helps you avoid ' + + 'mistakes by thinking before acting. The output is returned to you ' + + 'for reference — no side effects occur.', + parameters: { + type: 'object', + properties: { + reasoning: { + type: 'string', + description: 'Your structured reasoning about the current task.', + }, + }, + required: ['reasoning'], + }, +} +``` + +### Tool implementation + +```typescript +// The entire tool is ~10 lines +async execute({ reasoning }: { reasoning: string }): Promise { + return reasoning; +} +``` + +### System prompt integration + +Add to the system prompt for weaker models (configurable): + +> Before making changes to code, use the `think` tool to plan your approach. +> Outline which files need to change, in what order, and why. This is +> especially important for multi-file changes. + +### Optional: structured think mode + +For models that support it, enforce a schema: + +```typescript +{ + goal: string; + files_involved: string[]; + steps: string[]; + risks: string[]; + current_step: number; +} +``` + +This could be a `think_structured` variant or a config flag. + +## Files to create +- `packages/core/src/tools/think.ts` — tool implementation +- `packages/core/src/tools/think.test.ts` + +## Files to modify +- `packages/core/src/config/config.ts` — register tool in `registerCoreTool()` +- `packages/core/src/core/prompts.ts` — add think guidance to system prompt + (gated on config flag or model capability) + +## Effort: ~1 hour diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 942d4c0e3c0..7d41caa0fe0 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -30,6 +30,7 @@ import { import { TodoWriteTool } from '../tools/todoWrite.js'; import { WebSearchTool } from '../tools/web-search.js'; import { SubAgentTool } from '../tools/subAgentTool.js'; +import { ThinkTool } from '../tools/think.js'; import { DeltaClient } from '../core/client.js'; import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; import { GitService } from '../services/gitService.js'; @@ -916,6 +917,7 @@ export class Config { registerCoreTool(WebSearchTool, this); } registerCoreTool(SubAgentTool, this); + registerCoreTool(ThinkTool); await registry.discoverAllTools(); return registry; diff --git a/packages/core/src/tools/think.ts b/packages/core/src/tools/think.ts new file mode 100644 index 00000000000..c27655e4256 --- /dev/null +++ b/packages/core/src/tools/think.ts @@ -0,0 +1,73 @@ +/** + * Think tool — forces the model to reason before acting. + * + * A no-op tool that accepts structured reasoning and returns it back into + * context. No side effects. The model calls it to plan before making changes. + * + * This is the single best compensator for weaker local models — research on + * process reward models shows that models which checkpoint reasoning mid-task + * make fewer cascading errors. + */ + +import { + BaseDeclarativeTool, + BaseToolInvocation, + Kind, + ToolResult, +} from './tools.js'; +import { FunctionDeclaration } from '@google/genai'; + +interface ThinkParams { + reasoning: string; +} + +const thinkToolSchemaData: FunctionDeclaration = { + name: 'think', + description: + 'Use this tool to plan your approach before taking action. Write out your reasoning about the problem, what files are involved, what changes are needed, and in what order. This helps you avoid mistakes by thinking before acting. The output is returned to you for reference — no side effects occur.', + parametersJsonSchema: { + type: 'object', + properties: { + reasoning: { + type: 'string', + description: 'Your structured reasoning about the current task.', + }, + }, + required: ['reasoning'], + $schema: 'http://json-schema.org/draft-07/schema#', + }, +}; + +// The invocation is trivially simple — return the reasoning back to the model. +// No file I/O, no side effects, no confirmation needed. +class ThinkToolInvocation extends BaseToolInvocation { + getDescription(): string { + const preview = this.params.reasoning.substring(0, 80); + return preview.length < this.params.reasoning.length ? `${preview}...` : preview; + } + + async execute(): Promise { + return { + llmContent: [{ text: this.params.reasoning }], + returnDisplay: this.params.reasoning, + }; + } +} + +export class ThinkTool extends BaseDeclarativeTool { + static readonly Name: string = thinkToolSchemaData.name!; + + constructor() { + super( + ThinkTool.Name, + 'Think', + thinkToolSchemaData.description!, + Kind.Think, + thinkToolSchemaData.parametersJsonSchema, + ); + } + + protected createInvocation(params: ThinkParams) { + return new ThinkToolInvocation(params); + } +}