Skip to content

48 · agent.yaml and gateway.yaml, key by key

Language support

The selector reads turns written in English and in Brazilian Portuguese (pt-BR). The language is chosen, never guessed from the turn: the console's Settings page (Language the selector reads), language on a plan request, or the settings.language of your vocabulary file; with none of them, English. A Portuguese turn may be typed with its accents or without them. European Portuguese is not a grammar the selector has, and a turn in any other language is unread.

This chapter lists every key the two files take, with what each one means. Every table is generated from the lists the stalwart-agents package checks a file against, so a key is here if and only if the package reads it. A key the package does not know is an error at its line, never ignored: a misspelt word is never a setting silently left at its default. Chapter 30 shows the files in use; this chapter is the place to look a key up.

The top of agent.yaml

Key What it says
agents the agents, by name; the first is the one a conversation starts with
tools where the tools come from: python: a module's public functions, mcp: a server's tools/list, openapi: each operation of a document, http: a web service's calls listed by hand, code: your own code in its own sealed space, or name: a tool's words
rulebook the Colang policy every tool call is checked against and the graph is proved against
facts what each fact the rulebook reads ($order_paid) is, read from a field of a tool's result: {from: get_order.status == paid, same: order_id} (chapter 45)
runs atomic (the default): a failed run is undone, last write first; stop: a failed run is left as it is
storage where runs and waiting confirmations are kept: sqlite:///runs.db, or postgres://... for several workers; in memory where not given
stores the stores the tools read and write, such as a memory or a search index, and how each is shared
budget limits on a run's loop, which the bug hunter's rule on unbounded loops reads
modules the modules code a model writes may import, which the bug hunter's rules on model-written code read
replies the reply check's settings (chapter 42)
answers grounded answers from your documents (chapter 44)
job the bot's job description: title, purpose, whom it serves (customers, staff or both), its manager and escalates_to (each a role), its team and its working hours
tasks the bot's tasks, by name: each with its title, the tools it uses, its output (reply, document, draft or action), where it starts on the ladder (watch or suggest), the triggers that start it, and whether its changes run in_order or all_at_once (steps)
triggers how work arrives by itself, by name: each of kind email, form, schedule, webhook, change or message; a schedule's timing is around, exact or always_on; a change's where takes conditions or a filter; a message arrives in teams, slack, whatsapp, instagram, messenger or sms, on an account
ladder the bar a task must clear to move up the ladder (proposals 50, agreement 0.95 and drift_free_days 14 at the least: raised, never lowered) and the sample of Act-and-tell runs a person reviews
drift when past work is replayed to watch for drift (replay: weekly or on_model_change) and the thresholds past which a signal is reported
site the bot's own page: on, off, or its title and language

job, tasks, triggers, ladder, drift and site describe the bot's work for the host that runs it (below); the package checks them and passes them on.

An agent

Under agents:, each agent by its name:

Key What it says
agents.<name>.model the agent's model, provider:model, used with your own key from your environment; leave it out and no model is called
agents.<name>.tools the tools this agent may call, by name
agents.<name>.hands_off_to the agents this one may pass the conversation to
agents.<name>.on_unread what a turn nothing reads does, once the person was asked to say it again: refuse, model, ask or answer; the console's setting where not given
agents.<name>.privilege low, standard or high: what a hand-off from this agent is trusted with
agents.<name>.remote how a hand-off to this agent travels when it runs elsewhere, and how that channel is authenticated
agents.<name>.budget this agent's own limits on a run: max_steps, max_tool_calls and max_model_calls, in place of the file's
agents.<name>.system what the model is told before the conversation
agents.<name>.job this agent's own job description, in the words of the top-level job
agents.<name>.defaults values the tools' arguments take wherever a turn gives none, by parameter name -- the account a tool acts on, say -- so the router never asks for them

budget

At the top of the file, or on an agent in place of the file's:

Key What it says
budget.max_steps the most steps one run takes; on an agent, that agent's own, else the file's, else 60. The bug hunter's rule on unbounded loops (QG023) reads it
budget.max_tool_calls the most tool calls one turn makes; the call past it is not made, and the turn stops with BudgetSpent
budget.max_model_calls the most model calls one turn makes; the call past it is not made, and the turn stops with BudgetSpent

remote

Key What it says
agents.<name>.remote.transport how a hand-off to this agent travels when it runs elsewhere, such as a2a
agents.<name>.remote.authentication how the far side proves who it is, such as mtls; a remote agent with none is QG107

Tools and where they come from

