A tool is a function the model can call. Antares registers sixteen, plus whatever MCP servers add.
| Tool | What it does |
|---|---|
read_file |
Read a text file's bytes verbatim under a line-range header, with offset and limit for large ones |
write_file |
Create or overwrite, making parent directories |
edit_file |
Replace an exact string, which must appear exactly once unless told otherwise |
list_files |
Directory entries, optionally recursive |
glob |
Find files by pattern, newest first |
grep |
Regular-expression search returning matching lines with paths and numbers |
Paths are relative to the workspace and cannot escape it.
edit_file requiring a unique match is deliberate: an edit that silently hits
the wrong occurrence is worse than one that fails.
read_file returns one header line, <path> — lines <first>-<last> of <total>, a blank line, then the file's bytes unaltered — tabs, CRLF, and lone
CR included. Nothing is stamped onto a line, so there is nothing for the model
to strip before using a region as an edit_file anchor. The same numbers are
in the result's metadata (path, first_line, last_line, total_lines) for
callers that would otherwise parse the header.
Two things below the header are the tool's rather than the file's: a clipped
read appends … N more lines (use offset=N to continue), and a read that hit
the byte cap appends … file truncated at 400 KB. Each occupies a line of its
own beginning with …, which is how the model is told to recognise them; an
anchor must never be taken from one. The newline that starts that line is the
tool's, so on the byte-cap path it is not evidence that the last content line
was terminated in the file.
A read the 400 KB cap stopped has not counted the lines behind it, and says so
rather than reporting what it read as the whole: the header's total becomes
≥N, the continuation note says at least N more lines, and the metadata
drops total_lines for truncated: true plus total_lines_at_least, which is
the N the header states. last_line is the last line returned, as on any
other read, and is smaller than N whenever the line limit stopped the read
before the byte cap did. grep counts the whole file (up to 8 MB), so it is the tool
that can still find something past the cap; asking read_file to page to a
line beyond it is refused, naming the cap.
edit_file matches old_string byte for byte, and writes new_string byte for
byte on every path but one. That path is the single recovery: if the file uses
CRLF and an anchor whose every break is LF did not match, it is retried with
those breaks expanded to CRLF, since a model emits \n whatever it read. It
runs only after the exact match has failed, translates new_string only because
old_string had to be, and says so in its result. So when the anchor matched
exactly, a new_string written with LF lands in a CRLF file as LF; the result
notes that rather than rewriting bytes the caller asked for.
An anchor that is not found is an anchor that is wrong — stale, misremembered,
or reformatted. The fix is another read_file, not more surrounding context.
Extra context is the answer to the other error, where the anchor matches more
than once.
| Tool | What it does |
|---|---|
terminal |
Run a shell command in a persistent session |
The session survives between calls — cd, exported variables, and activated
environments persist. Backends are local, Docker, or SSH.
| Tool | What it does |
|---|---|
vps_run |
Run a shell command on a dashboard-saved VPS; omit command to list servers |
vps_upload |
SFTP upload a local workspace file to the VPS |
vps_download |
SFTP download a remote file into the workspace |
Default command timeout is 120 seconds (raise timeout_seconds up to 900 for
systemctl restart / upgrades). Transfers are capped at 256 MiB. Credentials
stay encrypted at rest; host keys are pinned on first connect (TOFU).
| Tool | What it does |
|---|---|
web_search |
Ranked results with titles, URLs, and snippets |
web_fetch |
Fetch a URL as readable text, HTML stripped |
browser |
Drive a real Chromium — its own guide |
web_fetch is faster and cheaper. Reach for browser when the page needs
JavaScript, a login, or a form.
| Tool | What it does |
|---|---|
memory |
Save, search, list, and delete durable facts |
session_search |
Full-text search across past conversations |
rag_search |
Semantic search over indexed documents |
rag_index |
Add a file or directory to the index |
See Memory and RAG.
| Tool | What it does |
|---|---|
todo |
A task list for the session, so progress stays visible |
skill |
List and read the skill library, and write new entries |
delegate_task |
Hand a self-contained subtask to an isolated sub-agent |
A sub-agent cannot see the conversation it came from, so its prompt has to stand alone. It returns only its final answer, which is the point: research that would flood the main context happens elsewhere.
A toolset is a named group. Give the model fewer tools and it chooses better; give it none it should not have and a whole class of mistakes cannot happen.
| Toolset | Contents |
|---|---|
minimal |
read_file, list_files, grep, todo |
coding |
files, terminal, todo, skill, delegate_task |
research |
read_file, web_search, web_fetch, browser, grep, todo, memory, session_search, rag_search, skill |
browser |
browser, web_search, web_fetch, read_file, write_file, todo |
default |
everything above, combined |
all |
every registered tool, including MCP |
antares config set tools.toolset researchOr per conversation, without saving:
/toolset coding
Adjust a toolset rather than replacing it:
tools:
toolset: coding
enabled: [web_search] # add
disabled: [terminal] # removedisabled wins over enabled.
A Telegram thread rarely wants a shell:
tools:
platform_toolsets:
telegram: research
discord: researchtools:
approval_mode: auto # auto | prompt | denyauto— tools run.prompt— tools that mutate state ask first.deny— mutating tools are refused; reading still works.
Tools declare whether they mutate. read_file does not; write_file,
terminal, and browser do.
tools:
max_output_chars: 30000
timeouts:
terminal: 120
web_fetch: 60
tool_loop_guardrails:
warn_after: 24
hard_stop_after: 32Output past the limit is truncated with a note saying how much was cut. The guardrails bound how many calls one turn may make; past the hard stop the model is told to summarise and finish. The repetition guard is separate and catches a different failure — the same call over and over.
Tools from MCP servers are namespaced mcp__<server>__<tool> and appear
alongside the built-ins. A server that fails to start is reported and skipped;
it never stops Antares from running. See MCP.
/tools
Lists what the model has this turn and which toolset it came from. The dashboard has the same on its Tools page, with per-tool switches.
type weatherTool struct{}
func (weatherTool) Name() string { return "weather" }
func (weatherTool) Description() string {
return "Current conditions for a city. Use when asked about weather."
}
func (weatherTool) Schema() map[string]any {
return schema(map[string]any{
"city": prop("string", "City name."),
}, "city")
}
func (weatherTool) Execute(ctx context.Context, in Input) Result {
var args struct{ City string `json:"city"` }
if err := in.Bind(&args); err != nil {
return Errorf("%v", err)
}
return Text("…")
}Register it in internal/tools/register.go and add it to whichever toolsets
should have it.
The description is prompt text, not documentation — it is how the model decides whether to reach for the tool. Say what it does and when to use it, and mention the tool it should be preferred over if there is one.
Add RequiresApproval() bool for a tool that mutates state.