Custom Tools
Extend the Stella agent with your own script tools by dropping a TOML manifest into your workspace or user config directory.
Beyond the built-in tools, you can give the agent your own developer-defined script tools. A custom tool is described by a small TOML manifest; once discovered, it appears alongside the built-ins and can be called by the agent like any other tool.
Where manifests live
Drop a <name>.toml manifest file into one of these directories:
- Workspace —
.stella/tools/(checked in with the project, shared with your team). - User global —
~/.config/stella/tools/(personal, available across all your workspaces).
If the same tool name is defined in both locations, the workspace manifest wins on collision. This lets a project override a personal default while keeping your global tools available everywhere else.
Run stella tools at any time to see which custom tools were discovered, alongside the built-ins.
Manifest shape
A manifest declares the tool's name, description, the command to run, and an input_schema:
# .stella/tools/wordcount.toml
name = "wordcount"
description = "Count the words in a file and print the total."
# The command Stella runs when the agent calls this tool. It is an argv array,
# spawned directly with no shell; command[0] resolves against the workspace root.
command = ["scripts/wordcount.sh"]
# Optional. Default 30000 (30 seconds), hard-capped at 600000 (10 minutes) —
# a manifest asking for more is clamped. Past the timeout, the script's whole
# process group is killed and the tool returns a named error.
timeout_ms = 60000
# Optional extra environment variables for the child process, applied on top
# of the inherited environment.
[env]
WORDCOUNT_LOCALE = "en_US"
# Tool inputs are a JSON Schema, written as TOML under [input_schema].
[input_schema]
type = "object"
required = ["path"]
[input_schema.properties.path]
type = "string"
description = "Path to the file whose words should be counted."command must be an argv array (["scripts/wordcount.sh"]), not a bare string — a string fails to parse and the tool never loads. Inputs go under [input_schema] (a JSON Schema), not [parameters]; an unknown table like [parameters] is silently ignored, leaving the tool with an empty schema.
The timeout_ms default is 30 seconds — half of the 60-second default that hooks get. Both share the same 10-minute hard cap.
How input reaches your script
When the agent calls a custom tool, Stella spawns command directly from the argv array — never through a shell, so the tool's input has no injection surface — with the working directory set to the workspace root. The model's input JSON is then delivered two ways, so trivial scripts need no JSON parser:
- The whole input object is written to the script's stdin as one JSON document, then stdin is closed — your script sees EOF.
- Each top-level scalar property (string, number, bool) is exported as
STELLA_INPUT_<UPPER_SNAKE_KEY>—pathbecomesSTELLA_INPUT_PATH,dry_runbecomesSTELLA_INPUT_DRY_RUN. Nested objects and arrays are delivered on stdin only.
The manifest's [env] table is applied on top of the inherited environment, before the STELLA_INPUT_* exports. Input keys come from the model but are namespaced under STELLA_INPUT_, so they can never clobber PATH or your [env] values.
A script consuming both channels:
#!/usr/bin/env bash
set -euo pipefail
# Scalar inputs arrive as environment variables…
path="$STELLA_INPUT_PATH"
# …and the full input object arrives as one JSON document on stdin —
# read it (e.g. with jq) when you need nested objects or arrays.
input="$(cat)" # {"path": "src/lib.rs"}
wc -w < "$path"Exit 0 returns the captured stdout as the tool result; a non-zero exit returns an error carrying the exit code and the stderr tail. Both streams are truncated middle-out past a byte cap, same as the built-in bash tool.
Validating manifests
Before a run, check every manifest with the strict pre-flight validator:
stella tools --validate # scans .stella/tools/ and ~/.config/stella/tools/
stella tools --validate ./mytools # or an explicit directoryIt parses each <name>.toml and reports errors (bad TOML, missing/invalid/reserved name, empty description or command, a non-table input_schema), warnings (e.g. a timeout_ms over the 10-minute cap is clamped, a name that shadows a built-in or another manifest), and info. It exits non-zero if any manifest has errors — handy in CI.
How custom tools appear
Once a manifest is discovered, its tool is registered into the session and shows up next to the built-ins — including in the output of stella tools. The agent can then call it as part of its normal step loop. Like every other tool, a custom tool is subject to the per-tool permission model, and its calls can be gated or blocked with a PreToolUse hook.