File Operations
The read, write, search, and shell tools the agent uses to manipulate your project
The agent doesn't write code by emitting characters into a chat window. It calls tools — discrete, sandboxed operations that read or modify files in your container. Every tool call is visible in the timeline, every result is bounded, and every failure is recoverable.
This page documents the tools you'll see most often, what they actually do, and what happens when they go wrong.
The tools
The agent has a small, opinionated tool set. Fewer tools = better tool selection. The core five:
| Tool | Purpose | Read or write |
|---|---|---|
read_file | Read a file (or a line range) | Read |
write_file | Create or overwrite a file | Write |
list_files | Walk the project tree | Read |
search_code | Ripgrep-backed regex search | Read |
run_command | Execute a shell command | Write |
There are auxiliary tools (replace_lines for surgical edits, file_overview for fast structural scans, rename_file, delete_file, npm_install) but if you understand these five you understand the model.
All tools execute against the workspace sidecar — a Hono service running inside your container at port 9000. The agent never touches your local machine, only the container's /workspace directory. See Cloud Overview for the container model.
read_file
Reads up to 300 lines per call, with line numbers prepended for context. The agent uses this constantly during orientation.
read_file({ path: "src/pages/Settings.tsx", start_line: 1, end_line: 80 })
→ [src/pages/Settings.tsx lines 1-80 of 412]
1| import { useState } from "react";
2| import { useAuth } from "@/hooks/useAuth";
...For files larger than 300 lines, the agent reads in chunks — first the top, then targeted ranges based on what it found. Tool output is hard-capped at roughly 50K characters per response, so very large files always come through the chunking path.
The pipeline tracks every line range that's been read. When the agent later calls replace_lines to do a surgical edit, the lines must have been previously read — otherwise the call is rejected with a hint to read first. This prevents hallucinated edits.
write_file
Creates the file if it doesn't exist, overwrites it if it does. There is no patch mode for write_file — the content you pass is the entire new file.
write_file({ path: "src/components/LoginForm.tsx", content: "..." })
→ ✓ wrote src/components/LoginForm.tsx (47 lines)What happens after a write:
- The sidecar saves the file under
/workspace/. - Vite's file watcher picks it up.
- HMR pushes the update into the preview iframe — usually under 200ms.
- The frontend emits a
file_writeevent over SSE; the file tree refreshes.
For edits to large files, the agent prefers replace_lines (surgical, only the changed lines) over write_file (full rewrite). Surgical edits are cheaper, faster, and don't risk dropping unchanged code.
JSX and CSS get extra validation — unbalanced braces produce a warning that gets fed back to the agent so it can self-correct. CSS is sanitized to strip common LLM hallucinations like fake @import directives.
The agent also recognizes // keep existing code markers. If a write_file content includes that line, the existing file gets diffed and only the marked sections are preserved — handy for partial rewrites the model wants to express compactly.
list_files
Returns the full file tree under /workspace, respecting .gitignore. The agent rarely calls this directly — the project map is already in its system prompt at startup. It's mostly used after operations that change file layout (like npm install adding a node_modules peer or a build step generating files).
Excluded by default: node_modules, .git, dist, .next, .swarmz, and shadcn UI primitives under components/ui/.
search_code
Ripgrep-backed text search. Supports literal strings and regex.
search_code({ pattern: "useAuth", file_pattern: "*.tsx" })
→ src/hooks/useAuth.ts:1:export function useAuth() {
src/pages/Login.tsx:4:import { useAuth } from "@/hooks/useAuth";
src/components/Header.tsx:6: const { user } = useAuth();Results are capped at 15 matches and 8-second timeout per search. The agent uses search_code heavily during orientation — finding all callers of a hook, locating a component by partial name, tracing imports.
You'll occasionally see the agent run several searches in parallel. Read-only tools (read, search, list) are batched concurrently when the model returns multiple tool calls in the same turn. Writes always run sequentially.
run_command
Full shell access inside the container. 30-second default timeout (extendable to 120s for installs). Output is captured up to 5MB.
run_command({ command: "npm run build" })
→ vite v5.0.0 building for production...
✓ 142 modules transformed.
dist/index.html 0.46 kB │ gzip: 0.30 kB
✓ built in 2.34sCommon uses:
| Command type | Example |
|---|---|
| Package management | npm install, pnpm add zod |
| Build / test | npm run build, npm test |
| Database | supabase db push, migration scripts |
| Git | git status, git log, git diff |
| Inspection | cat, wc -l, ls, tree |
run_command is privileged — every invocation gets surfaced in the timeline so you can see exactly what ran. The agent is trained to prefer specific tools (npm_install over raw npm install, read_file over cat) but falls back to the shell for anything not covered.
How tool calls show up in chat
Every tool invocation is rendered as a card in the timeline:
┌─ Reading Settings.tsx ─────────────────────────────┐
│ Need to see the current tab structure │
│ ✓ done · 142ms │
└────────────────────────────────────────────────────┘
┌─ Searching "useAuth" ──────────────────────────────┐
│ Find all callers before changing the signature │
│ ✓ done · 287ms · 4 matches │
└────────────────────────────────────────────────────┘
┌─ Editing Settings.tsx ─────────────────────────────┐
│ Replacing tabs with sub-tabbed structure │
│ ✓ done · 89ms · 412 → 438 lines │
└────────────────────────────────────────────────────┘Each card shows the verb (Reading, Searching, Editing, Running...), the target (filename or query), the agent's one-line reason, status, and duration. Click into any card to see the full input and output — useful for debugging when something didn't go as expected.
Tool failures
Tools return errors as strings, not exceptions. This is a deliberate design choice: the agent can read the error and decide what to do, rather than crashing the whole run.
What happens on common failures:
- File not found. The agent gets back
Error: File not found: src/Foo.tsx. HINT: Use grep_search to find the correct path.and typically follows the hint. - Tool timeout. A
run_commandthat exceeds 30s returns a partial result with[TIMEOUT]appended. The agent often retries with a more constrained command. - Sidecar unreachable. If the container is asleep or restarting, the request fails fast. The pre-flight in
ai-chat-v3auto-wakes the container, so this is rare mid-run. - Edit on unread lines.
replace_linesrejects edits to lines the agent hasn't read. The error includes the missing range so the agent reads them and retries.
The agent retries automatically up to ~3 times for transient failures. After that, it either falls back to a different tool (e.g. switches from replace_lines to write_file) or, if it's truly stuck, calls done with an honest summary of what it could and couldn't do — no silent failures.
If you see the agent retrying the same tool with the same input three times, that's a signal something's wrong with the container, not the prompt. Check container status in the editor's status bar — a quick restart usually clears it.
Related
- Overview — the streaming loop and event types
- Plan Mode — review before any of these tools run
- Prompting Tips — get the agent to invoke the right tools