Each entry of tools: is one source, named by the key that says what kind it is, or a tool's own words under name:. An entry of each kind takes only the keys in its table.

python:

Key What it says
tools[].python a module of your own, imported into the agent's process and trusted; every public function is a tool, its schema from its type hints

mcp:

Key What it says
tools[].mcp an MCP server's address, or a file listing its tools; each tool it lists is a tool, with the hints the server gives
tools[].url the address calls go to, where mcp: names a file that lists the tools
tools[].version the server version the tools are pinned to
tools[].digest the digest of the server's tool list the tools are pinned to
tools[].headers headers sent on every request, each ${NAME} read from your environment when the request is made; how an agent sends its gateway token
tools[].receipts true behind a gateway that admits each call on the agent's own check, which then travels with the call as a receipt
tools[].tools only these of the server's tools; every one where not given

openapi:

Key What it says
tools[].openapi an OpenAPI document; each operation is a tool, GET and HEAD reads, any other method a write unless its x-stalwart says otherwise
tools[].headers headers sent on every request, each ${NAME} read from your environment when the request is made; how an agent sends its gateway token
tools[].receipts true behind a gateway that admits each call on the agent's own check, which then travels with the call as a receipt

An operation may say in its x-stalwart extension what its document cannot, with the tool words below and one of its own:

Key What it says
x-stalwart.undo names the tool that undoes this write; a failed run calls it, checked like any call
x-stalwart.irreversible the write cannot be undone; its confirmation says so, and a rollback leaves it in place
x-stalwart.retry_safe calling it twice with the same arguments does what calling it once does, so a call whose outcome is unknown may be retried
x-stalwart.reads_outside what it returns comes from outside, an e-mail or a web page, and is untrusted; what follows it carries that mark
x-stalwart.sends_out it may send to a URL or another destination; a destination argument is refused on any tool that does not say this
x-stalwart.after the tool runs only after each named tool, in the same session
x-stalwart.not_after the tool never runs after a named tool, or after anything from outside where it names untrusted
x-stalwart.at_most the tool runs at most this many times in a session; {count, per} and a list of those, one per period, are checked when the file is read, not yet applied
x-stalwart.requires_privilege only an agent with at least this privilege may call it
x-stalwart.argument_kinds what an argument is, such as code, sql, path or url, which the bug hunter's rules on model-written values read
x-stalwart.confidential what the tool returns is confidential, and to whom
x-stalwart.subject_argument the argument naming whose data the tool returns
x-stalwart.idempotency {header: <name>}: the header a call's idempotency key is sent in
x-stalwart.also_called words a person uses for what the tool acts on, read as the words of its name are

http:

Key What it says
tools[].http the address of a REST API with no OpenAPI document, starting https://
tools[].headers headers sent on every request, each ${NAME} read from your environment when the request is made; how an agent sends its gateway token
tools[].operations the API's calls, listed by hand, each a tool (the next table)
tools[].receipts true behind a gateway that admits each call on the agent's own check, which then travels with the call as a receipt

Each of its operations:, besides any tool word:

Key What it says
tools[].operations[].name the tool's name
tools[].operations[].method GET, a read, or POST, PUT, PATCH or DELETE, each of which must say whether it writes
tools[].operations[].path the path after the API's address
tools[].operations[].schema the arguments' JSON Schema; beside it, any tool word

code:

Key What it says
tools[].code your own code, to run isolated in its own sandbox, each function a tool; a folder beside agent.yaml, or a Git URL pinned to a commit. Checked and kept; running it in its sandbox is still to come
tools[].language the code's language; python, for now
tools[].requirements the file of pinned requirements (name==1.2.3), installed when the sandbox is built
tools[].functions only these functions; every public one where not given
tools[].state a folder inside the project kept between calls
tools[].mode call, one run per tool call, or service, which the host keeps running
tools[].sandbox the sandbox's bounds (the sandbox table)

Its sandbox::

Key What it says
tools[].sandbox.level standard, the default, which may only tighten the standard bounds, or advanced, which lets the owner widen them, each widening accepted by a person on the host
tools[].sandbox.network the hosts the code may reach; none where not given; any at the advanced level only
tools[].sandbox.secrets environment variables passed into this sandbox only, by name, never by value; never one of Stalwart's own
tools[].sandbox.limits the time, memory and processes a run may use (the limits table)
tools[].sandbox.mounts host folders the code may see, at the advanced level only
tools[].sandbox.allow the relaxations the host's allow list names, at the advanced level only

The sandbox's limits::

