Built-in Tools
yoagent ships with six coding-oriented tools for native hosts (the default native feature; not available on wasm32). Get them all with default_tools():
#![allow(unused)] fn main() { use yoagent::tools::default_tools; let tools = default_tools(); }
BashTool
Execute shell commands with timeout and output capture.
- Name:
bash - Parameters:
command(string, required)
Configuration
#![allow(unused)] fn main() { pub struct BashTool { pub cwd: Option<String>, // Working directory pub timeout: Duration, // Default: 120s pub max_output_bytes: usize, // Default: 256KB, per stream (stdout and stderr each) pub deny_patterns: Vec<String>, // Blocked commands pub env_allowlist: Option<Vec<String>>, // Default: None (inherit the whole environment) pub confirm_fn: Option<ConfirmFn>, // Confirmation callback } }
Default deny patterns: rm -rf /, rm -rf /*, mkfs, dd if=, fork bomb. These are substring
guardrails against typos, not a security control.
By default a command inherits the agent's whole environment, including any *_API_KEY. Set
env_allowlist to pass only the listed variables (plus PATH, HOME, PWD if present).
Output beyond max_output_bytes is drained and discarded, so memory stays bounded. A timeout
returns an error carrying the output so far. On Unix a timeout or cancel kills the whole process
group (pipeline stages, && lists, background jobs; only a process that starts its own session
escapes); elsewhere it kills the bash process only.
A command that finishes on its own leaves its background jobs running (a server it started, say),
even if the call is cancelled after bash exits. The call ends when bash exits: the output is
read for one more second, and a job that still holds the output pipes then is left running. What
it prints from then on is read and discarded in the background (so a closed pipe does not kill
it; a job that prints without pause keeps that reading busy), and the result says so with a
NOTE: line and details.output_still_open: true. Redirect a job's output (cmd >log 2>&1 &)
to keep it. Before 0.25.8 such a job held the call until the timeout, which then killed it.
Example
#![allow(unused)] fn main() { let bash = BashTool::default(); // Or customize: let bash = BashTool { cwd: Some("/workspace".into()), timeout: Duration::from_secs(60), ..Default::default() }; }
ReadFileTool
Read file contents with optional line range.
- Name:
read_file - Parameters:
path(required),offset(optional, 1-indexed line),limit(optional, number of lines)
Configuration
#![allow(unused)] fn main() { pub struct ReadFileTool { pub max_bytes: usize, // Default: 1MB pub allowed_paths: Vec<String>, // Path restrictions (empty = no restriction) pub max_lines: usize, // Default: 500 when the call gives no limit (usize::MAX = unbounded) } }
Every file tool (read_file, write_file, edit_file, list_files, search) has
allowed_paths and a with_allowed_paths(..) builder. The check runs against the resolved
path, so .. and symlinks (dangling ones included) cannot escape, and the tool does its I/O on
the checked path (see PathSandbox). Before 0.24.2 a .. after a missing directory, or a
dangling symlink, could get past the check.
read_file, write_file and edit_file refuse a path that is not a regular file (after
following symlinks): a FIFO, a device, a socket or a directory. Before 0.25.8 a FIFO passed the
size check (its length reads 0) and the read blocked until something wrote to it, and a device
such as /dev/zero was read without end.
WriteFileTool
Write content to a file. Creates parent directories automatically.
- Name:
write_file - Parameters:
path(required),content(required) - Configuration:
allowed_paths: Vec<String>
EditFileTool
Surgical search/replace edits. The most important tool for coding agents — instead of rewriting entire files, the agent specifies exact text to find and replace.
- Name:
edit_file - Parameters:
path(required),old_text(required),new_text(required) - Configuration:
allowed_paths: Vec<String>
The old_text must match exactly, including whitespace and indentation.
ListFilesTool
List files recursively with optional glob filtering.
- Name:
list_files - Parameters:
path(optional, default:.),pattern(optional glob),max_depth(optional, default: 3)
Configuration
#![allow(unused)] fn main() { pub struct ListFilesTool { pub max_results: usize, // Default: 200 pub timeout: Duration, // Default: 10s pub allowed_paths: Vec<String>, } }
Uses find, skipping target/, .git/ and node_modules/. Paths find can't read (a subdirectory without permission) don't fail the listing: the files it did find are returned, with the errors under Warnings and in details.warnings, so a partial listing is never presented as complete. A listing with no files and an error is a failure.
SearchTool
Search files using ripgrep, falling back to grep.
- Name:
search - Parameters:
pattern(required, regex),path(optional),include(optional file glob, e.g.*.rs),case_sensitive(optional, default false)
Configuration
#![allow(unused)] fn main() { pub struct SearchTool { pub root: Option<String>, // Root directory pub max_results: usize, // Default: 50, in total pub timeout: Duration, // Default: 30s pub allowed_paths: Vec<String>, } }
Returns matching lines with file paths and line numbers. Past max_results the search is stopped
and the result says there are more (details.truncated). The pattern and path are passed so they
can never be read as flags. When some files cannot be searched (unreadable, removed mid-walk), the
matches found elsewhere are still returned, followed by the tool's stderr under Warnings:
(details.warnings holds the same text, capped at 2,000 characters, or null); only a search
that found nothing and hit an error fails.
The tool uses ripgrep when an rg is on PATH (looked up directly, no which needed) and
grep -r otherwise; details.engine says which ran. Your own options never reach it: rg runs
with --no-config, and RIPGREP_CONFIG_PATH and GREP_OPTIONS are cleared, so neither can change
the path:line:text output or follow a symlink out of allowed_paths. They still apply to rg or
grep run through bash. A search that ends abnormally (killed by a signal, an unexpected exit
code, an unreadable output) is an error when it found nothing, and its matches carry a warning
otherwise: it never reads as "No matches".
SharedStateTool
Read and write named variables in a shared key-value store. This tool is not included in default_tools() — it is automatically injected into sub-agents when you call SubAgentTool::with_shared_state(), or into the agent itself with Agent::with_shared_state(). Unlike the tools above it also works on wasm32.
- Name:
shared_state - Parameters:
action(required:get,set,list,remove),key(required for get/set/remove),value(required for set)
| Action | Description |
|---|---|
get | Returns the value for a key, or error if not found |
set | Stores a value, returns confirmation with byte size |
list | Lists all keys with their byte sizes |
remove | Deletes a key |
See Sub-Agents: Shared State for usage details.