WarpGrep is a subagent your coding agent calls as a tool. The flow:
- Your agent (Claude, Codex, GPT-4o, Gemini) needs to find code
- It calls WarpGrep with a natural language query
- WarpGrep searches in its own isolated context window, multiple turns, 8 parallel tool calls per turn
- It returns only the relevant file/line-range spans
- Your agent continues with clean context
The parent agent never sees the intermediate search steps, the rejected files, or the dead-end greps. On SWE-Bench Pro, this makes Opus 15.6% cheaper and 28% faster than searching on its own.
SDK adapters for Anthropic, OpenAI, Vercel AI SDK, and Gemini.
Quick Start
Anthropic
OpenAI
Vercel AI SDK
import Anthropic from '@anthropic-ai/sdk';
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const anthropic = new Anthropic();
// Create the tool
const warpGrepSubagent = morph.anthropic.createWarpGrepTool({ repoRoot: '.' });
// Use it with your agent
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 12000,
tools: [warpGrepSubagent],
messages: [{ role: 'user', content: 'Find authentication middleware' }]
});
// Execute the tool call
const toolUse = response.content.find(c => c.type === 'tool_use');
if (toolUse) {
const result = await warpGrepSubagent.execute(toolUse.input);
console.log(warpGrepSubagent.formatResult(result));
}
import OpenAI from 'openai';
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const openai = new OpenAI();
// Create the tool
const warpGrepSubagent = morph.openai.createWarpGrepTool({ repoRoot: '.' });
// Use it with your agent
const response = await openai.chat.completions.create({
model: 'gpt-4o',
tools: [warpGrepSubagent],
messages: [{ role: 'user', content: 'Find authentication middleware' }]
});
// Execute the tool call
const toolCall = response.choices[0].message.tool_calls?.[0];
if (toolCall) {
const result = await warpGrepSubagent.execute(toolCall.function.arguments);
console.log(warpGrepSubagent.formatResult(result));
}
import { generateText, stepCountIs } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
// Create the tool
const warpGrepSubagent = morph.vercel.createWarpGrepTool({ repoRoot: '.' });
// Vercel AI SDK handles the tool loop automatically
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
tools: { grep: warpGrepSubagent },
prompt: 'Find authentication middleware',
stopWhen: stepCountIs(5)
});
E2B
Modal
Daytona
Vercel Sandbox
Cloudflare Sandbox
Docker/SSH
import { Sandbox } from "@e2b/code-interpreter";
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: "YOUR_API_KEY" });
const sandbox = await Sandbox.create();
const repoDir = "/home/user/repo";
// Clone repo and install ripgrep
await sandbox.commands.run(`git clone --depth 1 https://github.com/example/repo ${repoDir}`);
await sandbox.commands.run("apt-get update && apt-get install -y ripgrep");
const warpGrepSubagent = morph.anthropic.createWarpGrepTool({
repoRoot: repoDir,
remoteCommands: {
grep: async (pattern, path) => {
const r = await sandbox.commands.run(
`rg --no-heading --line-number '${pattern}' '${path}'`,
{ cwd: repoDir }
);
return r.stdout || '';
},
read: async (path, start, end) => {
const r = await sandbox.commands.run(`sed -n '${start},${end}p' '${path}'`);
return r.stdout || '';
},
listDir: async (path, maxDepth) => {
const r = await sandbox.commands.run(
`find '${path}' -maxdepth ${maxDepth} -not -path '*/node_modules/*'`
);
return r.stdout || '';
},
},
});
// Use with Anthropic
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
tools: [warpGrepSubagent],
messages: [{ role: 'user', content: 'Find authentication middleware' }]
});
import { ModalClient } from "modal";
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: "YOUR_API_KEY" });
const modal = new ModalClient();
const sandbox = await modal.sandboxes.create(app, image);
const repoDir = "/home/repo";
const warpGrepSubagent = morph.openai.createWarpGrepTool({
repoRoot: repoDir,
remoteCommands: {
grep: async (pattern, path) => {
const proc = await sandbox.exec([
"rg", "--no-heading", "--line-number", pattern, path
]);
return await proc.stdout.readText();
},
read: async (path, start, end) => {
const proc = await sandbox.exec(["sed", "-n", `${start},${end}p`, path]);
return await proc.stdout.readText();
},
listDir: async (path, maxDepth) => {
const proc = await sandbox.exec([
"find", path, "-maxdepth", String(maxDepth)
]);
return await proc.stdout.readText();
},
},
});
// Use with OpenAI
const response = await openai.chat.completions.create({
model: 'gpt-4o',
tools: [warpGrepSubagent],
messages: [{ role: 'user', content: 'Find authentication middleware' }]
});
import { Daytona } from "@daytonaio/sdk";
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: "YOUR_API_KEY" });
const daytona = new Daytona({ apiKey: process.env.DAYTONA_API_KEY });
const sandbox = await daytona.create({ language: 'python' });
const repoDir = "/home/daytona/repo";
const warpGrepSubagent = morph.vercel.createWarpGrepTool({
repoRoot: repoDir,
remoteCommands: {
grep: async (pattern, path) => {
const r = await sandbox.process.executeCommand(
`rg --no-heading --line-number '${pattern}' '${path}'`,
repoDir
);
return r.result || '';
},
read: async (path, start, end) => {
const r = await sandbox.process.executeCommand(
`sed -n '${start},${end}p' '${path}'`,
repoDir
);
return r.result || '';
},
listDir: async (path, maxDepth) => {
const r = await sandbox.process.executeCommand(
`find '${path}' -maxdepth ${maxDepth}`,
repoDir
);
return r.result || '';
},
},
});
// Use with Vercel AI SDK
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
tools: { grep: warpGrepSubagent },
prompt: 'Find authentication middleware'
});
import { Sandbox } from '@vercel/sandbox';
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: "YOUR_API_KEY" });
const sandbox = await Sandbox.create({ runtime: 'node24' });
// Clone the repo manually (source.type: 'git' is not supported)
const repoDir = '/home/user/repo';
await sandbox.runCommand({
cmd: 'git',
args: ['clone', '--depth', '1', 'https://github.com/example/repo.git', repoDir],
});
// ripgrep isn't in Amazon Linux 2023 repos, download the binary
await sandbox.runCommand({
cmd: 'sh',
args: ['-c', 'curl -sL https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz | tar xz -C /tmp && cp /tmp/ripgrep-14.1.1-x86_64-unknown-linux-musl/rg /usr/local/bin/'],
sudo: true,
});
const warpGrepSubagent = morph.anthropic.createWarpGrepTool({
repoRoot: repoDir,
remoteCommands: {
grep: async (pattern, path, glob) => {
const args = ['--no-heading', '--line-number', '-C', '1', pattern, path];
if (glob) args.push('--glob', glob);
const r = await sandbox.runCommand('rg', args);
return await r.stdout();
},
read: async (path, start, end) => {
const r = await sandbox.runCommand('sed', ['-n', `${start},${end}p`, path]);
return await r.stdout();
},
listDir: async (path, maxDepth) => {
const r = await sandbox.runCommand('find', [
path, '-maxdepth', String(maxDepth),
'-not', '-path', '*/node_modules/*',
'-not', '-path', '*/.git/*',
]);
return await r.stdout();
},
},
});
// Use with Anthropic
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
tools: [warpGrepSubagent],
messages: [{ role: 'user', content: 'Find authentication middleware' }]
});
// @morphllm/morphsdk uses Node.js builtins (fs, child_process) that are
// incompatible with the Workers runtime. Use remoteCommands directly instead.
import { getSandbox } from '@cloudflare/sandbox';
const sandbox = getSandbox(env.Sandbox, 'code-search');
// Clone repo (ripgrep is pre-installed via apt-get in the Dockerfile)
await sandbox.exec('git clone --depth 1 https://github.com/example/repo.git /workspace/repo');
const repoDir = '/workspace/repo';
// Pass these remoteCommands to createWarpGrepTool() in your Node.js backend
const remoteCommands = {
grep: async (pattern, path, glob) => {
let cmd = `rg --no-heading --line-number -C 1 '${pattern}' '${path}'`;
if (glob) cmd += ` --glob '${glob}'`;
const r = await sandbox.exec(cmd);
return r.stdout;
},
read: async (path, start, end) => {
const r = await sandbox.exec(`sed -n '${start},${end}p' '${path}'`);
return r.stdout;
},
listDir: async (path, maxDepth) => {
const r = await sandbox.exec(
`find '${path}' -maxdepth ${maxDepth} -not -path '*/node_modules/*' -not -path '*/.git/*'`
);
return r.stdout;
},
};
import { NodeSSH } from 'node-ssh';
import { MorphClient } from '@morphllm/morphsdk';
const morph = new MorphClient({ apiKey: "YOUR_API_KEY" });
const ssh = new NodeSSH();
await ssh.connect({ host: 'your-server.com', username: 'user', privateKey: '...' });
const repoDir = "/home/user/repo";
const warpGrepSubagent = morph.anthropic.createWarpGrepTool({
repoRoot: repoDir,
remoteCommands: {
grep: async (pattern, path) => {
const result = await ssh.execCommand(
`rg --no-heading --line-number '${pattern}' '${path}'`,
{ cwd: repoDir }
);
return result.stdout || '';
},
read: async (path, start, end) => {
const result = await ssh.execCommand(`sed -n '${start},${end}p' '${path}'`);
return result.stdout || '';
},
listDir: async (path, maxDepth) => {
const result = await ssh.execCommand(`find '${path}' -maxdepth ${maxDepth}`);
return result.stdout || '';
},
},
});
// Use with Anthropic
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
tools: [warpGrepSubagent],
messages: [{ role: 'user', content: 'Find authentication middleware' }]
});
Your sandbox needs ripgrep (rg) installed for the grep function. Most sandbox providers support apt-get install ripgrep (Ubuntu/Debian). On Amazon Linux 2023 (Vercel Sandbox), download the static binary instead.
Configuration
const warpGrepSubagent = morph.openai.createWarpGrepTool({
repoRoot: '.',
excludes: ['dist', '*.test.ts'],
includes: ['src/**/*.ts'],
});
| Option | Default | Description |
|---|
repoRoot | (required) | Root directory of the repository to search |
excludes | (see below) | Glob patterns to exclude |
includes | (all files) | Glob patterns to include (e.g., ['src/**/*.ts', 'lib/**/*.js']) |
name | codebase_search | Tool name exposed to the LLM |
description | (see SDK) | Tool description for the LLM |
remoteCommands | (local) | Functions for remote sandbox execution |
morphApiUrl | https://api.morphllm.com | Override API base URL |
timeout | 30000 | Timeout in ms (also via MORPH_WARP_GREP_TIMEOUT env var) |
Default Excludes
WarpGrep excludes common non-source directories by default:
- Dependencies:
node_modules, bower_components, .pnpm, .yarn, vendor, Pods, .bundle
- Build output:
dist, build, .next, .nuxt, out, target, .output
- Python:
__pycache__, .pytest_cache, .mypy_cache, .ruff_cache, .venv, venv, site-packages
- Version control:
.git, .svn, .hg
- Lock files, minified files, source maps, and common binary formats
Pass excludes to override these defaults. Your list replaces the defaults entirely — it does not merge with them.
Searching node_modules
By default, node_modules is excluded. To search inside dependencies (e.g., debugging a library or finding how a package implements something), pass an empty excludes list:
const warpGrepSubagent = morph.anthropic.createWarpGrepTool({
repoRoot: '.',
excludes: [],
});
WarpGrep runs faster with less context to search over. Only include node_modules in your search when you need it. See the node_modules example.
GitHub Search Options
createGitHubSearchTool() accepts:
| Option | Default | Description |
|---|
morphApiKey | MORPH_API_KEY env var | API key for Morph |
morphApiUrl | https://api.morphllm.com | Override API base URL |
codeSearchUrl | https://morphllm.com | Code storage service URL |
timeout | 30000 | Timeout in ms |
GitHub Search
Search public GitHub repositories. No local clone or ripgrep needed — Morph indexes the repo remotely. See the GitHub search example.
Client API
const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });
const result = await morph.warpGrep.searchGitHub({
searchTerm: 'Find authentication middleware',
github: 'vercel/next.js', // or full URL: 'https://github.com/vercel/next.js'
branch: 'canary', // optional, defaults to repo's default branch
});
if (result.success) {
for (const ctx of result.contexts) {
console.log(`${ctx.file}: ${ctx.content}`);
}
}
Each SDK adapter provides createGitHubSearchTool():
// Same pattern as createWarpGrepTool — just swap the factory:
const githubTool = morph.anthropic.createGitHubSearchTool();
// Or: morph.openai.createGitHubSearchTool()
// Or: morph.vercel.createGitHubSearchTool()
Usage is identical to the Quick Start above — pass githubTool in your tools array.
GitHub search returns the same WarpGrepResult format as local search.
Remote Execution (Sandboxes)
When your code lives in a remote sandbox (E2B, Modal, Daytona, Docker), provide three functions that execute commands remotely. The SDK handles all parsing.
const warpGrepSubagent = morph.anthropic.createWarpGrepTool({
repoRoot: '/home/user/repo',
remoteCommands: {
grep: async (pattern, path, glob) => {
const cmd = `rg --no-heading --line-number --color never -C 1 ${glob ? `--glob '${glob}'` : ''} '${pattern}' '${path}'`;
const r = await sandbox.run(cmd);
return r.stdout;
},
read: async (path, start, end) => {
const r = await sandbox.run(`sed -n '${start},${end}p' '${path}'`);
return r.stdout;
},
listDir: async (path, maxDepth) => {
const r = await sandbox.run(`find '${path}' -maxdepth ${maxDepth}`);
return r.stdout;
},
},
});
Replace sandbox.run(...) with your provider’s exec method (E2B’s sandbox.commands.run, Modal’s sandbox.exec, etc).
The SDK parses the raw output for you:
grep expects ripgrep format (path:line:content) with -C 1 context lines
read expects raw file content (SDK adds line numbers)
listDir expects one path per line (from find command)
Your sandbox needs ripgrep (rg) installed. Most providers support apt-get install ripgrep.
API Reference
Input (WarpGrepInput):
{
searchTerm: string, // Natural language search query
repoRoot: string, // Root directory to search
excludes?: string[], // Glob patterns to exclude
includes?: string[], // Glob patterns to include
streamSteps?: boolean, // Stream progress (see Streaming page)
provider?: WarpGrepProvider, // Custom file system provider (see Custom Providers page)
remoteCommands?: { // For sandbox environments
grep: (pattern, path, glob?) => Promise<string>,
read: (path, start, end) => Promise<string>,
listDir: (path, maxDepth) => Promise<string>,
},
}
Returns (WarpGrepResult):
{
success: boolean,
contexts?: Array<{
file: string, // File path relative to repo root
content: string, // Relevant code section
}>,
summary?: string, // Summary of findings
error?: string, // Error message if failed
}
Tool methods (when using as agent tool):
// Execute the tool with the LLM's input
const result = await warpGrepSubagent.execute(toolInput);
// Format the result as a string to send back to the LLM
// (converts the structured result into a readable text block)
const formatted = warpGrepSubagent.formatResult(result);
Error Handling
const result = await warpGrepSubagent.execute(toolUse.input);
if (!result.success) {
console.error(result.error);
// Common errors:
// - "Search did not complete" — the model did not call finish within 4 turns
// - "API error" — authentication or network issue
// - "timeout" — search took longer than the configured timeout
}
Type Exports
import {
warpGrepInputSchema,
executeToolCall,
WARP_GREP_TOOL_NAME,
WARP_GREP_DESCRIPTION,
} from '@morphllm/morphsdk/tools/warp-grep';
import type {
WarpGrepResult,
WarpGrepContext,
WarpGrepInput,
WarpGrepToolConfig,
GitHubSearchInput,
GitHubSearchToolConfig,
RemoteCommands,
} from '@morphllm/morphsdk';