Swarmz

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:

ToolPurposeRead or write
read_fileRead a file (or a line range)Read
write_fileCreate or overwrite a fileWrite
list_filesWalk the project treeRead
search_codeRipgrep-backed regex searchRead
run_commandExecute a shell commandWrite

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:

  1. The sidecar saves the file under /workspace/.
  2. Vite's file watcher picks it up.
  3. HMR pushes the update into the preview iframe — usually under 200ms.
  4. The frontend emits a file_write event 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.34s

Common uses:

Command typeExample
Package managementnpm install, pnpm add zod
Build / testnpm run build, npm test
Databasesupabase db push, migration scripts
Gitgit status, git log, git diff
Inspectioncat, 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_command that 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-v3 auto-wakes the container, so this is rare mid-run.
  • Edit on unread lines. replace_lines rejects 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.

  • 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

On this page