Skip to content

Tools · Execution

The workspace and shell tools. File operations route through the workspace executor; shell commands run inside the project container. See Tool Reference for how to read these signatures.

WorkspaceTools

Group workspace. All five tools are tier Standard — file I/O stays inside the workspace, carrying no network or escalation risk.

Every path argument is normalized before it reaches the container: surrounding whitespace is trimmed, a path written against the container's workspace root has that prefix removed, and a leading relative workspace/ segment is stripped too, so a file does not land one directory deeper than the plan asked for.

read_file, write_file, edit_file and revert_file also accept file_path as another name for path, and write_file accepts text for content. The Engine renames them when it parses the call, before any guard sees the arguments; when both names are present, path and content win.

read_file

read_file(path, start_line?, end_line?) → file contents

Param Type Req Description
path string yes File path relative to workspace root
start_line int no 1-based first line to read (default 1)
end_line int no 1-based last line to read (default: the last line; past the end means the last line)

Returns the raw file contents (or an Error… string). With a range it returns only those lines, each prefixed with its number (990| …) under a [path: lines A-B of N] header; lines are counted the way edit_file counts them, so a range read here is the range edit_file's start_line/end_line replace. A start past the end, or an end before the start, is an error that states the line count. A ranged read is never offloaded to .tool-output/; a whole-file read too large for the context still is, and its notice gives the file's line count and how to read a range of it. A path whose extension names an image, audio, video, document, spreadsheet, slide-deck or archive format is refused before it is read, and the refusal names list_files on its folder as the check that works; SVG, HTML and scripts are text and read normally. Description steers the agent away from calling it before edit_file/code_review, which read internally.

write_file

write_file(path, content) → status string

Param Type Req Description
path string yes File path relative to workspace root
content string yes Full file content to write

Creates or overwrites. Captures a best-effort pre-write snapshot for revert_file.