Key What it says
tools[].sandbox.limits.seconds the seconds a run may take; 60 at most at the standard level, and 0, no limit, only with mode service
tools[].sandbox.limits.memory_mb the memory a run may use, in megabytes; 1024 at most at the standard level
tools[].sandbox.limits.cpus the processors a run may use; 1 at most at the standard level
tools[].sandbox.limits.processes the processes a run may start; 32 at most at the standard level

Each of its mounts::

Key What it says
tools[].sandbox.mounts[].path the host folder; never the host's root, its system folders, Stalwart's own or the project's, and never a relative path
tools[].sandbox.mounts[].write true where the code may write there

The floor. These are refused at every level, wherever they appear, because they would reach Stalwart itself or the owner's other bots:

Key What it says
privileged a privileged container
capabilities added Linux capabilities
devices the host's devices
docker_socket the Docker socket
host_network the host's network
gate switching off the gate
audit switching off the audit
stop_button switching off the stop button

A tool's words

Under name:, or as @tool(...) in Python, or beside an http: operation:

Key What it says
writes the tool changes something, so its call is checked against your rules, and waits for a person where they say so or where nothing allows it
undo names the tool that undoes this write; a failed run calls it, checked like any call
irreversible the write cannot be undone; its confirmation says so, and a rollback leaves it in place
retry_safe calling it twice with the same arguments does what calling it once does, so a call whose outcome is unknown may be retried
reads_outside what it returns comes from outside, an e-mail or a web page, and is untrusted; what follows it carries that mark
sends_out it may send to a URL or another destination; a destination argument is refused on any tool that does not say this
after the tool runs only after each named tool, in the same session
not_after the tool never runs after a named tool, or after anything from outside where it names untrusted
at_most the tool runs at most this many times in a session; {count, per} and a list of those, one per period, are checked when the file is read, not yet applied
confirm_with what a person is asked before the call runs, in your words; an irreversible tool that gives none is asked about as Run <tool>? Nothing undoes it once it has run.
requires_privilege only an agent with at least this privilege may call it
description what the tool does, which the selector reads turns against; the docstring's first line where not given
checks conditions each call must meet, such as a quantity at most what is in stock; checked when the file is read, not yet applied to a call
filter on a read, which records it hands on: each names the list, keep the conditions each record meets; checked when the file is read, not yet applied
spend the most a field may add up to per period, tools with the same pool sharing one count; checked when the file is read, not yet applied
currency the currency the tool's amounts are in, as USD or BRL; required with spend
outcome on a write, the read that follows it to its end and what done and failed look like; checked when the file is read, not yet applied
argument_kinds what an argument is, such as code, sql, path or url, which the bug hunter's rules on model-written values read
sandboxed the code the tool runs is contained, which lowers the severity of a finding about it
sanitised_by the check that cleans an argument before the tool uses it
confidential what the tool returns is confidential, and to whom
confidential_fields which fields of what it returns are confidential
subject_argument the argument naming whose data the tool returns
retrieves the store the tool reads from, and the argument its reads are scoped by
persists the store the tool writes into
source where the tool's code comes from, which the bug hunter's rule on unpinned sources reads
server the MCP server that serves the tool, with its version or digest
also_called words a person uses for what the tool acts on, read as the words of its name are

Conditions and limits on a tool

A tool's checks, its filter, its at_most, its spend and its outcome share one grammar. Each condition is about one thing and has exactly one operator:

Key What it says
checks[].field the value the condition is about: an argument, or a field of the result, by name or dotted path, as order.total; a path through a list, as items[].price, holds only where every item does
checks[].total the sum over a list's items, as legs[].cost; it compares as a number
checks[].count how many items a list holds, as legs[]; it compares as a number
checks[].any a list of conditions, any one of which is enough; a plain list of conditions means all of them
checks[].is the value equals this
checks[].is_not the value differs from this
checks[].one_of the value is one of a list, or of what an earlier call returned, as {from: get_slots.slots[].start}
checks[].none_of the value is none of a list, or of what an earlier call returned
checks[].at_least the number is this or more; the operand may be {from: <tool>.<field>}, what an earlier call in the run returned
checks[].at_most the number is this or less, as a quantity at most {from: check_stock.in_stock}
checks[].under the number is less than this
checks[].over the number is more than this
checks[].between the number, date or time is within two bounds, lowest first, as [10, 20]
checks[].before the date or time is earlier than this, written as 2026-10-16, 17:30 or 2026-10-16T17:30
checks[].after the date or time is later than this
checks[].contains the value contains this text
checks[].because the reason in your own words, shown when the condition stops something
checks[].unknown what a value nobody could read does: fail, the default, or pass, which only a filter and a change trigger's where may say, never a check that lets a call through

