What ships
internal/tools/builtin/ provides the five tools that every rousseau daemon wires up by default (see internal/cli/chat.go for the wiring):
| Tool | Purpose | Mutates? |
|---|---|---|
read |
UTF-8 text file read. | No |
write |
UTF-8 text file overwrite. Creates parents. | Yes |
edit |
Exact-string replacement, unique-match required. | Yes |
grep |
RE2 regex search under a directory. | No |
bash |
/bin/sh -c <cmd> with a timeout. |
Yes |
Each is registered via registry.MustRegister(builtin.NewXTool()). Register additional tools without touching the agent core — see Developer Guide: Add a Tool.
read
Read a UTF-8 text file from the local filesystem.
Input schema:
{
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute filesystem path to the file to read."
}
},
"required": ["path"]
}
Semantics:
pathmust be absolute; relative paths are rejected.- Rejects binary content via a
\x00sniff over the first 512 bytes. - Returns the file contents verbatim as a string.
Errors: missing path, relative path, unreadable file, non-text content.
write
Write UTF-8 text to a file, replacing existing contents. Creates parent directories as needed.
Input schema:
{
"type": "object",
"properties": {
"path": { "type": "string", "description": "Absolute filesystem path to write." },
"content": { "type": "string", "description": "The complete file contents to write." }
},
"required": ["path", "content"]
}
Semantics:
- Overwrites the file (not append). Use
editfor incremental changes. MkdirAll(dir, 0o755)on the parent directory.- File written with permission
0o644. - Returns
wrote <n> bytes to <path>on success.
Errors: missing path, relative path, mkdir failure, write failure.
edit
Exact-string replacement with a unique-match constraint. Borrowed from Claude Code's Edit tool.
Input schema:
{
"type": "object",
"properties": {
"path": { "type": "string", "description": "Absolute filesystem path to the file to edit." },
"old_string": { "type": "string", "description": "Exact text to find. Must be unique in the file." },
"new_string": { "type": "string", "description": "Text to replace old_string with." }
},
"required": ["path", "old_string", "new_string"]
}
Semantics:
old_stringmust appear exactly once in the file. Zero occurrences → error. Two or more → error (asks the model to provide more surrounding context).old_string == new_string→ error (no-op edits are rejected).- Preserves indentation and whitespace verbatim.
- Returns
edited <path> (1 replacement)on success.
The unique-match rule is deliberate: it prevents the model from performing accidental mass-replacement. When the model wants to change every occurrence, it has to author multiple edit calls, each with enough surrounding context to disambiguate.
Errors: missing / relative path, missing old_string, no match, non-unique match, identical strings, read / write failure.
grep
Regex search under a directory. Deliberately simpler than ripgrep — no dependency, runs in-process.
Input schema:
{
"type": "object",
"properties": {
"pattern": { "type": "string", "description": "Go RE2 regular expression to match." },
"path": { "type": "string", "description": "Absolute directory to search under." },
"include": { "type": "string", "description": "Optional filename glob (e.g. '*.go'). Applied to the base name." },
"ignore_case": { "type": "boolean", "description": "Case-insensitive match. Defaults to false." }
},
"required": ["pattern", "path"]
}
Semantics:
- Go RE2 syntax — no backreferences, no lookaround.
- Recursively walks
path. Skips.git,node_modules,vendor,.venv,__pycache__,dist,build. - Skips files larger than
MaxFileBytes(default 4 MiB) and binary content. - Caps output at
MaxMatches(default 200); truncation is annotated inline. - Returns
<path>:<line>: <matching-line>rows. - Returns the string
no matcheswhen nothing matched.
Errors: missing pattern / path, relative path, invalid regex, invalid include glob.
bash
Execute a shell command via /bin/sh -c. The load-bearing security boundary.
Input schema:
{
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The shell command to execute."
}
},
"required": ["command"]
}
Semantics:
- Runs under
/bin/sh -c <command>. Not bash-specific — POSIX shell. - Combined stdout+stderr is returned.
- Default timeout: 60 seconds. Configurable at registration via
NewBashTool(timeout). - Timeout returns a
bash: timed out after <duration>error along with any output produced before the deadline. - Non-zero exit yields an error whose string wraps the exit status; the output is still returned for the model to inspect.
Safety:
- The tool has no built-in allowlist. The Approver is the load-bearing gate. Always enable pattern-mode approval on unattended daemons.
- The command runs with the daemon's UID and filesystem visibility. Layer a rootless container underneath (Deployment).
Tool errors and the loop
When a tool returns an error, the agent converts it into a tool_result block with isError: true and feeds it back to the model on the next iteration:
[user] make the change
[assistant] tool_use: edit {"path": "/tmp/foo", "old_string": "x", "new_string": "y"}
[user] tool_result: "edit: old_string not found in /tmp/foo" (isError=true)
[assistant] I couldn't find "x" in /tmp/foo. Could you confirm the path?
This is the same channel used for approver denials — see Approval Policies.
Registering additional tools
registry := tools.NewRegistry()
registry.MustRegister(builtin.NewReadTool())
registry.MustRegister(builtin.NewWriteTool())
registry.MustRegister(builtin.NewEditTool())
registry.MustRegister(builtin.NewGrepTool(0, 0)) // zero → defaults
registry.MustRegister(builtin.NewBashTool(60 * time.Second))
registry.MustRegister(myCustomTool) // any tools.Tool
tools.Registry is concurrency-safe; registration is thread-safe.
Security implications at a glance
| Tool | Blast radius | When NOT to use |
|---|---|---|
read |
Read files with daemon's FS visibility. Can exfiltrate any readable file. | If any secret material is on disk in the workspace. Restrict via approver match regex. |
grep |
Same as read plus a regex CPU cost. | If matching untrusted patterns — ReDoS is possible with pathological regex. |
edit |
Modifies file contents in-place. | If the daemon's FS visibility extends beyond the intended workspace. Combine with a container bind mount. |
write |
Creates/overwrites files. | Same as edit, plus can create files anywhere the daemon can write. |
bash |
Arbitrary command execution. | On any unattended daemon without a pattern-mode approver. The primary security boundary. |
Troubleshooting
read: read /path: is a directory
The read tool is file-only. Use grep with a path pattern or bash (with ls) if you need directory contents.
edit: old_string not found
The model's proposed old_string did not match the file's contents byte-for-byte. Common causes: whitespace/newline drift, wrong line-ending style, the file was edited between the model's read and the edit call.
edit: old_string is not unique
Rousseau's edit tool refuses ambiguous edits — the model must include enough surrounding context to make old_string a unique substring. This prevents accidental multi-site replacement.
bash: timed out after 1m0s
Default 60s timeout. Long-running commands (build, test) will fail. Either raise the timeout with NewBashTool(2*time.Minute) when embedding, or split into faster steps.
grep returns nothing but the pattern is definitely there
Rousseau's grep uses Go's regexp package (RE2), which does not support all PCRE features. Backreferences and lookarounds will silently fail. Rewrite the pattern for RE2.
Related pages
- User Guide: Approval Policies — the gate on every tool call.
- Developer Guide: Add a Tool — build your own.
- Concepts — how tools fit into the agent loop.
- Agent loop — how tool results feed back into the next turn.
- Reference: Tool schemas — machine-readable schemas.
Further reading
internal/tools/builtin/read.go— file read with truncation.internal/tools/builtin/write.go— file write.internal/tools/builtin/edit.go— the unique-string constraint enforcer.internal/tools/builtin/grep.go— recursive regex search.internal/tools/builtin/bash.go—/bin/sh -cshell wrapper.