Tools

August 27, 2026 ยท View on GitHub

Neo exposes a small built-in tool surface to the model.

ToolSurfaceDescription
agentInteractive chatSpawn a fresh subagent with a self-contained prompt. mode: "work" is writable and serial; mode: "inspect" is read-only and parallel-safe.
bashChat and headlessRun a shell command via /bin/bash -c. Returns bounded combined stdout and stderr, retaining the start and end when truncated.
edit_fileChat and headlessReplace exactly one occurrence of old_string with new_string. Fails if the old text is missing, appears more than once, or the file changed since the agent last read it. old_string matches the file's raw text, so the line-number gutter read_file adds must be stripped first.
globChat and headlessList workspace files matching a glob pattern, honouring .gitignore. Supports ** for recursive matches. Returns one path per line.
grepChat and headlessSearch the workspace with a regular expression, honouring .gitignore. Returns matching lines as path:line:text.
read_fileChat and headlessRead a file from disk, prefixing each line with its 1-indexed number and a tab so the numbers match what grep reports. Returns up to tools.MaxOutputBytes (64 KiB); use offset and limit to page through larger files.
workflowInteractive chatCreate or update the visible workflow checklist. Neo attaches tool and subagent activity to the active item automatically.
write_fileChat and headlessWrite content to a file, creating parent directories. Overwrites the file if it exists.

Independent inspect calls issued in one model response can run concurrently. Inspect children receive only read_file, grep, and glob.

Output size

tools.MaxOutputBytes (64 KiB, roughly 16k tokens) is the single limit on what one tool call contributes to the transcript. bash truncates its own output head-and-tail at that size so a failing command's trailing error survives; read_file refuses above it and asks the model to page; the agent applies the same cap as a backstop for any tool that does not bound itself. A single line longer than the limit cannot be read by read_file, since pagination cuts on line boundaries; use bash for that.

Stale edits

read_file records each file's modification time and size. edit_file refuses when they no longer match and tells the model to read the file again, which catches a change the model could not observe: the user saving in an editor, a git checkout, or a concurrent work-mode subagent. A file the agent has never read is not stale and edits normally โ€” the guard is for invisible changes, not for model error. write_file and edit_file re-record after writing so the agent's own writes are never mistaken for external ones.

tools.NewFileTools constructs read_file, write_file, and edit_file sharing one record. Build a fresh set per agent so a subagent's reads never satisfy the coordinator's edits. The check is a stat comparison, not a lock: a writer racing between the check and the write still wins.

grep and glob shell out to ripgrep, which honours .gitignore, skips binaries, and is far faster than a hand-rolled walk. --no-require-git is passed so ignore rules apply whether or not the workspace is a git checkout.

Hidden files are searched (--hidden), because .github/workflows and the like are ordinary project files; .git/ is excluded explicitly, since --no-require-git turns off ripgrep's own handling of it.

Output is read incrementally and ripgrep is stopped once max_matches lines have been collected. A broad pattern over a large workspace can produce far more than will ever be returned, and buffering it first would let a model-chosen pattern decide how much memory Neo uses. The consequence is that truncation is reported without a total: counting the rest means reading it.

max_matches bounds output lines, not matches. With context_lines set, the context counts toward it.

If rg is not on PATH, both tools return an error naming the missing dependency. It deliberately does not tell the model to use bash: the coordinator will reach for it anyway, and an inspect subagent has no shell, so suggesting one would only waste a turn. Inspect subagents therefore lose search entirely on a machine without ripgrep and will say so.

There is no Go fallback: a second implementation would mean two sets of ignore rules and two output shapes. neo doctor reports ripgrep as a warning, not a failure, for the same reason.

They stay separate tools rather than folding into bash because they are classified parallel-safe, which bash cannot be without interpreting shell commands, and because inspect-mode subagents need read-only search without a shell.

Search applies no path confinement of its own, matching read_file, write_file, and edit_file. The sandbox is the boundary.

Cancelling a search kills the ripgrep process and returns an error with no partial output.

Execution and confirmations

Neo relies on its VM or sandbox for security boundaries. Interactive users can set tool_approvals to confirm exact tool names or Bash command prefixes. The list is empty by default and is not applied to headless or child agents.