Skip to content

Creating agents

Agents live as subdirectories under agents_dir (default: ./agents/). Each agent needs two files.

name: sre-triage # must match the directory name
model: anthropic/claude-sonnet-4-6
max_tokens: 8192
tools: # MCP server names from mcp_servers config
- grafana
- atlassian
Field Required Description
name Yes Agent identifier. Must match the directory name. Referenced as agentId in requests.
model Yes Default model string. Can be overridden per-request via executor_config.model in VectorStep.
model_fallbacks No List of model strings to try, in order, if model exhausts its retries (see limits.llm_retry_attempts). Once a fallback succeeds, later iterations in the same run try it first.
max_tokens Yes Max output tokens per LLM call.
thinking_level No Default reasoning effort: off, minimal, low, medium, high, xhigh. Omit for no extended thinking. Overridable per-request via executor_config.thinking_level in VectorStep (see below).
tools No MCP server names, optionally scoped to specific tools (see below). Omit or leave empty for no tool access.
name: sre-triage
model: anthropic/claude-sonnet-4-6
model_fallbacks:
- anthropic/claude-haiku-4-5
- openrouter/deepseek/deepseek-chat
max_tokens: 8192

If anthropic/claude-sonnet-4-6 returns a retryable error (e.g. 529 overloaded), the gateway retries it llm_retry_attempts times with exponential backoff, then falls over to claude-haiku-4-5, then to the OpenRouter model if that also fails. Non-retryable errors (e.g. 400/401) skip the retry and fall over immediately.

By default, listing an MCP server name in tools: grants every tool that server exposes. To shrink the schema bloat in context (and the capability surface) for servers that expose dozens of tools, scope an entry down to a {server_name: [tool_a, tool_b]} mapping instead of a bare name:

tools:
- filesystem # every tool from filesystem
- atlassian: [jira_search, jira_get_issue] # only these two from atlassian

Tool names here are the unscoped MCP tool names (e.g. jira_search), not the namespaced server__tool form used internally — check GET /mcp/tools (see REST API) for the exact names a server exposes. Mixing scoped and unscoped entries in the same list is fine.

An agent whose job genuinely needs deliberation can say so once, in its own config, rather than relying on every calling pipeline step to remember:

name: sre-triage
model: anthropic/claude-sonnet-4-6
max_tokens: 8192
thinking_level: high

Precedence works exactly like model/model_override: a request that carries its own thinking_level (a step’s executor_config.thinking_level — see Executors) wins, and the agent’s value applies otherwise. Passing off per-request is therefore the escape hatch for running a high-effort agent cheaply on one step. Omitting the field everywhere leaves behaviour exactly as it was: no extended thinking.

Only the Anthropic provider acts on this today — it maps the level to a thinking budget. Every other provider logs a warning and ignores it, the same as it already does for the per-request form. OpenAI’s equivalent (reasoning_effort) is not wired up.

The system prompt. Written in Markdown, sent as the system message to the LLM on every call. For what makes a soul.md — and an agent’s scope and tool grants — actually good, see Writing good agents.

When agents are loaded (at startup and on every POST /reload / SIGHUP), the gateway validates each agent’s model and model_fallbacks against the configured providers:

  • Unrecognized prefix (e.g. my-custom/model) — logged as ERROR. The agent will load but every request will fail with a KeyError at runtime.
  • Known prefix, missing api_key (e.g. openrouter/... but providers.openrouter.api_key is empty) — logged as WARNING. The agent will load but requests will fail with auth errors.
  • thinking_level set on a provider that ignores it — logged as WARNING, once per agent. Names every affected model string, and distinguishes the two cases: if the agent’s primary model is on such a provider it will never use extended thinking at all; if only model_fallbacks are, it silently loses extended thinking the moment it fails over. Only Anthropic acts on thinking_level today (see Reasoning effort).

Local Ollama (ollama/...) is exempt from the api_key check — it requires no credentials by default.

These are warnings/errors in the log, not hard failures. All other agents continue to load normally. Check startup logs if an agent behaves unexpectedly at request time.

Terminal window
POST /reload # via HTTP
kill -HUP <pid> # via SIGHUP

Reloads all agent configs from disk without restarting. In-progress runs are unaffected. Validation runs against the reloaded agents on every reload.