Tools
orangu exposes local workspace tools to the active
model.
Tools are sent as OpenAI tools on every chat request,
and the model asks for one by answering with tool_calls;
orangu runs it in the workspace, feeds the result back as a
tool message, and lets the model continue.
max_tool_rounds in [orangu] bounds how many
such rounds one prompt may take.
Whether this works depends on the model, not on
orangu: a model whose chat template has no tool support never sees the
declarations and will say it has no tools available.
orangu-server passes the array through to the template and
translates the answer back — see Tool calling in the
Inference server chapter for which answer formats it recognises.
Configured Streamable HTTP MCP servers can add tools to this list.
Their names are namespaced as
mcp__<server>__<tool> so they cannot collide
with built-in tools or another server. orangu discovers them once when a
workspace opens; a server that cannot be reached is disabled with a
warning while built-in and other MCP tools remain available. MCP tool
results are returned through the same text tool result channel as
built-in tools and are capped at 20,000 characters.
MCP support currently exposes tools only. Resources and prompts are
not exposed; /mcp refresh re-discovers tools at
runtime.
Available tools
| Tool | Purpose | Key arguments |
|---|---|---|
show_file |
Show a text file from the workspace | path, optional
start_line, optional end_line, optional
mode |
create_file |
Create a file with content, optionally with its permissions | path, optional
content, mode, overwrite,
parents, git |
modify_file |
Modify a file, by text replacement or by line ranges | path, either
old_text/new_text (+ replace_all)
or edits, optional git |
move_file |
Move or rename a file | from, to,
optional mode, overwrite,
parents, git |
delete_file |
Delete a file | path, optional
git |
create_directory |
Create one directory | path, optional
mode, parents |
move_directory |
Move a directory and everything under it | from, to,
optional mode, parents |
delete_directory |
Delete an empty directory | path |
list_directory |
List files and directories below the workspace | optional path, optional
max_depth |
fetch_url |
Fetch an external URL and return readable text | url, optional
max_chars |
run_shell_command |
Run a shell command inside the workspace | command, optional
cwd, optional timeout_seconds |
expand_context |
Retrieve previously compressed/truncated output using its hash ID | id |
The eight file-lifecycle tools are the same operations
orangu-server serves over HTTP as
/v1/create_file, /v1/modify_file and so on —
one shared implementation (orangu::files), so a tool call,
a typed command and an API request behave identically. Their full
field-by-field schemas are in the HTTP endpoints chapter, under
File-lifecycle API.
They replace the earlier read_file and
edit_file: read_file is now
show_file with the same arguments, and
edit_file is now modify_file, which still
takes old_text/new_text and additionally
accepts the server’s edits line ranges. The typed
/add_file is obsolete for the same reason — it was
/create_file without content (see the Git commands
chapter). An existing path is overwritten on every surface — tool, typed
command and HTTP endpoint alike; pass "overwrite": false
for create-if-absent.
Licence headers. A file that
create_file brings into existence is generated code, and it
is written with the workspace’s own licence at the top
of it, as a comment in that file’s own language — // for
Rust and the C family, # for shells and configuration
formats, -- for SQL and Lua, ;; for Lisps,
% for TeX and Erlang, REM for batch files, and
a delimited <!-- -->, /* */ or
(* *) block for a language with no line comment. A shebang
or an XML declaration keeps the first line; the licence goes directly
beneath it.
The licence and the copyright holder are read from the workspace: the
license field of its Cargo.toml,
pyproject.toml or package.json, or failing
that its LICENSE/COPYING file — used
verbatim when it is a licence orangu has no header for. A
project that says nothing gets MIT. /license
overrides all of it for the session, including turning headers
off; see the Core tools chapter.
Three things never get one, and they are the point of the rule:
- A file that already existed. Rewriting or editing it is a change to somebody’s project, and a project’s licensing is not the agent’s to decide — an MIT header must never land on top of a GPL source file.
- A file you spelled out yourself with the typed
/create_filecommand. That is you writing a file, not orangu generating one. - A format with nowhere to put a comment —
.json,.csv— which would be corrupted rather than licensed. - A workspace with no copyright holder to name. It is
taken from the manifest’s first author, the
LICENSEfile’s own copyright line (for MIT and the BSDs, whose licence files name the project rather than a foundation), orgit config user.name. With none to be found there is no header, because inventing an attribution is worse than omitting one —/license <spdx> <holder>supplies one.
Because the header is added on top of what was sent, the file on disk
has more lines than the content argument did. The tool
result reports this as "licensed": true, and a
modify_file that names line numbers should read the file
back first. "licensed": false means no header was added —
which, now that the licence is the project’s, is the ordinary answer in
a project that does not declare one. The licence texts are strings
compiled into the binary rather than files read from disk, and they are
shared with the web console’s code blocks, which use the same
implementation.
Git. In a Git repository these tools make their
change with the Git command —
create_file/modify_file stage with
git add, move_file moves with
git mv, delete_file deletes with
git rm — so work is staged as it happens. Nothing
is ever committed; that stays your decision
(/commit). Pass "git": false on a call for a
plain filesystem change.
Workspace restrictions
The tools are rooted in the active workspace. By default this is the
current directory, unless orangu was started with
--workspace /path/to/project.
Paths that attempt to escape the workspace are rejected.
Absolute paths are allowed only when they still resolve inside the workspace after normalization.
show_file
show_file returns text content with line numbers:
{
"path": "src/main.rs",
"start_line": 10,
"end_line": 20,
"mode": "full"
}Behavior:
pathis requiredstart_linedefaults to line 1end_linedefaults to the end of the filemodedefaults tofull. Valid modes arefull(read actual content),signatures(extract only public interfaces), ormap(extract top-level item declarations for an overview).- Each returned line is prefixed as
N. text(only applies tofullmode) - Repeated unchanged whole-file reads in the same conversation may return a cache stub instead of resending the entire file
- The cache stub means the model should reuse the earlier full content
already in context; use
start_lineandend_lineto request a fresh focused excerpt when needed
modify_file
modify_file performs a targeted replacement inside a
workspace file:
{
"path": "src/main.rs",
"old_text": "fn old_name()",
"new_text": "fn new_name()"
}Optional flags:
replace_allreplaces every match instead of only the first one
Important details:
path,old_text, andnew_textare required by the tool schema- If the file does not exist, it is created (mode
0644) withnew_textas its contents - If
old_textis empty, the file content is replaced withnew_text - If
old_textis not found in an existing file, the tool returns an error - Successful edits return JSON with
path,created,updated,original_bytes,new_bytes,mode, andgit - Instead of
old_text/new_text,editsmay be given — an array of{start_line, end_line, replacement}line ranges, exactly asorangu-server’s/v1/modify_filetakes them.editswins if both are supplied - In a Git repository the change is staged with
git add; pass"git": falseto leave the index alone
list_directory
list_directory is a workspace-scoped directory listing
tool:
{
"path": "src",
"max_depth": 3
}Behavior:
pathdefaults to.max_depthdefaults to2- Each result line is formatted as
kind<TAB>path kindis eitherdirorfile- Paths are shown relative to the workspace when possible
fetch_url
fetch_url retrieves external documentation or reference
material:
{
"url": "https://example.com/docs",
"max_chars": 12000
}Behavior:
urlis requiredmax_charsdefaults to20000- HTML responses are converted into readable text
- Non-HTML responses are returned as plain text
- Long responses are truncated and end with
[truncated]
expand_context
expand_context retrieves exact session-cached context
that orangu kept outside the prompt to save tokens. Most
truncation markers point directly to the complete original blob; bounded
workflows such as /create_patch may instead point to a
small index whose entries can be expanded selectively.
{
"id": "abc1234567"
}Behavior:
idis required and must be a 10-character cache ID.- You will find these IDs injected into your context as markers
(e.g.
[Note: Output truncated. Run expand_context(id="abc1234567")]). - The tool returns the exact cached node. This is normally the full, uncompressed original output; a bounded index node contains IDs for smaller exact chunks.
- The cache ID is a SHA-256 hash prefix of the content, meaning it is mathematically guaranteed to perfectly match the truncated payload.
- Cache files are strictly scoped to the active session and automatically deleted when the session ends.
run_shell_command
run_shell_command executes a Bash command inside the
workspace:
{
"command": "cargo test --quiet",
"cwd": "crates/core",
"timeout_seconds": 60
}Behavior:
commandis requiredcwddefaults to the workspace roottimeout_secondsdefaults to30- The command runs through the platform’s shell:
bash -lcon Linux and macOS,powershell -NoLogo -NonInteractive -Commandon Windows. It is passed verbatim and never translated between them, so anything beyond the simplest command is written for one or the other - Output is intercepted and compressed before being sent to the LLM to
prevent flooding the context window. Native slash commands (like
/diff,/log,/build) execute locally and display their full output to the user, but when this output is injected into the model’s context, it is compressed and tracked via a cache identifier to ensure it is only transmitted once if unchanged. - Output is returned as pretty-printed JSON with
exit_code,stdout, andstderr stdoutandstderrare each truncated to at most 20,000 characters