Four guards sit in front of the write and can refuse it. They exist because a rewrite that damages a file is indistinguishable from a good one downstream — the step still reports that a productive tool fired:

  • Shrink guard — refuses a write that would discard most of an existing file. Retention is measured as the share of the existing content, by character, that survives; below 0.5 the write is refused. Files under 400 bytes are not protected.
  • Reference guard — refuses an HTML write that would leave a sibling script reaching for element ids the new markup no longer declares. Only regressions count: an id already missing before the write is not attributed to it, and only literal getElementById('…') / querySelector('#…') lookups are checked, so a computed id is never reported.

  • Binary-path guard — refuses text at a path whose extension names a binary format (images, archives, audio and video, fonts, compiled objects, SQLite files, and the 3D scenes and engine assets .blend, .glb, .usdz, .3ds, .umap and .uasset). The refusal names the tool that does produce that file instead: comfyui_generate or capture_reference for an image, download_pdf for a PDF; for a 3D file it says that nothing in the workspace makes one and that a stand-in under its name is not the file. .fbx stays writable, because the format has an ASCII form; the required-output gate reads its header instead. It is judged before the snapshot, because there is nothing to snapshot for a write that must not happen, and it applies to edit_file on the same terms.

  • Escaped-fence guard — refuses a Markdown write (.md, .mdx, .markdown; the write_file content or the edit_file replacement) in which a line opens with a backslash-escaped code fence, \`\`\`, which renders as literal backslashes instead of a code block. It refuses twice for the same path, naming the first such line; a third attempt has the escapes repaired and goes through. Logged as [ESCAPED-FENCE].

The shrink and reference guards refuse once, naming what would be lost and pointing at edit_file; if the agent asks again for the same path the write is allowed, so a deliberate rewrite is never blocked. Both fail closed — if the check itself errors, the write is refused with the error, and that refusal does not count as the first ask. Refusals are logged as [SHRINK-GUARD] and [REF-GUARD]. The binary-path guard has no second ask: a text tool never produces those formats. The shrink guard also stands aside for a file the step owes at a words range (a script such as show/episode-01-script.md words 400-520): every draft of such a file is new prose, so retention would read as a rewrite on any cut, and the required-output gate already governs its length. That exemption is per path, so a page owed beside the script is still protected, and the skip is logged.

In a sequential team one more rule applies to write_file, edit_file and revert_file: a stage may not change a file another stage owes (that stage's required_output) unless it owes the file too. The refusal names the stage that owns the file and what this stage owes, and a fourth attempt ends the turn; refusals are logged as [STAGE-LANE]. Files no stage owes are not affected, and a checking stage keeps its own narrower rule (it writes only its report).

An agent that owes files also has a write phase: from eight rounds before the end of its tool-round budget (round 5 at the earliest), or earlier when it tries to stop with files still owed, its research tools are withdrawn and it is told which files it owes. From then on, a write_file or edit_file that lands none of them while none is on disk yet comes back with "That is not the file you owe" and the list still to write. Before the write phase a note or a helper file passes without comment. A repair turn whose owed files are all on disk already has no write phase and keeps its tools.

When the turn is about to end at its round cap, after eight read-only calls in a row, or on the same-tool streak or duplicate-call exit, while a file is still owed or the turn is a repair, it gets a final write phase instead of a closing reply: two more calls in which only its write tools run (write_file, edit_file, and the tool that makes an owed binary), told which paths it owes and, in a repair, why the turn was opened. Any other tool is refused. Checkers, re-plan turns and agents that owe nothing end as before.

edit_file

edit_file(path, search, replace, start_line?, end_line?) → status string

Param Type Req Description
path string yes File path to edit
search string yes Text to find. Exact match preferred; whitespace differences are tolerated. Empty to create a missing file or when using a line range
replace string yes Text to replace with (the whole file content when creating or replacing the whole file)
start_line int no 1-based first line of a range
end_line int no 1-based last line of a range (omitted: the last line of the file when search is non-empty, so a search from start_line looks to the end; start_line when search is empty, where the range is the edit; past the end of the file means the last line). The agent-facing parameter description still reads "defaults to start_line"

Matching is exact first, then whitespace-tolerant (line by line, ignoring indentation and spacing) when that matches exactly one place. With an empty search the tool creates the file if it does not exist; with a range it replaces those lines (empty search) or confines the search to them. A range from line 1 to the last line with an empty search is the sanctioned whole-file rewrite: an end_line past the end is clamped to the last line (the result says so), and because a line range states explicitly what is replaced, it is not put through the write_file shrink guard. A search that matches more than once is replaced everywhere, except when the inserted material would outweigh the text being edited (the replacement's length times the match count exceeds the file's, or the range's): that edit is refused, because a short search times a document-sized replacement is the document repeated, not an edit. The same refusal applies to a search that matches once when the replace opens with the file's own first line and runs to more than four times the lines of the search — a title line swapped for a whole regenerated document would leave the file holding the text twice, so the message names the whole-file form instead. A miss reports the nearest line so the agent can correct the search text without a re-read; when the failed search is at least half the file long, or the range is out of bounds, the message also spells out the whole-file form. The description tells the agent not to read_file first because the tool reads the file itself. The router accepts find as a legacy alias for search, and reads start_line/end_line given as numeric strings. Captures a pre-edit snapshot.

search_files

search_files(query?, path?, glob?, regex?, max_results?) → matches or file list

Param Type Req Default Description
query string no — Text (or regex when regex=true) to find in file contents; omit to only list files
path string no root Directory to search, relative to the workspace root
glob string no every file File name pattern such as *.py or docs/**/*.md
regex bool no false Treat query as a regular expression
max_results int no 200 Maximum matching lines or files to return

Read-only, output capped. With a query it returns matching lines as path:line: text grouped by file; without one it lists the files matching the glob. Runs find/grep inside the container, or walks the host filesystem when containers are off. Its description tells the agent to use it instead of run_shell with ls, find or grep.

list_files

list_files(path?, depth?) → directory tree

Param Type Req Default Description
path string no . (root) Directory path relative to workspace root
depth int no 3 Max directory depth to list

Returns a tree view. Description discourages calling it before write_file.

revert_file

revert_file(path) → result string

Param Type Req Description
path string yes Same path passed to the original write_file/edit_file

Restores the file to its pre-edit snapshot; deletes it if the file did not exist before the first write. Snapshots are taken on the first write to a path within a build run and are scoped to that run. Fails if no snapshot exists — including for a file that existed but could not be read when the write happened, which deliberately gets no snapshot rather than an error message stored as its content.

ShellTools

Group shell. Both tools are tier Elevated. run_shell additionally forces human approval (no supervisor fallback) when the command matches a destructive pattern — see tiers.

run_shell

run_shell(command, background?) → combined output

Param Type Req Default Description
command string yes — Shell command to execute
background bool no false Run in background and return immediately (for dev servers)

Runs through the container executor, not the host shell. Returns combined stdout with stderr and exit code folded in (\nSTDERR:\n…, \nExit code: N). With background: true the command is wrapped as nohup <command> > /workspace/.server.log 2>&1 &, which can start only one plain executable, so a cd/export/source prefix, &&, ||, ;, a newline, a trailing & or a leading VAR= is refused without running, and the error names the working form. Wrap a compound command as sh -c '<script>'. Errors if there is no container context. Every invocation is recorded to the container_command_logs table.

read_logs

read_logs(source, target, lines?, filter?) → Markdown log block

Param Type Req Default Description
source string yes — docker, systemd, or file
target string yes — Container name, systemd service name, or log file path
lines int no 100 (clamped 1–2000) Number of lines to return
filter string no — Optional grep pattern to filter lines

Returns a fenced log block (## Docker logs: / ## Systemd logs: / ## File log:) or an Error… string for an unknown source or missing target.

With source: "file" the target is a container path, and nothing on the Engine's host is opened. A path under /workspace, or a relative one, is read the way read_file reads it; any other absolute path (a background server's /tmp/server.log) is read inside the team container, and is refused when there is no container. A file too large to read, or one that cannot be read, returns an error pointing at run_shell with tail. The header says how much of the file it shows (last N of M lines).

Where to go next