Config file
Every key in ~/.tomo/config.yaml: the model and providers, policy, channels, heartbeat, voice, MCP servers, workers, and the data dir.
tomo reads one YAML file, ~/.tomo/config.yaml by default, or the path you pass with --config.
Run tomo onboard to write a starter file with every section present and annotated.
A missing config is an error that names the fix.
This page lists every key tomo reads. Anything not here is ignored.
Environment variable expansion
Any value may reference an environment variable with ${VAR}, and tomo expands it when it loads the file.
An unset variable expands to the empty string.
This keeps secrets like API keys and bot tokens out of the file itself:
providers:
anthropic:
api_key: ${ANTHROPIC_API_KEY}
default_model
default_model: anthropic/claude-fable-5
The provider/model spec used when a command gives no --model.
The form is provider/model, where provider names an entry under providers and model is whatever that backend calls the model.
The model part may itself contain slashes, which some gateways use.
There is no built-in default: with no default_model and no --model, a command that needs a model errors.
providers
providers:
<name>:
type: anthropic
api_key: ${ANTHROPIC_API_KEY}
base_url: ""
pricing:
input: 3
cached_input: 0.30
cache_write: 3.75
output: 15
model_pricing:
smaller-model:
input: 0.25
cached_input: 0.025
cache_write: 0.3125
output: 1.50
A map from a provider name to a backend.
The name on the left is what default_model and --model reference before the slash.
| Key | Meaning |
|---|---|
type |
The backend dialect: anthropic or openai. Anything speaking the OpenAI chat completions dialect uses openai. |
api_key |
The provider key. Usually a ${VAR} reference. |
base_url |
Override the endpoint. Point it at a local server or a gateway; leave it empty for the provider's own API. |
pricing |
Optional list-rate snapshot in USD per one million tokens. Omit it when rates are unknown. An explicit all-zero block marks a free or local model. |
model_pricing |
Optional rates keyed by exact model ID. A matching model overrides the provider-level pricing block. |
The pricing keys are input, cached_input, cache_write, and output. Use model_pricing when one gateway serves models with different rates. tomo records the selected rates on every call, so later rate changes do not rewrite historical costs. Fresh input, cache reads, cache writes, and output are costed separately. A provider that does not report one token category records zero for that category.
You can define as many providers as you like and switch between them per command with --model.
A turn runs its tool-use rounds until the model ends the turn on its own, so a long multi-step task runs to completion.
tracing
tracing:
enabled: true
dir: ~/.tomo/traces
Model calls are captured by default in a normalized SQLite ledger. The ledger stores each distinct system prompt, tool set, message, and response once under a content hash. Calls reference those objects instead of copying the growing conversation into every record. Runs are indexed by date, provider, model, and a stable task identifier derived from the first user message. WAL mode, a busy timeout, bounded retries, and short transactions allow several tomo processes to write to the same ledger concurrently.
Credentials are redacted before content is stored. The trace directory is created with mode 0700 and the ledger with mode 0600. Traces can still contain private prompts, source code, and model output, so protect the data directory accordingly. Set enabled: false to retain no model content.
Use tomo traces list to filter runs and tomo traces summary to aggregate usage, failures, exact cost components, and priced versus unpriced calls. tomo traces export RUN_ID produces Hugging Face Session Trace Simple Format JSONL by default. The file is ready for a Hugging Face dataset or Storage Bucket and renders in the agent trace viewer. --format native produces tomo's lossless resolved JSON shape instead.
tomo traces export-all DATASET_DIR materializes all matching sessions under a date, provider, model, task, and run hierarchy. The ledger stays deduplicated until an export is explicitly requested. Upload the resulting directory to a Hugging Face dataset or Storage Bucket.
Older tomo-labs proxy captures can be imported once with:
go run ./scripts/migrate-legacy-traces --source "$HOME/data" --destination "$HOME/data/tomo-traces-v2"
The migration is resumable, never deletes source files, records a SHA-256 manifest of every selected source file, verifies run and call counts, runs SQLite's integrity check, and writes migration-report.json with source size, ledger size, and logical savings.
Credential-like values are redacted, but traces may still contain private prompts, source code, local paths, tool output, screenshots, and personal data. Review exports before publishing them or keep the destination dataset private.
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
Capture normalized model-call traces. |
dir |
traces under the data dir |
Directory containing trace.sqlite. |
policy
policy:
read: allow
net: allow
write: ask
exec: ask
rules:
bash: deny
write: allow
The gate every tool call passes before it runs.
The four class keys set the baseline decision for each capability class; rules overrides individual tools by name.
See the policy and safety guide for the full trust model, including the taint escalation and how external tools are handled.
| Key | Default | Meaning |
|---|---|---|
read |
allow |
Baseline for tools that read local state. |
net |
allow |
Baseline for tools that talk to the network. |
write |
ask |
Baseline for tools that mutate local state. |
exec |
ask |
Baseline for tools that run arbitrary code. |
rules |
Map of tool name to decision (allow, ask, or deny), winning over the class default. |
Each decision is one of allow, ask, or deny.
A class you leave unset stays at the safe default shown above, and a value tomo does not recognize falls back to ask rather than opening up.
sandbox
sandbox: none
The confinement an approved exec-class command runs under.
The gate decides whether a command may run; the sandbox bounds what it can touch once it does.
Off (none) by default, so a command runs with tomo's own privileges.
Confinement is OS-enforced (Seatbelt on macOS, namespaces on Linux) with no container engine and no change to the CGO-free build.
See the policy and safety guide.
| Value | Filesystem | Network |
|---|---|---|
none |
tomo's own privileges (default) | as tomo |
restricted |
read the working tree, write nothing | none |
standard |
read all but secrets, write the working tree and tmp | none |
net |
same as standard |
outbound allowed |
dev |
standard plus build caches |
outbound allowed |
A worker may set its own sandbox to override this top-level value.
channels
channels:
telegram: {}
discord: {}
slack: {}
imessage: {}
The front doors tomo serve opens.
The web chat is always on and has no entry here; the rest start only when configured.
Each names the conversations it will serve, so a leaked token or a stray invite does not hand anyone an agent.
See the channels guide.
telegram
channels:
telegram:
token: ${TELEGRAM_BOT_TOKEN}
allow_chats: [123456789]
| Key | Meaning |
|---|---|
token |
The bot token. The channel starts only when this is set. |
allow_chats |
List of numeric chat ids allowed to reach the bot. |
discord
channels:
discord:
token: ${DISCORD_BOT_TOKEN}
allow_channels: ["000000000000000000"]
| Key | Meaning |
|---|---|
token |
The bot token. The channel starts only when this is set. |
allow_channels |
List of channel ids (as strings) allowed to reach the bot. |
slack
channels:
slack:
app_token: ${SLACK_APP_TOKEN}
bot_token: ${SLACK_BOT_TOKEN}
allow_channels: ["C0000000000"]
| Key | Meaning |
|---|---|
app_token |
The app-level token that opens the socket. The channel starts only when this is set. |
bot_token |
The bot token used to post messages. |
allow_channels |
List of channel ids (as strings) allowed to reach the bot. |
imessage
channels:
imessage:
allow_handles: ["+15555550123"]
db_path: ""
macOS only, and needs Full Disk Access since it reaches a real Messages account.
The presence of the imessage block is what turns it on; there is no separate flag.
| Key | Meaning |
|---|---|
allow_handles |
List of phone numbers or emails permitted to drive the agent. |
db_path |
Path to the Messages database. Leave empty for the default location. |
heartbeat
heartbeat:
enabled: true
every: "@every 30m"
file: ~/.tomo/HEARTBEAT.md
channel: telegram
chat: "123456789"
Runs tomo on a cadence against a checklist file, so it can pick up standing work without being spoken to. It stays quiet when there is nothing worth saying. Background runs cannot get approval, so anything gated to ask is declined while unattended. See the scheduling guide.
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Off unless set true. The defaults below apply only when it is on. |
every |
@every 30m |
The schedule, in the same forms tomo cron add accepts. |
file |
HEARTBEAT.md in the data dir |
The checklist read each beat. |
channel |
web |
Where to deliver anything worth saying. The web chat has nowhere to push, so point this at a poster like telegram to have results delivered. |
chat |
Chat id within that channel. |
voice
voice:
model: ~/.tomo/models/ggml-base.en.bin
bin: whisper-cli
ffmpeg: ffmpeg
tts_model: ~/.tomo/models/en_US-amy-medium.onnx
tts_bin: piper
Speech both ways, handled locally, so no audio leaves the machine.
Setting model turns on transcription of inbound voice notes with whisper.cpp.
Setting tts_model turns on spoken replies with piper, sent back as a voice note wherever you spoke first.
| Key | Default | Meaning |
|---|---|---|
model |
Path to a ggml whisper model. Setting it enables voice-in. | |
bin |
whisper-cli |
The whisper.cpp CLI on PATH. |
ffmpeg |
ffmpeg |
ffmpeg on PATH, used to decode inbound clips and encode the spoken reply to opus. |
tts_model |
Path to a piper voice model. Setting it enables voice-out. | |
tts_bin |
piper |
The piper CLI on PATH. |
mcp
mcp:
servers:
<name>:
command: ""
args: []
env: {}
url: ""
headers: {}
Model Context Protocol servers to attach on startup.
Each server's tools join the toolset, namespaced by the server key: a filesystem tool named read under a server keyed files becomes files_read.
These tools are not tomo's own code, so they default to ask even when their class would run; add a policy.rules entry to allow one you trust.
See the MCP guide.
A server sets either command (a local subprocess over stdio) or url (a remote server over HTTP).
| Key | Meaning |
|---|---|
command |
Executable to launch for a stdio server. |
args |
Its arguments. |
env |
Extra environment for the subprocess. |
url |
Endpoint of an HTTP server. |
headers |
Sent on every HTTP request, for auth. |
workers
workers:
<name>:
persona: You dig up sources and summarize. Cite what you find.
model: anthropic/claude-fable-5
policy:
write: deny
sandbox: standard
channels: ["slack:C0RESEARCH"]
Named specialists that handle some conversations in their own right.
The default worker is tomo itself and needs no entry here.
Each worker gets its own memory, so nothing one learns leaks into another's prompt.
Reach a worker by starting a message with @name, or bind a channel:chat to it so its messages always route there; an explicit @name wins over a binding.
See the workers guide.
| Key | Meaning |
|---|---|
persona |
Extra system-prompt lines that set its role. |
model |
Provider/model override. Empty means the default. |
policy |
Its own gate, in the same shape as the top-level policy, merged over the top-level one. |
sandbox |
Exec sandbox for this worker, overriding the top-level sandbox. Empty means the default. |
workspace |
Working directory for this worker's file and shell tools, overriding the top-level workspace. Empty means the default. |
channels |
List of channel:chat keys whose messages route to it. |
data_dir
data_dir: ~/.tomo
Where tomo keeps everything: the config, the tomo.db ledger, the audit.log, normalized model traces, and the memory, skills, and skill-drafts dirs.
Defaults to ~/.tomo.
workspace
workspace: ~/tomo
The working directory the read, write, and bash tools are rooted at.
A relative path the agent writes lands here, the shell runs here, and the agent is told where it is in its system prompt so it stops guessing a home directory.
An absolute path the agent gives is still honored as-is, and a ~ prefix still expands to the home directory.
Defaults to the directory tomo was launched from, which keeps the old behavior where a relative path resolved against the process working directory.
A worker may set its own workspace to override this top-level value.
Complete example
A full config with every section filled in. Commented lines mark the optional pieces you can leave out.
# tomo config. Values may reference environment variables with ${VAR}.
default_model: anthropic/claude-fable-5
providers:
anthropic:
type: anthropic
api_key: ${ANTHROPIC_API_KEY}
local:
type: openai
base_url: http://gamingpc:8000/v1
api_key: ${LOCAL_API_KEY}
tracing:
enabled: true
dir: ~/.tomo/traces
# Reads and network run; writes and code execution ask first.
# A per-tool rule wins over the class default.
policy:
read: allow
net: allow
write: ask
exec: ask
rules:
bash: deny # never run shell, whatever the class says
write: allow # trust file writes without a prompt
# Confine an approved shell command at the OS level. none by default.
sandbox: none
# The directory the file and shell tools work in. Defaults to where tomo starts.
workspace: ~/tomo
# The web chat is always on; these start only when configured.
channels:
telegram:
token: ${TELEGRAM_BOT_TOKEN}
allow_chats: [123456789]
discord:
token: ${DISCORD_BOT_TOKEN}
allow_channels: ["000000000000000000"]
slack:
app_token: ${SLACK_APP_TOKEN}
bot_token: ${SLACK_BOT_TOKEN}
allow_channels: ["C0000000000"]
imessage: # macOS only, needs Full Disk Access
allow_handles: ["+15555550123"]
# Runs on a cadence against a checklist and stays quiet when there is nothing to say.
heartbeat:
enabled: true
every: "@every 30m"
file: ~/.tomo/HEARTBEAT.md
channel: telegram
chat: "123456789"
# Speech both ways, all local. model enables voice-in, tts_model enables voice-out.
voice:
model: ~/.tomo/models/ggml-base.en.bin
bin: whisper-cli
ffmpeg: ffmpeg
tts_model: ~/.tomo/models/en_US-amy-medium.onnx
tts_bin: piper
# MCP servers attached on startup; their tools default to ask.
mcp:
servers:
files:
command: mcp-server-filesystem
args: [/Users/me/work]
github:
command: npx
args: [-y, "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
remote:
url: https://mcp.example.com/mcp
headers:
Authorization: Bearer ${MCP_TOKEN}
# Named specialists with their own persona, model, policy, and memory.
workers:
research:
persona: You dig up sources and summarize. Cite what you find.
model: anthropic/claude-fable-5
policy:
write: deny # this one only reads and reports
sandbox: standard # confine this worker's shell; others stay unconfined
workspace: ~/research # this worker's files land here, not the shared dir
channels: ["slack:C0RESEARCH"]
data_dir: ~/.tomo