# OpenCode Tool System Architecture: How grep, glob, read, write, edit, and lsp Work Together

> Explore OpenCode's tool system architecture learn how grep, glob, read, write, edit, and lsp work together securely using a modular, permission-aware design with Tool.define, ToolRegistry, and Tool.Context.

- Repository: [Anomaly/opencode](https://github.com/anomalyco/opencode)
- Tags: architecture
- Published: 2026-02-16

---

**OpenCode's tool system uses a modular, permission-aware architecture built around `Tool.define`, `ToolRegistry`, and `Tool.Context` to safely execute filesystem and LSP operations.**

The `anomalyco/opencode` repository implements a plug-in-style tool layer that enables AI agents to interact with the filesystem, search codebases, edit files, and query Language Server Protocol (LSP) services. Understanding this OpenCode tool system architecture is essential for extending the platform or integrating custom tooling.

## Core Architecture Components

### Tool Definition with Tool.define

At the heart of the system is `Tool.define` in [`packages/opencode/src/tool/tool.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/tool.ts) (lines 48-84). This function creates type-safe tool definitions using Zod for parameter validation:

```typescript
export function define<Parameters extends z.ZodType, Result extends Metadata>(
  id: string,
  init: Info<Parameters, Result>["init"] | Awaited<ReturnType<Info<Parameters, Result>["init"]>>,
): Info<Parameters, Result> { … }

```

The `define` function automatically validates arguments using `toolInfo.parameters.parse` before execution and wraps results with output truncation via `Truncate.output`. Each tool returns a `Tool.Info` object containing the tool's schema, description, and execution logic.

### Tool Registry and Discovery

The `ToolRegistry` in [`packages/opencode/src/tool/registry.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/registry.ts) (lines 31-119) handles discovery and registration of both built-in and plugin tools. The registry assembles the final tool list in a specific order:

```typescript
[
  InvalidTool,
  ...(client === "cli" ? [QuestionTool] : []),
  BashTool,
  ReadTool,
  GlobTool,
  GrepTool,
  EditTool,
  WriteTool,
  TaskTool,
  WebFetchTool,
  TodoWriteTool,
  WebSearchTool,
  CodeSearchTool,
  SkillTool,
  ApplyPatchTool,
  ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []),
  ...(config.experimental?.batch_tool ? [BatchTool] : []),
  ...custom // plugins & user-added tools
]

```

The registry also scans for custom tools in `tool/*.ts` or `tools/*.ts` files within user-configured directories and loads plugin tools via `Plugin.list()`.

### Tool Context and Permissions

The `Tool.Context` type in [`packages/opencode/src/tool/tool.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/tool.ts) (lines 16-27) carries session data and permission handling:

```typescript
export type Context<M extends Metadata = Metadata> = {
  sessionID: string;
  messageID: string;
  agent: string;
  abort: AbortSignal;
  extra?: { [key: string]: any };
  messages: MessageV2.WithParts[];
  metadata(input: { title?: string; metadata?: M }): void;
  ask(input: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">): Promise<void>;
};

```

The `ctx.ask` method triggers permission requests (e.g., `"read"`, `"glob"`, `"edit"`, `"lsp"`) before executing sensitive operations. The frontend or test harness can grant or deny these requests, protecting the repository from unwanted modifications.

## Built-in Tools Deep Dive

### glob - File System Pattern Matching

The `glob` tool in [`packages/opencode/src/tool/glob.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/glob.ts) returns up to 100 matching file paths ordered by modification time:

```typescript
export const GlobTool = Tool.define("glob", {
  description: DESCRIPTION,
  parameters: z.object({
    pattern: z.string().describe("The glob pattern to match files against"),
    path: z.string().optional().describe("Directory to search; defaults to cwd."),
  }),
  async execute(params, ctx) { … }
});

```

Execution flow:
1. Calls `ctx.ask` for `"glob"` permission
2. Resolves the search directory via `Instance.directory` to absolute path
3. Uses `Ripgrep.files` (fast Rust-based file enumerator) to stream matching paths
4. Collects up to 100 entries, sorts by `mtime`, and returns the list

Key implementation details are found in lines 38-56 for `Ripgrep.files` iteration and lines 43-48 for truncation handling.

### grep - Content Search with Ripgrep

The `grep` tool in [`packages/opencode/src/tool/grep.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/grep.ts) searches file contents using regular expressions:

```typescript
export const GrepTool = Tool.define("grep", {
  description: DESCRIPTION,
  parameters: z.object({
    pattern: z.string(),
    path: z.string().optional(),
    include: z.string().optional(),
  }),
  async execute(params, ctx) { … }
});

```

Core steps:
1. Permission request (`"grep"`)
2. Resolve search directory
3. Build `rg` command with `--regexp`, `--glob` (if provided), and `--field-match-separator=|`
4. Spawn process with `Bun.spawn`, capture stdout/stderr
5. Parse lines of format `file|line|text`
6. Sort by modification time, truncate to 100 matches, format human-readable output

Key code appears in lines 46-50 for process spawning and lines 77-96 for match parsing.

### read - Safe File and Directory Reading

The `read` tool in [`packages/opencode/src/tool/read.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/read.ts) handles file and directory reading with safety limits:

```typescript
export const ReadTool = Tool.define("read", {
  description: DESCRIPTION,
  parameters: z.object({
    filePath: z.string(),
    offset: z.coerce.number().optional(),
    limit: z.coerce.number().optional(),
  }),
  async execute(params, ctx) { … }
});

```

Highlights:
- Resolves relative paths against `Instance.directory`
- Calls `ctx.ask` for `"read"` permission
- For directories: lists entries with pagination (`offset`, `limit`)
- For files: detects images/PDFs (returns data-URI) and binary files (throws error)
- Enforces line count (`DEFAULT_READ_LIMIT`), line length (`MAX_LINE_LENGTH`), and byte budget (`MAX_BYTES`)
- Updates LSP and file-time caches via `LSP.touchFile` and `FileTime.read`

Binary detection logic appears in `isBinaryFile` function (lines 206-260), with result construction in lines 90-104.

### write - File Writing with Diff Generation

The `write` tool in [`packages/opencode/src/tool/write.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/write.ts) overwrites files with preview capabilities:

```typescript
export const WriteTool = Tool.define("write", {
  description: DESCRIPTION,
  parameters: z.object({
    content: z.string(),
    filePath: z.string(),
  }),
  async execute(params, ctx) { … }
});

```

Execution flow:
1. Resolve absolute path, verify external directory
2. Compute unified diff via `diff.createTwoFilesPatch` → `trimDiff`
3. Permission request `"edit"` with diff metadata
4. Write file using `Bun.write`, publish `File.Event.Edited` and `FileWatcher.Event.Updated`
5. Trigger LSP via `LSP.touchFile`, fetch diagnostics via `LSP.diagnostics`
6. Format diagnostic snippets (max 20 per file, up to 5 files), embed in output

Key code includes diff generation (lines 34-35), permission request (lines 35-43), and diagnostics handling (lines 60-73).

### edit - Batch File Modifications

The `edit` tool in [`packages/opencode/src/tool/edit.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/edit.ts) provides a higher-level batch edit capability, wrapping the `write` tool functionality to handle multi-file modifications used by the **Apply Patch** tool.

### lsp - Language Server Protocol Integration

The `lsp` tool in [`packages/opencode/src/tool/lsp.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/lsp.ts) executes LSP operations:

```typescript
export const LspTool = Tool.define("lsp", {
  description: DESCRIPTION,
  parameters: z.object({
    operation: z.enum(operations),
    filePath: z.string(),
    line: z.number().int().min(1),
    character: z.number().int().min(1),
  }),
  async execute(args, ctx) { … }
});

```

Flow:
1. Resolve file path, verify external directory
2. Permission request `"lsp"` (wildcard `*` pattern)
3. Convert to `file://` URL and zero-based LSP position
4. Ensure LSP client attached via `LSP.hasClients`
5. Call appropriate LSP method (`definition`, `references`, etc.)
6. Return JSON-formatted results or "no results" message

Dispatch switch appears in lines 62-82, with result handling in lines 85-88.

## Tool Registration and Discovery Flow

The `ToolRegistry` in [`packages/opencode/src/tool/registry.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/registry.ts) orchestrates how tools become available to the AI model. The registry assembles tools in a specific priority order:

1. **Built-in tools**: Core utilities like `BashTool`, `ReadTool`, `GlobTool`, `GrepTool`, `EditTool`, `WriteTool`, and conditionally `LspTool` (when `Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL` is enabled)
2. **CLI-specific tools**: `QuestionTool` (only when `client === "cli"`)
3. **Experimental tools**: `BatchTool` (when `config.experimental?.batch_tool` is true)
4. **Custom tools**: Plugin tools from `Plugin.list()` and user-defined tools discovered in `tool/*.ts` or `tools/*.ts` files

The `ToolRegistry.state` method (lines 34-48) handles dynamic discovery by scanning configured directories for TypeScript tool definitions and loading them at runtime.

## Permission Model and Security

Every tool in OpenCode invokes `ctx.ask` before performing I/O operations, creating a security boundary between the AI agent and the filesystem. The permission request structure includes:

- **permission**: The operation type (`"read"`, `"glob"`, `"grep"`, `"edit"`, `"write"`, `"lsp"`)
- **patterns**: File globs the tool wants to access (e.g., `[filepath]` or `["*"]`)
- **always**: Patterns always allowed (typically `["*"]` for read-only tools)
- **metadata**: Optional diagnostic data for UI rendering (such as diffs for write operations)

The frontend or test harness evaluates these requests and grants or denies permissions, ensuring the AI cannot modify files without explicit user consent. This model applies consistently across all six core tools: `glob`, `grep`, `read`, `write`, `edit`, and `lsp`.

## Practical Code Examples

### Listing Available Tools

To retrieve the complete list of registered tools for a specific model and client:

```typescript
import { ToolRegistry } from "@opencode-ai/opencode";

const modelInfo = { providerID: "opencode", modelID: "gpt-4" };
const tools = await ToolRegistry.tools(modelInfo, { name: "cli-agent" });

console.log(tools.map(t => `${t.id}: ${t.description}`));

```

This demonstrates how `ToolRegistry.tools` (lines 33-64) filters tools based on model capabilities and experimental flags.

### Executing a Glob Search

Programmatically using the `glob` tool to find TypeScript files:

```typescript
import { ToolRegistry } from "@opencode-ai/opencode";

async function runGlob(pattern: string) {
  const tools = await ToolRegistry.all();
  const GlobTool = tools.find(t => t.id === "glob");
  const ctx = {
    sessionID: "session-123",
    messageID: "msg-456",
    agent: "test-agent",
    abort: new AbortController().signal,
    messages: [],
    metadata: () => {},
    ask: async () => {} // Auto-approve for testing
  };
  
  const result = await GlobTool.init().execute({ pattern }, ctx);
  console.log(result.output);
}

runGlob("**/*.ts");

```

This example shows the `execute` method receiving validated arguments and a context object, returning `{title, output, metadata}`.

### Performing LSP Definition Lookups

Querying language server definitions programmatically:

```typescript
import { ToolRegistry } from "@opencode-ai/opencode";

async function definitionAt(file: string, line: number, ch: number) {
  const tools = await ToolRegistry.all();
  const lsp = tools.find(t => t.id === "lsp");
  const ctx = /* construct Tool.Context with ask() implementation */;
  
  const result = await lsp.init().execute(
    { operation: "goToDefinition", filePath: file, line, character: ch },
    ctx
  );
  console.log(JSON.parse(result.output));
}

```

This demonstrates the LSP tool's dispatch mechanism (lines 62-82 in [`lsp.ts`](https://github.com/anomalyco/opencode/blob/main/lsp.ts)) and JSON result formatting.

### Writing Files with Diff Preview

Creating files with automatic diff generation and diagnostics:

```typescript
import { ToolRegistry } from "@opencode-ai/opencode";

async function writeFile(path: string, newContent: string) {
  const tools = await ToolRegistry.all();
  const write = tools.find(t => t.id === "write");
  const ctx = /* Tool.Context with ask() for "edit" permission */;
  
  const res = await write.init().execute({ filePath: path, content: newContent }, ctx);
  console.log(res.output); // includes unified diff and LSP diagnostics
}

```

This showcases the diff generation via `diff.createTwoFilesPatch` (lines 34-35) and diagnostic fetching via `LSP.diagnostics` (lines 60-73).

## Summary

- **OpenCode's tool system** is built on three core concepts: `Tool.define` for schema validation, `ToolRegistry` for discovery and registration, and `Tool.Context` for permission handling.
- **Six built-in tools**—`glob`, `grep`, `read`, `write`, `edit`, and `lsp`—provide filesystem access and code intelligence through a unified interface.
- **Security is enforced** via the `ctx.ask` permission model, requiring explicit approval for read, write, and LSP operations before execution.
- **Extensibility** is supported through dynamic discovery of custom tools in `tool/*.ts` files and plugin integration via `Plugin.list()`.
- **Output handling** is standardized through automatic truncation (`Truncate.output`) and rich metadata support for UI rendering.

## Frequently Asked Questions

### What is the OpenCode tool system architecture?

The OpenCode tool system architecture consists of a three-layer framework: the definition layer (`Tool.define` in [`packages/opencode/src/tool/tool.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/tool.ts)) handles schema validation and execution logic; the registry layer (`ToolRegistry` in [`packages/opencode/src/tool/registry.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/registry.ts)) manages tool discovery and model-specific filtering; and the context layer (`Tool.Context`) provides session management and permission controls. This architecture supports six core tools—`glob`, `grep`, `read`, `write`, `edit`, and `lsp`—through a unified, extensible interface.

### How does OpenCode handle permissions for tool execution?

OpenCode implements a mandatory permission system where every tool invokes `ctx.ask` before performing I/O operations. The permission request includes the operation type (e.g., `"read"`, `"edit"`, `"lsp"`), file patterns being accessed, and optional metadata such as diffs for write operations. The frontend or test harness evaluates these requests and grants or denies access, ensuring the AI cannot modify files without explicit user consent. This model applies consistently across all tools in `packages/opencode/src/tool/`.

### What is the difference between write and edit tools in OpenCode?

The `write` tool in [`packages/opencode/src/tool/write.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/write.ts) overwrites entire files, generating unified diffs via `diff.createTwoFilesPatch` and triggering LSP diagnostics after writing. The `edit` tool in [`packages/opencode/src/tool/edit.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/edit.ts) provides a higher-level batch editing capability that wraps the `write` functionality, enabling multi-file modifications typically used by the **Apply Patch** tool. While `write` handles single-file atomic replacements, `edit` orchestrates complex, multi-file refactoring operations.

### How does OpenCode integrate LSP functionality into its tool system?

OpenCode integrates LSP through the `lsp` tool in [`packages/opencode/src/tool/lsp.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/tool/lsp.ts), which executes Language Server Protocol operations including `goToDefinition`, `references`, `hover`, and symbol queries. The tool converts file paths to `file://` URLs and adjusts to zero-based LSP positions before calling methods on attached LSP clients via `LSP.hasClients`. Results are returned as JSON-formatted data. The LSP integration is gated behind the `Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL` flag in the registry and requires `"lsp"` permission via `ctx.ask` before execution.