Config

substructure.toml declares one project. Every key is here.

The CLI reads the file from the working directory, or from the path you pass with -c. It does not search parent directories.

An unknown key is a parse error.

A full file

substructure.toml
name = "support-bot"
db = "substructure.db"
log = "info"

[llm.claude]
type = "anthropic"

[agent.support]
llm = "claude"
model = "claude-sonnet-4-5"
system = "You are a support agent."
mcp = ["sentry"]
sub_agents = ["researcher"]

[agent.researcher]
description = "Finds and reads sources."
llm = "claude"
model = "claude-haiku-4-5"

[mcp.sentry]
url = "https://mcp.sentry.dev/mcp"

[slack]
dm = "support"
mentions = "support"

[run]
agent = "support"
output = "pretty"

[serve]
host = "127.0.0.1"
port = 8080

[remote]
url = "https://api.substructure.ai"
org = "org_01hx…"
project = "proj_01hx…"

Two roles

A file has two roles. It can have one or both.

RoleKeysCommands
An engine you rundb, log, [run], [serve]subs run, subs serve
A deployment you administer[remote]subs apply, subs keys, subs sessions

What the project is stays the same for both roles: name, [llm.<id>], [agent.<id>], [mcp.<id>], and [slack].

A second environment is a second file. subs apply -c substructure.staging.toml deploys a separate project.

Top level

KeyTypeDefaultMeaning
namestringnoneThe project's name. subs apply creates the project from it and renames it when it changes.
dbpaththe file's name with a .db suffixThe SQLite file holding events, sessions, and connector credentials. A relative path resolves against the file.
logstringerror for run, info for serveA RUST_LOG filter. $RUST_LOG wins over it.

substructure.toml uses substructure.db. subs.staging.toml uses subs.staging.db. Two files in one directory are two engines.

[llm.<id>]

Where a model call runs. An agent names a block by its id.

[llm.claude]
type = "anthropic"
api_key_env = "MY_ANTHROPIC_KEY"

[llm.byo]
type = "worker"
format = "anthropic"
KeyTypeDefaultMeaning
typeanthropic, openai, openrouter, workerrequiredWho makes the call.
api_key_envstringthe vendor's own variableThe variable holding the key. For an engine you run.
base_urlurlthe vendor's ownWhere to send the call.
formatopenai, anthropicthe engine's own shapeThe wire shape of the llm.execute a worker answers. type = "worker" only.

There is no default block and no fallback. An agent names a block, or its calls fail. See LLMs.

api_key_env names a variable on your machine. subs apply removes it. A deployment refuses a document that carries one.

[agent.<id>]

An agent. The id is what clients, channels, and parent agents route on.

[agent.support]
llm = "claude"
model = "claude-sonnet-4-5"
system = "You are a support agent."
mcp = ["sentry"]
sub_agents = ["researcher"]
tools = [{ name = "confirm", description = "Ask the human", handler = "client" }]
worker = "https://bot.example.com/agent"
signing_secret_env = "SUPPORT_SIGNING_SECRET"

[agent.support.retry]
tool = { max_attempts = 3 }
KeyTypeMeaning
llmstringThe [llm.<id>] block. Required when the section sets anything.
modelstringThe model. Required when the section sets anything.
systemstringThe system prompt.
descriptionstringWhat this agent does, shown to a parent that calls it.
mcplistConnections. An id, or { id, tools } to take fewer tools.
sub_agentslist of idsAgents this one can call.
toolslistBrowser tools. Each needs handler = "client".
workerurlWhere decisions go. Leave it off and the engine decides.
signing_secret_envstringThe variable holding the signing secret. For an engine you run.
retrytableTimeouts and attempts, per kind. See Retries.

An agent that sets nothing needs a worker. An agent that sets anything needs llm and model. See Agents.

The tools your worker runs are worker code. They do not go in the file.

[mcp.<id>]

An MCP server the engine connects to.

[mcp.sentry]
url = "https://mcp.sentry.dev/mcp"

[mcp.github]
url = "https://api.githubcopilot.com/mcp/"
auth = { token_env = "GITHUB_TOKEN" }
prefix_tools = false
KeyTypeDefaultMeaning
urlurlrequiredThe server.
authtablenone{ token_env = "…" } for a static credential. For an engine you run.
prefix_toolsbooltrueShow the model <id>__<tool> instead of the connection's own names.

A token written in the file is a parse error. Authorize a server with subs mcp login <id>. See Connectors.

[slack]

Where the bot answers. Every key defaults to silence.

[slack]
dm = "support"
mentions = "support"

[slack.channel.C0ENGOPS]
agent = "oncall"

[slack.channel.C0RANDOM]
off = true
KeyTypeMeaning
dmagent idAnswers direct messages.
mentionsagent idAnswers mentions in any channel channel does not name.
channel.<id>.agentagent idAnswers in that channel.
channel.<id>.offboolThe bot stays out of that channel.

Name a channel by id, never by name. A #name is a parse error. See Slack.

[run]

Defaults for subs run.

KeyTypeDefaultMeaning
agentagent idnoneWhich agent a bare subs run uses.
outputag-ui, jsonl, prettyag-uiHow to print the turn.

[serve]

Defaults for subs serve.

KeyTypeDefaultMeaning
hoststring127.0.0.1The address to bind.
portnumber8080The port.
authbooltrueClient and worker authentication. Set false only for a server nothing off this machine can reach.

[remote]

The deployment this file administers. That can be the hosted cloud, one you host, or another person's subs serve.

KeyTypeDefaultMeaning
urlurlhttps://api.substructure.aiThe API to talk to.
orgidnoneWritten by subs link or subs apply.
projectidnoneWritten by subs apply when it creates the project.

subs apply writes the pin back into the file and keeps your comments. A second apply changes nothing.

Precedence

flag > environment variable > substructure.toml > default

Setting a value in the file still lets you override it on the command line.

The message, --input, and --session have no key in the file. They say what one run does.

Secrets

The file names secrets. It never holds them.

SecretHow the file refers to it
Provider keyapi_key_env on the LLM block, for an engine you run. subs llm set-key for a deployment.
Signing secretsigning_secret_env on the agent, for an engine you run. The deployment creates its own.
Connector tokenauth.token_env on the connection, or subs mcp login.
Slack tokens$SLACK_APP_TOKEN and $SLACK_BOT_TOKEN, or subs slack connect.

subs apply strips api_key_env and signing_secret_env before it sends.

Next

  • Agents: what an agent section declares.
  • CLI: the commands that read this file.
  • Cloud: applying it to a deployment.
  • Protocol: the same types on the wire.