Local Tools
Local tools run directly inside the Contenox process — no network, no subprocess, no protocol overhead. They are the fastest way to give a model access to the machine it's running on.
local_fs — Filesystem access
Always available. Provides read, write, search, and metadata operations scoped to a configured directory. All paths are validated against the allowed directory; attempts to escape with ../ are rejected.
Configure the allowed root via contenox config set local-exec-allowed-dir /path/to/project or the --allowed-dir flag.
Tools
| Tool | Parameters | Description |
|---|---|---|
read_file | path | Read the full content of a file. Also satisfies the read-before-mutate prerequisite for write_file / sed against the same path. |
write_file | path, content | Write content to a file (creates parent dirs, overwrites). For existing files, requires a prior read_file or read_file_range against the same path in this session. |
list_dir | path (optional) | List entries in a directory (dirs marked with /) |
read_file_range | path, start_line, end_line | Read a specific line range. Like read_file, satisfies the read-before-mutate prerequisite. |
grep | path, pattern | Find lines containing a string (returns line_number: content) |
sed | path, pattern, replacement | Replace all occurrences of a string in a file. For existing files, requires a prior read_file or read_file_range of the same path in this session. |
count_stats | path | Count lines, words, and bytes (like wc) |
stat_file | path | Get file metadata: name, size, mod time, isDir |
Read-before-write contract
write_file and sed against an existing file are blocked unless the same session has previously called read_file or read_file_range on that path. The model receives a soft denial it can act on: it sees a tool result instructing it to read the file first, then retry the write. New files (paths that do not yet exist) are unaffected.
This is a deterministic guard — no LLM judgement involved — designed to prevent confabulated edits to files the model has never seen. The contract is scoped per session: a read in one contenox session does not satisfy a write in another. The state lives in a private local_fs_reads table the tool maintains itself; the chain engine has no visibility into it.
If the model uses local_shell (cat, head, grep, sed) instead of local_fs.read_file, the guard does not count it as a satisfying read — by design. The shell tools are not bounded the same way and broadening the guard to recognise their output reliably is impractical. Prefer local_fs.* tools for file inspection (the default chains include a TOOL PREFERENCE system-prompt addendum that nudges the model toward this).
tools_policies.local_fs keys
Tighten the sandbox per task by adding a tools_policies.local_fs block to execute_config:
| Key | Type | Default | Description |
|---|---|---|---|
_max_read_bytes | int | 1048576 (1 MiB) | Max file size for read_file / read_file_range / grep / sed / count_stats. 0 or negative = unlimited. Larger files return an error so the model can narrow with read_file_range. |
_max_output_bytes | int | 524288 (512 KiB) | Max byte size of any tool result returned to the model (UTF-8 bytes). Prevents listing a huge directory or grepping a large file from blowing up context. 0 or negative = unlimited. |
_max_list_depth | int | 6 | Cap on list_dir(recursive=true). Hard-capped at 32 regardless of policy. |
_max_grep_matches | int | 5000 | Stops grep after this many matching lines and returns an error so the model narrows the pattern. Hard-capped at 500000. |
_denied_path_substrings | comma-sep | empty | Path substrings that always reject (e.g. node_modules,.git/,dist/). Matched against the path relative to the allowed root. |
"tools_policies": {
"local_fs": {
"_allowed_dir": ".",
"_max_read_bytes": "1048576",
"_max_output_bytes": "524288",
"_max_list_depth": "6",
"_max_grep_matches": "5000",
"_denied_path_substrings": "node_modules,.git/,dist/,/.next/,/out/,package-lock.json"
}
}
Values are strings even when conceptually numeric — tools_policies is the chain's policy carrier and uses string values uniformly across tools. The default chains (default-chain.json, default-run-chain.json) ship with the table above as their local_fs block.
Chain example
"execute_config": {
"model": "qwen2.5:7b",
"provider": "ollama",
"tools": ["local_fs"]
}
webtools — HTTP calls
Always available. Lets the model call any HTTP endpoint via per-verb tools. Unlike remote tools (which require an OpenAPI spec), webtools exposes six generic verb tools — the model picks the verb, URL, query params, headers, and body at call time.
Caution
Because the model controls the destination URL, every request is gated by SSRF and size limits configured via tools_policies.webtools (see below). The defaults block link-local / loopback / cloud-metadata addresses and cap response size at 1 MiB. Mutating verbs (web_post, web_put, web_patch, web_delete) trigger a HITL approval prompt by default. Do not point chains at untrusted user input without tightening the policy further.
Tools
| Tool | Parameters | Description |
|---|---|---|
web_get | url, headers?, query? | HTTP GET. Use for read-only retrieval. Default-allow under HITL. |
web_head | url, headers?, query? | HTTP HEAD. Inspect headers / status without fetching the body. Default-allow under HITL. |
web_post | url, headers?, query?, body? | HTTP POST. HITL-approve by default. |
web_put | url, headers?, query?, body? | HTTP PUT. HITL-approve by default. |
web_patch | url, headers?, query?, body? | HTTP PATCH. HITL-approve by default. |
web_delete | url, headers?, query?, body? | HTTP DELETE. HITL-approve by default. |
Parameter shapes:
| Parameter | Type | Description |
|---|---|---|
url | string | Absolute URL. Scheme must be in _allowed_schemes (default http,https). Host is checked against _allowed_hosts / _denied_hosts. |
headers | object | string | JSON object {"X-Foo":"bar"} (preferred). A JSON-encoded string is also accepted for back-compat. |
query | string | URL-encoded query string (e.g. a=1&b=2). Merged with the URL's existing query. |
body | any | Used by mutating verbs only. Strings sent as-is; any other JSON value is marshalled. Capped by _max_request_body_bytes (default 256 KiB). |
tools_policies.webtools keys
| Key | Type | Default | Description |
|---|---|---|---|
_allowed_hosts | comma-sep | empty (any host) | When set, only listed hosts pass. |
_denied_hosts | comma-sep | 169.254.169.254,169.254.170.2,localhost,127.0.0.1,0.0.0.0,::1,metadata.google.internal,metadata.azure.com | Empty string "" opts out of the SSRF baseline. |
_allowed_schemes | comma-sep | http,https | Block file://, gopher://, ftp://, etc. |
_max_response_bytes | int | 1048576 (1 MiB) | 0 or negative = unlimited. Truncated responses include a marker. |
_max_request_body_bytes | int | 262144 (256 KiB) | 0 or negative = unlimited. Oversized body blocks the call before sending. |
_request_timeout_seconds | int | 30 | Per-call timeout. |
_max_attempts | int | 3 | Retries 5xx and transport errors only — never 4xx. |
_initial_backoff_ms | int | 250 | Exponential backoff with jitter. |
_max_backoff_ms | int | 5000 | Cap on the exponential backoff. |
_disallow_redirects | bool-string | "false" | When "true", blocks all 3xx redirect-following. |
Chain example
"execute_config": {
"model": "qwen2.5:7b",
"provider": "ollama",
"tools": ["webtools"],
"tools_policies": {
"webtools": {
"_allowed_hosts": "api.github.com,api.openai.com",
"_max_response_bytes": "524288",
"_request_timeout_seconds": "20"
}
}
}
local_shell — Shell command execution
Caution
local_shell gives the model direct access to run arbitrary commands on your machine. Never enable it in public-facing deployments or when processing untrusted user input.
Opt-in only — disabled by default. Enable per-invocation with the --shell flag:
contenox run --shell "clean up unused imports in the codebase"
contenox chat --shell "run the tests and fix anything that breaks"
Command policy is set in the chain, not on the CLI. Add a tools_policies block to execute_config:
"execute_config": {
"model": "{{var:model}}",
"provider": "{{var:provider}}",
"tools": ["local_shell"],
"tools_policies": {
"local_shell": {
"_allowed_commands": "git,go,make,ls,cat",
"_denied_commands": "sudo,su,dd,mkfs"
}
}
}
_allowed_commands— comma-separated list of permitted command names. When set, any command not on this list is rejected before it runs._denied_commands— comma-separated commands that are always blocked, regardless of the allowlist._allowed_dir— if set, the command (or its absolute path) must reside under this directory.
The default chains (default-chain.json, default-run-chain.json) ship with sensible defaults: common dev tools allowed, privilege-escalation and raw-disk commands denied.
To use local_shell with no policy restrictions (fully open), omit tools_policies entirely. Only do this in fully-trusted, local-only environments. Review tool use in your chain and use --shell only when you intend to grant shell access.
Tool
local_shell
| Parameter | Type | Required | Description |
|---|---|---|---|
command | string | ✅ | Executable path or name |
args | string | — | Space-separated arguments |
cwd | string | — | Working directory |
timeout | string | — | Duration e.g. 30s |
shell | boolean | — | Run via /bin/sh -c (allows pipes, redirects, $VAR). Disabled when _allowed_commands or _allowed_dir is set. |
ssh — Remote command execution
Always available. Runs a single command on a remote host over SSH. Supports password and private-key authentication.
Caution
The model controls the command string and target host. Restrict ssh to chains with tightly scoped system instructions and trusted user input only.
Tool
execute_remote_command
| Parameter | Type | Required | Description |
|---|---|---|---|
host | string | ✅ | Remote hostname or IP |
user | string | ✅ | SSH username |
command | string | ✅ | Command to execute |
port | int | — | SSH port (default: 22) |
password | string | — | Password authentication |
private_key | string | — | PEM-encoded private key content |
private_key_file | string | — | Path to a private key file |
timeout | string | — | Duration, e.g. "30s" (default: "30s") |
host_key | string | — | Expected host key fingerprint (SHA-256) |
strict_host_key | bool | — | Enforce host key verification (default: true) |
Returns a JSON object with fields: exit_code, stdout, stderr, duration_seconds, command, host, success.
Use output_template on the task to extract specific fields:
"output_template": "Exit <span v-pre>{{.exit_code}}</span>: <span v-pre>{{.stdout}}</span>"
Chain example
"execute_config": {
"model": "qwen2.5:7b",
"provider": "ollama",
"tools": ["ssh"]
}
print — Append to conversation
Always available. Appends a message to the chat history as a system message, or returns the message as a plain string when no chat history is active.
Tool
print
| Parameter | Type | Required | Description |
|---|---|---|---|
message | string | ✅ | Text to append or return |
Chain example
"execute_config": {
"model": "qwen2.5:7b",
"provider": "ollama",
"tools": ["print"]
}
echo — Debug passthrough
Always available. Echoes the input back, prefixed with "Echo: ". Useful for verifying what a task receives during chain development.
Tool
echo
| Parameter | Type | Required | Description |
|---|---|---|---|
input | string | ✅ | Text to echo |
Chain example
"execute_config": {
"model": "qwen2.5:7b",
"provider": "ollama",
"tools": ["echo"]
}
Adding custom local tools
Adding new local tools types requires modifying the Contenox Go source code and implementing the taskengine.HookRepo interface. For custom capabilities without writing Go, build a small HTTP service (FastAPI, Express, etc.) and register it as a Remote Tools instead — no code changes required.