Agents

Updated Aug 28, 2026View as Markdown

An agent is a model, a prompt, and a set of tools. You declare one with an [agent.<id>] section.

subs.toml
[llm.openrouter]
type = "openrouter"

[agent.oncall]
llm = "openrouter"
model = "anthropic/claude-sonnet-4-5"
system = "You are the on-call assistant."

The ID is how everything else names the agent. Slack routes to it. A client submits to it. A parent agent calls it.

Agent keys

KeyMeaning
llmWhich [llm.<id>] block the model call runs on.
modelThe model to call.
systemThe system prompt.
effortHow hard the model thinks.
descriptionWhat this agent does, for a parent that calls it.
mcpConnections whose tools the model can call. See Connectors.
pluginsPlugins whose skills and servers this agent gets. See Plugins.
subagentsOther agents this one can call. See Subagents.
toolsTools the browser runs. See Client-side tools.
workerWhere decisions go. See Workers.
retryTimeouts and attempts. See Retries.
signing_secret_envThe variable holding the signing secret, for an engine you run.

Config has the full reference, including the defaults and the table forms of mcp and plugins.

Declare more than one agent

A second agent is a second section. Each one names its own model.

subs.toml
[llm.claude]
type = "anthropic"

[llm.cheap]
type = "openai"

[agent.assistant]
llm = "claude"
model = "claude-sonnet-4-5"
system = "Delegate research to the researcher, then answer."
subagents = ["agent.researcher"]

[agent.researcher]
description = "Finds and reads sources."
llm = "cheap"
model = "gpt-5-mini"
system = "Answer with citations. Be brief."

The model sees researcher as a tool. The child runs in its own session with its own transcript and cost.

Choose who decides

worker sets who answers this agent's decisions.

workerResult
SetThe engine POSTs every decision to that URL.
Not setThe engine decides by accepting its own proposal.

Routing is per agent. An engine-hosted parent can call a worker-hosted child.

subs.toml
[agent.triage]
llm = "claude"
model = "claude-haiku-4-5"
worker = "https://triage.internal/agent"

Let a worker write the config

A section does two things: it declares that the agent exists, and it can set the agent's config. An agent whose worker builds its own config needs only the first.

subs.toml
[agent.support]
worker = "http://localhost:4000/substructure/agent"

That section sets no config, so session.start carries no proposal and the worker declares the whole agent.

Two rules follow:

  • An agent that sets no config needs a worker.
  • An agent that sets any config needs llm and model.

A partial config is a parse error.

Where tools come from

SourceRuns onDeclared in
A connectorThe enginemcp on the agent
A pluginThe engineplugins on the agent
Your codeYour workerThe config the worker returns
The browserThe clienttools on the agent, with handler = "client"

Only browser tools go in the file. The tools that your worker runs are worker code.

Next steps

  • Connectors: tools from Sentry, GitHub, and any MCP server.
  • Plugins: skills and servers from a plugin directory.
  • Workers: decide with your own code.
  • Subagents: agents that call agents.
  • Config: every key in the file.