filter:, on a read:

Key What it says
filter.each the list in the result whose records the read hands on, ending in [], as jobs[]
filter.keep the conditions each record must meet to be handed on; inside them, {from_row: <field>} is another field of the same record

at_most: is a number of calls per session, or one of these, or a list of them:

Key What it says
at_most.count the most calls in the period, a whole number from 1
at_most.per the period counted over: call, run, session, minute, hour, day, week or month; one limit per period

Each of spend:, with the tool's currency:

Key What it says
spend[].field the amount's field, as amount, or legs[].cost for the items together
spend[].at_most the most the amounts may add up to in the period, a number above 0, in the tool's currency
spend[].per the period counted over, as day or month; the same periods as at_most
spend[].pool a name; tools with the same pool share one counter

outcome:, on a write:

Key What it says
outcome.follow the read tool that reports how the write ended; required, and it must be a declared tool that does not write
outcome.same the field that links the write and the read, as order_id
outcome.done_when the conditions on the read's result that say the write is done; required
outcome.failed_when the conditions on the read's result that say the write failed
outcome.give_up_after the minutes to wait, at least 1, before the outcome is unknown

Stores and modules

Each of stores:, by the store's name:

Key What it says
stores.<name>.persistent true where what the store holds outlives a run, which the hunter's rules on planted text read (QG021)
stores.<name>.multi_tenant true where several tenants share the store, so what one writes another may read

Each of modules:, by the module's path:

Key What it says
modules.<path>.version the version a Colang action module is pinned to
modules.<path>.digest the digest a Colang action module is pinned to

Facts

Each of facts:, by the name the rulebook reads it under:

Key What it says
facts.<name>.from the tool result the fact is read from, as tool.field == value or tool.field; never a tool that reads outside
facts.<name>.same the argument, list of arguments, or mapping of arguments that ties the fact to the call being checked, so one order's status never allows a refund of another

Replies and answers

replies::

Key What it says
replies.rules the reply rules file, as the console's Reply rules page saves it
replies.phrasings the outcome phrasings file
replies.composer model: the model writes the reply and the check holds it; template: the reply is the phrasings, and no model writes it
replies.language the default phrasings' language: en or pt

answers::

Key What it says
answers.index the source index stalwart sources build writes
answers.k at most this many passages; 3 where neither the file nor the console says
answers.floor no passage scoring below this is used, from 0 to 1; 0.3 where neither says
answers.composer extract, the documents' own sentences; or compose, your model writes from them

The bot's work: job, tasks and triggers

These keys describe the bot to the host that runs it. The package checks each of them when it reads the file; what each one does is the host's.

Key What it says
job the bot's job description: title, purpose, whom it serves (customers, staff or both), its manager and escalates_to (each a role), its team and its working hours
tasks the bot's tasks, by name: each with its title, the tools it uses, its output (reply, document, draft or action), where it starts on the ladder (watch or suggest), the triggers that start it, and whether its changes run in_order or all_at_once (steps)
triggers how work arrives by itself, by name: each of kind email, form, schedule, webhook, change or message; a schedule's timing is around, exact or always_on; a change's where takes conditions or a filter; a message arrives in teams, slack, whatsapp, instagram, messenger or sms, on an account
ladder the bar a task must clear to move up the ladder (proposals 50, agreement 0.95 and drift_free_days 14 at the least: raised, never lowered) and the sample of Act-and-tell runs a person reviews
drift when past work is replayed to watch for drift (replay: weekly or on_model_change) and the thresholds past which a signal is reported
site the bot's own page: on, off, or its title and language

job

Key What it says
job.title the job's name
job.purpose what the bot is for, in a sentence
job.serves whom it serves: customers, staff or both
job.manager the role that manages it; the host names the person
job.team who gives it work and who approves (the team table)
job.hours its working hours (the hours table)
job.escalates_to the role a stuck card goes to

Its team::

Key What it says
job.team.gives_work the roles that give it work
job.team.approves the roles that approve what it proposes

Its hours::

Key What it says
job.hours.days the working days, as mon to sun
job.hours.from the time work starts, as HH:MM
job.hours.to the time work ends, as HH:MM
job.hours.time_zone the time zone the hours are in, such as Europe/London

tasks

Each task, by its name:

Key What it says
tasks.<name>.title the task's name in words; required
tasks.<name>.tools the tools the task uses, each declared under tools
tasks.<name>.agent the agent that does it; the first where not given
tasks.<name>.serves whom the task serves: customers, staff or both
tasks.<name>.output what it produces: reply, document, draft or action
tasks.<name>.starts_at the ladder level it starts at: watch or suggest; no task starts higher
tasks.<name>.triggers the triggers that start it, each declared under triggers
tasks.<name>.run_window minutes a sleeping host may still start a due run, from 1 to 1440
tasks.<name>.form true where people may start the task by hand
tasks.<name>.steps in_order, or all_at_once: every write lands together, or none does

triggers

Each trigger, by its name, is one kind and the keys that kind takes:

Key What it says
triggers.<name>.kind how work arrives: email, form, schedule, webhook, change or message; it decides the keys beside it
triggers.<name>.inbox the inbox's name
triggers.<name>.urgent true where the work does not wait for working hours
Key What it says
triggers.<name>.kind how work arrives: email, form, schedule, webhook, change or message; it decides the keys beside it
triggers.<name>.audience who fills the form in: customers or staff
triggers.<name>.urgent true where the work does not wait for working hours
Key What it says
triggers.<name>.kind how work arrives: email, form, schedule, webhook, change or message; it decides the keys beside it
triggers.<name>.at the time it is due, as HH:MM
triggers.<name>.days the days it is due, as mon to sun
triggers.<name>.timing around the time, exact, or always_on
triggers.<name>.urgent true where the work does not wait for working hours
Key What it says
triggers.<name>.kind how work arrives: email, form, schedule, webhook, change or message; it decides the keys beside it
triggers.<name>.secret the signing secret's environment variable, as ${NAME}; never the secret itself
triggers.<name>.urgent true where the work does not wait for working hours
Key What it says
triggers.<name>.kind how work arrives: email, form, schedule, webhook, change or message; it decides the keys beside it
triggers.<name>.tool the tool it polls; it names this or resource, not both
triggers.<name>.resource the resource it subscribes to; it names this or tool, not both
triggers.<name>.every minutes between polls, from 1 to 1440
triggers.<name>.where conditions on the polled result's fields, such as {field: open_count, at_least: 1}
triggers.<name>.urgent true where the work does not wait for working hours
Key What it says
triggers.<name>.kind how work arrives: email, form, schedule, webhook, change or message; it decides the keys beside it
triggers.<name>.in the channel: teams, slack, whatsapp, instagram, messenger or sms
triggers.<name>.account the page, number or workspace: a name, or ${NAME} for one the host holds
triggers.<name>.urgent true where the work does not wait for working hours

ladder, drift and site

Key What it says
ladder.bar what a task must show to move up a level: proposals, agreement and drift_free_days, raised, never lowered below 50, 0.95 and 14
ladder.sample the share of Act-and-tell runs a person reviews; 0.1 where not given
Key What it says
drift.replay weekly, or on_model_change only
drift.thresholds how far each signal may move before it is drift: tools, plan_shape, decisions, plan_lock_stops, refusals, untraced_claims
drift.baseline_days the days of runs the signals are compared against, from 1 to 90
Key What it says
site.title the site's title
site.language the site's language: en or pt
site.colour the site's main colour
site.logo the site's logo

A template

A template is an agent.yaml a person starts from. It adds:

Key What it says
first_bot true where the template starts Make my first bot
needs the catalogue entries the template uses, each with its sign-in (the next table)
optional entries it offers and works without

Each of its needs::

Key What it says
needs.<name>.entry the catalogue entry's id; required
needs.<name>.sign_in how the entry signs in: oauth, service or none
needs.<name>.why why the template needs it, in a sentence the owner reads
needs.<name>.route how its data is reached: api, aggregator, owner_feed or owner_browser

gateway.yaml

The gateway (chapter 37) reads these from the file it is started with, and refuses to start on any other:

$ stalwart gateway --config gateway.yaml
Key What it says
catalogue the tenant's catalogue the gateway checks every call against, in the extracted-model format; required
upstreams which MCP server holds each tool, by tool name; "*" for the rest
agents one token per agent, each ${NAME} from the environment; every agent connects with its own, and at least one is required
admin_token the token the rollback route takes; the route is off where it is not set
audit the file every decision is appended to
elicitation_timeout seconds a person has to answer a question the call asks; 600 where not given
listen the address and port it serves on; 127.0.0.1:8800 where not given

The limits

  • A key's table says what the package reads. Some keys are checked and kept but not yet acted on -- checks, filter, spend and outcome on a tool, and a code: source's code, which is not yet run -- and their rows say so.
  • The founding keys are checked here and acted on by the host that runs the bot.