Declarative Reference
Every agent.md field, default, accepted value, and typed exception.
This page is the canonical and complete authoring contract for agent.md.
Compiler behavior, CLI output, examples, and every other developer guide must
conform to this reference. It answers four
questions for every field:
- What happens when I omit it?
- What values compile?
- What may I override?
- Where does configuration that is not accepted here belong?
Canonical plugin-selection grammar
Every installable feature is delivered by an Agent Plugin. Its location in
agent.md declares the role in which the plugin is activated; authors do not
repeat that role in a generic plugin envelope.
| Role cardinality | Defaults | Typed deviations |
|---|---|---|
Singular (deploy, state, blob, sandbox, secrets, observability, context) | Scalar plugin name: state: postgres | One-key mapping: state: { postgres: { connectionEnv: DATABASE_URL } } |
Plural (channels, capabilities) | List of plugin names: channels: [slack, photon] | Name-to-options mapping: channels: { slack: {}, photon: { audio: openai } } |
The normalized compiler representation is { name, options } for a singular
role and a list of those references for a plural role. These authoring forms
are intentionally not accepted:
channels: slack # plural fields are never scalars
channels: slack, photon # comma-separated strings are not lists
state: [postgres] # singular fields are never lists
state:
use: postgres # there is no use/with wrapper
with:
connectionEnv: DATABASE_URLThe rules are uniform:
- Omit a field to use its documented default.
- Use a scalar or list when selected plugins keep their defaults.
- Use a mapping only when a selected plugin needs a schema-validated deviation.
- An empty mapping or null entry means “use this plugin's defaults.”
- Unknown fields, plugin names, and options fail compilation.
- Routes, packages, credentials, capabilities, and provider metadata stay with the plugin that owns them.
plugins.lock is the dependency and authority lock for every installed Agent
Plugin. capabilities: is different: it activates packages that contribute
tools, connections, Skills, hooks, or other optional executable capabilities.
A plugin selected under channels: or any singular typed field is installed
and locked, but is not repeated under capabilities:. Channel selection also
activates tools owned by that channel plugin. Optional connections remain
explicit capabilities, even when the same package contributes both a channel
and a connection. The same typed-selection rule applies to qualified context,
observability, composition, and automation-lifecycle references.
Inspect the complete result without copying it back into source:
assembly-line validate agent
assembly-line inspect agent --resolvedFind the change
| You need to change | Use |
|---|---|
| Identity, model, reasoning, or loop limit | Identity and model policy |
| Deploy target, database, blobs, sandbox, or secrets | Infrastructure and Secrets |
| Ingress, delivery, attachments, or audio transcription | Channels |
| Tools, connections, hooks, context, or another executable capability | Capabilities |
| A timer or provider event response | Automations |
| Runtime-created Skills, automations, or connections | Mutability |
| Turn-specific instructions, reasoning, model, sandbox, or tool availability | Composition |
| Conversation history and transcript limits | Context |
| Final response shape | Structured output |
| Telemetry and content capture | Observability |
| Delegation | Subagents |
Smallest valid agent
model and a non-empty Markdown body are the only required author inputs:
---
model: openrouter/openai/gpt-5.4-mini
---
You are a concise assistant.Kernel-local deploy, state, blob, and sandbox implementations plus process-env
secrets and the default context are selected automatically. Scheduling is
kernel orchestration over the chosen state store, not an authoring field or a
fake provider. No channels, capabilities, automations, subagents, external
telemetry, or structured-output constraint are added. The model prefix still
requires its installed and locked model plugin; assembly-line init supplies
the OpenRouter plugin.
Set id before production use. Without it, durable state is scoped to the
content-derived agent revision and a source change creates a new scope.
agent.md fields
| Field | Type | Omitted behavior |
|---|---|---|
model | Non-empty provider/model string | Required. Compilation fails. |
id | Non-empty string | Durable scope uses agentRevision. |
name | Non-empty string | No authored display name. |
description | Non-empty string | No authored description. Required in every listed subagent. |
reasoning | off, minimal, low, medium, high, xhigh, or max | No effort is authored. Pi uses medium; another harness uses its own default. |
maxReasoning | Same values as reasoning | No authored ceiling. When set, it must be at least reasoning. |
maxIterations | Positive integer | Runtime limit is 25 model iterations. |
audienceIsolation | Boolean | false. |
deploy | Deploy plugin | local. |
state | State plugin | local. |
blob | Blob plugin | local. |
sandbox | Sandbox plugin | local, mounted at /workspace. |
channels | Channel selection | No public channel. |
timezone | IANA timezone string | UTC. |
capabilities | Published capability selection | No published capability plugins. Local plugins activate by presence. |
automations | Automation mapping | None. |
mutability | static or policy mapping | Skills and automations are autonomous; external connections are disabled. |
composition | Rule list or plugin contribution | The top-level model, reasoning, instructions, sandbox, and capabilities apply to every turn. |
subagents | List of child directory names | None. |
outputSchema | Inline JSON Schema or contained .json path | Final output is unconstrained. |
secrets | env, 1password, or 1Password mapping | Process environment. |
observability | otlp, OTLP mapping, or plugin contribution | No external sink; model content capture is off. |
context | default, default-context mapping, or plugin contribution | Built-in defaultContext. |
There is no top-level metadata, media, tools, hooks, connections,
runtime, infrastructure, scheduler, defaultChatModel, privacy, or raceGoal
field. Put a setting in the typed field that consumes it. Put executable
behavior in a plugin.
The YAML parser accepts YAML 1.2 core values. Duplicate keys, aliases, anchors,
merge keys, and explicit tags fail. agent.md is limited to 1 MiB,
frontmatter to 256 KiB, 10,000 YAML nodes, and 64 levels.
Identity and model policy
Add only the identity or model policy the product needs:
id: coachgpt
name: CoachGPT
description: Reviews training and recovery data.
model: openrouter/openai/gpt-5.4-mini
reasoning: medium
maxReasoning: high
maxIterations: 18
audienceIsolation: truemodel is declared once. Conditional model changes belong under
composition; plugins may declare the models their composition contribution
can select. defaultChatModel is not an authoring field.
The first path segment is a plugin-owned provider ID. Compilation resolves it
from exactly one installed and locked models contribution, obtains static
metadata from that plugin, and packages only the selected provider graph. The
runtime imports the same package's assemblyLineModelProvider registration
into the otherwise provider-neutral Pi loop. An unknown provider needs no
compiler, Pi, Node, runtime, or CLI registry edit.
The official model plugins are:
| Prefix | Package | Authentication | Notes |
|---|---|---|---|
openrouter/ | @assemblyline-agents/openrouter | OPENROUTER_API_KEY or generic interactive credential storage | Default scaffold; owns dynamic discovery, video payloads, max reasoning, embeddings, and OpenRouter usage reconciliation. |
openai/ | @assemblyline-agents/openai | OPENAI_API_KEY or generic interactive credential storage | Owns OpenAI discovery, embeddings, and usage reconciliation. |
openai-codex/ | @assemblyline-agents/openai-codex | Pi-native browser or device-code OAuth | Wraps Pi's standard openaiCodexProvider(); uses ChatGPT subscription billing and the generic durable model-credential store. It does not use the removed custom Codex app-server harness. |
Install and select from the plugin declaration:
assembly-line add openrouter agent --role model
assembly-line add openai-codex agent --role model
assembly-line auth openai-codex agentplugins.lock pins the selected package authority. Subagents that choose the
same provider reuse the host's provider credential store; credentials never
enter agent.md, the lockfile, or model context.
Infrastructure
A clean hosted selection stays one line per concern:
deploy: hetzner
state: postgres
blob: r2
sandbox: e2bThe built-in runtime is always Node. Provider mappings accept only the options
listed below. packageName, configuration/credential declarations,
capabilities, generic options, and credential values are resolved provider
data, not agent fields.
The framework kernel owns only the local selections and process-env secrets.
Scheduling remains kernel orchestration over the chosen state adapter and is
controlled operationally, not through agent.md. Every other infrastructure name
must resolve from exactly one installed, locked Agent Plugin providers
contribution. That package owns the public name, role, runtime kind, package
binding, defaults, and JSON Schema for deviations; adding a provider does not
require a compiler registry edit.
Deploy
| Plugin | Runtime requirement | Scalar behavior |
|---|---|---|
local | None | Runs the Node host on the current machine. |
docker | Docker | Builds an image. It starts a container only when deploy serve mode is enabled. |
fly | FLY_API_TOKEN and an app from FLY_APP_NAME or app | Deploys one durable Fly Machine. |
hetzner | A named Hetzner host inventory entry | Publishes to the selected host with blue-green containers. |
railway | RAILWAY_TOKEN and a linked or selected project/service | Publishes the Node service to Railway. |
deploy.docker
| Option | Type | Default |
|---|---|---|
image | Non-empty string | assembly-line:<first 12 build-revision characters> |
dockerBin | Non-empty string | docker |
serve | Boolean | false |
port | Positive integer | 3000 when serve: true |
cwd | Non-empty string | Current working directory |
volumeName | Non-empty string | Generated from the agent and deploy environment |
containerName | Non-empty string | Generated from the agent and deploy environment |
deploy:
docker:
serve: true
port: 3100deploy.fly
| Option | Type | Default |
|---|---|---|
app | Non-empty string | FLY_APP_NAME; required if the environment does not set it |
region | Non-empty string | FLY_REGION, then iad |
flyBin | Non-empty string | flyctl |
org | Non-empty string | FLY_ORG, otherwise unset |
internalPort | Positive integer | 3000 |
detach | Boolean | false |
cwd | Non-empty string | Current working directory |
volumeName | Non-empty string | assembly_line_data |
memory | Non-empty Fly memory string | Unset |
vmSize | Non-empty Fly VM size | Unset |
autoStop | Boolean | false |
minMachinesRunning | Positive integer | 1; 0 when autoStop: true |
deploy:
fly:
app: coachgpt
region: ord
memory: 2gbdeploy.railway
| Option | Type | Default |
|---|---|---|
project | Non-empty string | RAILWAY_PROJECT_ID, otherwise the linked project |
service | Non-empty string | RAILWAY_SERVICE_ID, otherwise the linked or only application service |
railwayBin | Non-empty string | railway |
json | Boolean | false |
detach | Boolean | false |
cwd | Non-empty string | Current working directory |
deploy.hetzner
deploy: hetzner names the plugin. Select the host with
deploy.hetzner.host or ASSEMBLY_LINE_HETZNER_HOST.
The inventory, not agent.md, owns the address, SSH user and key, pinned host
key, provider resource ID, region, and ingress namespace. The default inventory
file is assembly-line.hosts.json; override it with
deploy.hetzner.hostsFile or ASSEMBLY_LINE_HETZNER_HOSTS_FILE.
| Option | Type | Default |
|---|---|---|
host | Non-empty inventory name | ASSEMBLY_LINE_HETZNER_HOST; required before deploy |
environment | Non-empty environment name | development |
hostsFile | Non-empty path | Nearest assembly-line.hosts.json |
expectedRegion | Non-empty string | No extra region assertion |
ingress.visibility | public or private | Inventory policy |
resources.cpus | Positive number | 1 |
resources.memory | Docker memory string, such as 2g | 1g |
resources.pids | Positive integer | 256 |
database.mode | external or host | external |
monitoring.enabled | Boolean | true |
monitoring.diskFreeMinimumMb | Positive integer | 5120 |
caddyImage | Explicit non-latest image tag | caddy:2.10.0-alpine |
postgresImage | Explicit non-latest image tag | postgres:17.10-alpine |
awsCliImage | Explicit non-latest image tag | amazon/aws-cli:2.17.57 |
deploy:
hetzner:
environment: production
ingress:
visibility: private
resources:
cpus: 2
memory: 2g
database:
mode: host@assemblyline-agents/vps is the plugin package for the hetzner deploy
selection. vps is not a declarative selection name.
State
| Plugin | Connection | Provider defaults |
|---|---|---|
local | None | Local state; no options. |
postgres | DATABASE_URL | TLS enabled; certificate verification enabled. |
neon | DATABASE_URL | Same Postgres contract, tagged as Neon. |
supabase | DATABASE_URL | Same Postgres contract, tagged as Supabase. |
railway | Railway database reference | databaseService: Postgres, provision: true, sslRejectUnauthorized: false. |
All non-local profiles compile to the Postgres runtime. Use state: postgres
for an ordinary database URL. Neon, Supabase, and Railway are placement presets,
not separate state implementations.
| Option | Profiles | Type | Default |
|---|---|---|---|
connectionEnv | All non-local | Non-empty environment variable name | DATABASE_URL |
ssl | All non-local | Boolean | true, unless the URL contains sslmode=disable |
sslRejectUnauthorized | All non-local | Boolean | true; Railway uses false |
optionalMigrations | All non-local | Boolean or list of migration IDs | false |
databaseService | Railway only | Non-empty string | Postgres |
provision | Railway only | Boolean | true |
state:
postgres:
connectionEnv: COACHGPT_DATABASE_URL
sslRejectUnauthorized: trueBlob
Blob plugins currently accept no authored options. Bucket names, endpoints, credentials, prefixes, and public URLs come from environment configuration.
| Plugin | Required environment | Default |
|---|---|---|
local | None | Local blob storage. |
r2 | R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET | R2 S3-compatible endpoint; empty prefix; no public URL. |
s3 | S3_BUCKET, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY | AWS S3 endpoint; empty prefix; no public URL. |
Sandbox
| Plugin | Required environment | Default image | Network default |
|---|---|---|---|
local | None | Host process | Host network |
docker | Docker | node:22-slim | none |
daytona | DAYTONA_API_KEY | node:22-slim | Daytona default |
e2b | E2B_API_KEY | E2B default template | Internet disabled |
modal | MODAL_TOKEN_ID, MODAL_TOKEN_SECRET | node:22-slim | Modal default |
Every plugin uses /workspace as its working directory unless a named sandbox
YAML definition changes it.
sandbox.docker
| Option | Type | Default |
|---|---|---|
network | Non-empty string | none |
cpus | Non-empty Docker CPU value | Unset |
memory | Non-empty Docker memory value | Unset |
pullPolicy | always, missing, or never | missing |
commandTimeoutMs | Positive integer | 30000 |
sandbox.e2b
| Option | Type | Default |
|---|---|---|
internet | enabled, disabled, true, or false | disabled |
template | Non-empty E2B template name | E2B_TEMPLATE, otherwise E2B default |
timeoutMs | Positive integer | ASSEMBLY_LINE_E2B_TIMEOUT_MS, otherwise E2B SDK default |
retainTimeoutMs | Positive integer | ASSEMBLY_LINE_E2B_RETAIN_TIMEOUT_MS, otherwise unset |
requestTimeoutMs | Positive integer | ASSEMBLY_LINE_E2B_REQUEST_TIMEOUT_MS, otherwise SDK default |
pauseKeepMemory | Boolean | true |
Enable internet only when the agent needs outbound access:
sandbox:
e2b:
internet: enabledsandbox.daytona
| Option | Type | Default |
|---|---|---|
networkAllowList | Non-empty Daytona list string | Unset |
domainAllowList | Non-empty Daytona list string | Unset |
ephemeral | Boolean | false |
networkBlockAll | Boolean | Daytona default |
autoStopInterval | Positive integer, minutes | Daytona default |
autoArchiveInterval | Positive integer, minutes | Daytona default |
autoDeleteInterval | Positive integer, minutes | Daytona default |
createTimeoutSeconds | Positive integer | Daytona SDK default |
lifecycleTimeoutSeconds | Positive integer | Daytona SDK default |
“Daytona default” means Assembly Line does not send that option. Daytona owns the behavior. The resolved inspector shows that the option is unset.
sandbox.modal
| Option | Type | Default |
|---|---|---|
timeoutMs | Positive integer | ASSEMBLY_LINE_MODAL_TIMEOUT_MS, otherwise Modal SDK default |
waitReady | Boolean | false |
Scheduler
| Value | Behavior |
|---|---|
local | The current Node process polls due work. |
postgres | Postgres coordinates due work across processes. |
gateway | The deploy gateway triggers scheduled work; local polling is disabled. |
Scheduler plugins currently accept no authored options.
Channels
One channel still uses the plural list form:
channels: [photon]Multiple channels may use a list when no exceptions are needed:
channels: [photon, slack]Use a mapping for exceptions. If exactly one channel is selected, it becomes
the default outbound channel. With multiple channels, no outbound default is
inferred; set default: true on at most one.
channels:
photon: {}
slack:
default: trueOfficial channel plugins
| Channel | Routes | Required environment | Default audio |
|---|---|---|---|
a2a | POST /a2a; GET /.well-known/agent-card.json | A2A_PEER_TOKENS, A2A_PUBLIC_URL | Disabled |
discord | POST /discord/events | DISCORD_PUBLIC_KEY, DISCORD_APPLICATION_ID, DISCORD_BOT_TOKEN | Disabled |
photon | GET, POST /photon/events | One Photon ingress secret group plus delivery credentials | OpenRouter |
slack | POST /slack/events | SLACK_SIGNING_SECRET, SLACK_BOT_TOKEN | OpenRouter |
teams | POST /teams/messages | MICROSOFT_APP_ID, MICROSOFT_APP_PASSWORD | OpenRouter |
telegram | POST /telegram/events | TELEGRAM_BOT_TOKEN; TELEGRAM_WEBHOOK_SECRET for production ingress | OpenRouter |
The channel owns routes, methods, transport, authentication, normalization,
delivery, attachments, scopes, required environment, connection binding,
description, and metadata. None of those fields can be restated in
agent.md. These declarations live with the channel's plugin.json and
ai.assemblyline entry; the compiler has no second official-channel registry.
Common channel options
| Option | Type | Default |
|---|---|---|
default | Boolean | Inferred only for a sole channel |
audio | openrouter, openai, disabled, false, or one-key provider mapping | Channel default in the table above |
hooks | Channel export name to qualified plugin channel-hook contribution | None |
Audio options
Audio is channel functionality. There is no top-level media or audio
field. Change a channel's provider, disable transcription, or configure that
channel's provider:
channels:
photon:
audio: openai
slack:
audio: disabled
telegram:
audio:
openrouter:
model: openai/whisper-large-v3
language: en| Option | Type | OpenRouter default | OpenAI default |
|---|---|---|---|
model | Non-empty string | openai/whisper-large-v3-turbo | gpt-4o-mini-transcribe |
fallbackModels | List of non-empty strings | openai/whisper-large-v3, openai/whisper-1 | whisper-1 |
language | Non-empty string | Unset | Unset |
endpoint | Non-empty URL string | https://openrouter.ai/api/v1/audio/transcriptions | https://api.openai.com/v1/audio/transcriptions |
modelMetadataKey | Non-empty string | Unset | Unset |
maxBytes | Positive integer | 26214400 | 26214400 |
maxAttachments | Positive integer | 4 | 4 |
timeoutMs | Positive integer | 60000 | 60000 |
maxResponseBytes | Positive integer | 524288 | 524288 |
title | Non-empty string | Assembly Line audio transcription | Not accepted |
referer | Non-empty string | Public app URL, then https://assembly-line.local | Not accepted |
OpenRouter requires OPENROUTER_API_KEY. OpenAI requires OPENAI_API_KEY.
Provider-specific audio environment variables can override omitted values; see
the environment reference.
Telegram options
| Option | Type | Default |
|---|---|---|
parseMode | MarkdownV2 or HTML | Plain text unless TELEGRAM_PARSE_MODE is set |
disableWebPagePreview | Boolean | false |
replyToMessage | Boolean | true |
allowedUpdates | List of non-empty strings | Provider webhook default |
Teams options
| Option | Type | Default |
|---|---|---|
allowedTenants | List of tenant IDs | Empty, which allows any verified tenant |
allowedServiceUrls | List of HTTPS service URLs | Empty, which allows verified Bot Framework URLs |
stripMentions | Boolean | true |
typing | Boolean | true |
Discord options
| Option | Type | Default |
|---|---|---|
gateway | Boolean or mapping with enabled, intents, url | Disabled unless DISCORD_GATEWAY_ENABLED enables it |
gateway.enabled | Boolean | false |
gateway.intents | Non-negative integer | Guilds, guild messages, direct messages, and message content |
gateway.url | Non-empty string | Discord /gateway/bot discovery |
allowedGuilds | List of guild IDs | Empty, which allows all guilds |
dmSupport | Boolean | true |
requireMention | Boolean | true for gateway guild messages |
allowedMentions | JSON mapping | { parse: [] } |
A2A options
channels: [a2a] derives a complete Agent Card:
| Option | Type | Default |
|---|---|---|
name | Non-empty string | Agent name, then id, then Assembly Line Agent |
description | Non-empty string | Agent description, then Send a task to <name>. |
version | Non-empty string | 1.0.0 |
skills | Non-empty list of skill mappings | One message skill derived from the description |
provider.organization | Non-empty string | Unset |
provider.url | Non-empty string | Unset |
documentationUrl | Non-empty string | Unset |
iconUrl | Non-empty string | Unset |
Each A2A skill requires id, name, and description. Optional fields are
tags, examples, inputModes, and outputModes, each a string list. Skill
IDs must be unique.
Photon and Slack accept no plugin-owned options beyond the common channel options.
Capabilities
Checked-in local plugins under plugins/<name>/ activate by presence. Do not
list them in agent.md. Published plugins must be installed, selected, and
pinned in plugins.lock.
capabilities: [notion]capabilities: [notion, github]Mapping form accepts plugin-owned config plus framework-owned,
contribution-scoped exceptions:
capabilities:
meal-memory:
config:
dataset: household
notion:
tools:
disable: [delete_page]
approval:
update_page: always
connections:
notion:
required: true
subject: workspace
disable: [delete_database]
approval:
create_page: once
events:
include: [page.content_updated]config is available only when the selected plugin declares a root
configSchema in its locked ai.assemblyline entry. The compiler validates
the value before generating code. Unknown keys, wrong types, missing required
values, invalid schemas, and config on a plugin without a schema fail the
build. A list selection validates as {}, so a schema may require explicit
configuration.
Configuration is plugin-wide because the package is the cohesive capability boundary. Native tool and connection factories receive package-owned contribution defaults followed by this agent-owned config; hook and lifecycle factories receive the config directly; a composition handler receives it as its argument. Static default exports, Skills, and portable MCP declarations are not rewritten by config. Use a native factory when executable construction depends on per-agent configuration.
config is compiled into the manifest for inspection and must never contain
credentials. Plugins classify names explicitly with requiredConfig,
optionalConfig, requiredCredentials, and optionalCredentials; deployment
and the selected secrets provider supply values without creating a shared
runtime secret environment.
Capability plugin configuration
| Field | Type | Default |
|---|---|---|
config | JSON mapping accepted by the plugin's locked configSchema | {} |
tools | Tool contribution exceptions | Plugin declarations |
connections | Connection contribution exceptions | Plugin declarations |
Tool contribution exceptions
| Field | Type | Default |
|---|---|---|
disable | List of contributed tool names or * | Plugin-reviewed availability |
approval | Tool-name mapping to always or never | Tool descriptor and host policy |
The shared parser accepts once, but native tool compilation rejects it.
once is connection-only. Unknown contributed tool names fail.
Connection contribution exceptions
Each key under connections must name a real connection contribution.
| Field | Type | Default |
|---|---|---|
disabled | Boolean | false |
disable | List of provider action names | Plugin-reviewed action surface |
approval | Action-name mapping to always, once, or never | Plugin-reviewed approval policy |
subject | user, workspace, installation, or environment | Plugin declaration |
required | Boolean | Plugin declaration |
events | false or mapping below | Plugin event defaults |
installation | environment | Unset; accepted only by github-app |
installationIdEnv | Uppercase environment variable name | GITHUB_APP_INSTALLATION_ID; accepted only by github-app |
events accepts include and exclude string lists plus a resources list
of mappings. The owning connection plugin validates resource shapes. Event
names are checked against the plugin catalog.
Plugin contribution selectors
| Concern | Syntax |
|---|---|
| Composition | composition: plugin.contribution |
| Context | context: plugin.contribution |
| Observability | observability: plugin.contribution |
| Automation lifecycle | automations.<name>.lifecycle: plugin.contribution |
| Channel | A unique contribution name selected directly under channels |
| Channel hook | channels.<name>.hooks.<export>: plugin.contribution |
Plugin Skills and hooks activate with the plugin. They are not selected again in frontmatter.
Automations
automations is a mapping keyed by a name that uses letters, numbers,
underscores, or hyphens. Each entry must declare exactly one schedule or
trigger.
| Field | Type | Default or rule |
|---|---|---|
description | Non-empty string | Unset |
schedule | Supported schedule string or five-field cron mapping | Mutually exclusive with trigger |
trigger | source.event string | Mutually exclusive with schedule |
connection | Selected connection contribution name | Inferred when the trigger source matches a selected plugin |
filter | JSON-compatible mapping | Match all; present mappings use recursive JSON-subset matching |
message | Non-empty string | Runtime target default |
skill | Root Skill name | No target override |
agent | Subagent name | No target override |
playbook | Playbook name | No target override |
delivery | Non-empty route or delivery name | Normal default-channel delivery |
lifecycle | Qualified plugin lifecycle contribution | None |
enabled | Boolean | true |
idempotency | Non-empty string | automation:<name> |
timezone | IANA timezone | Agent timezone |
At most one of skill, agent, and playbook may be set. There is no
automation metadata field.
timezone: America/Chicago
automations:
morning:
schedule: weekdays at 09:00
message: Prepare the morning brief.
training-review:
trigger: mirror.source.changed
filter:
provider: strava
skill: review-training
delivery: silentSupported schedule strings are:
daily at HH:MM
sunday|monday|tuesday|wednesday|thursday|friday|saturday at HH:MM
weekdays at HH:MM
every N minutes # N is 1 through 59
monthly on day N at HH:MM # N is 1 through 31Use a five-field cron for any other schedule:
schedule:
cron: "0 9 1 * *"
timezone: America/ChicagoMove a large automation map into one contained YAML file:
automations:
include: ./automations.yamlThe include must be a contained .yaml or .yml mapping. Inline and included
names must be unique.
Mutability
Mutability controls what the running agent may add to durable state. It does not control source edits, plugin authority, sandbox filesystem access, or provider permissions.
| Surface | Omitted value | Accepted values |
|---|---|---|
skills | autonomous | autonomous, approval, disabled |
automations | autonomous | autonomous, approval, disabled |
externalAccess | disabled | disabled or an enabled mapping |
Omitted mapping keys keep the same defaults. Change only the exception:
mutability:
automations: approvalmutability: static disables all three surfaces. Enabled external access
requires a mode and a non-empty host allowlist:
mutability:
externalAccess:
mode: approval
allowedHosts: [api.example.com, "*.example.org"]mode is autonomous, approval, or disabled. A host matches itself and
its subdomains; a leading *. is optional and normalized away.
Composition
Composition applies typed turn-time exceptions in order. Each rule contains
one when condition or otherwise: true, plus at least one action.
maxReasoning: high
composition:
- when:
channel: slack
reasoning: high
instructions: Reply for a workplace audience.
capabilities:
disable: [delete_page]
- when:
principal.roles:
includes: admin
capabilities:
enable: [delete_page]
- otherwise: true
reasoning: low| Condition | Compared value |
|---|---|
channel | String |
principal.type | String |
principal.id | String |
principal.roles | { includes: <role> } |
audience.private | Boolean |
attachments.present | Boolean |
state.<key> | String or Boolean |
| Action | Type |
|---|---|
model | Non-empty provider/model string |
reasoning | Supported reasoning level, no higher than maxReasoning |
instructions | Non-empty appended instruction string |
capabilities.enable | List of existing tool, Skill, connection, or subagent names |
capabilities.disable | List of existing tool, Skill, connection, or subagent names |
Only one otherwise rule is allowed. Unknown condition fields, actions, state
keys with empty names, and capability names fail compilation. Use a qualified
plugin contribution when the policy needs executable logic:
composition: company.access-policyContext
Omit context or use context: default for defaultContext. Only the default
profile accepts an author mapping:
context:
default:
recentHistory:
maxMessages: 12
transcript:
resume: true
reserveTokens: 16384
keepRecentTokens: 20000
toolResultCapChars: 2000| Option | Type | Default |
|---|---|---|
recentHistory.maxMessages | Non-negative integer | No extra cap; normal conversation lookup supplies up to 20 stored messages |
transcript.resume | Boolean | true |
transcript.reserveTokens | Positive integer | 16384 |
transcript.keepRecentTokens | Positive integer | 20000 |
transcript.toolResultCapChars | Positive integer | 2000 |
Use a mapping only when the agent needs a fixed override. Custom executable context behavior requires a selected contribution:
context: company.privateStructured output
Use an inline JSON Schema:
outputSchema:
type: object
additionalProperties: false
required: [answer]
properties:
answer:
type: stringOr use a contained JSON file:
outputSchema: schemas/answer.jsonAbsolute paths, traversal, missing files, non-JSON files, and invalid JSON Schema fail compilation.
Secrets
Omission and secrets: env select the framework-kernel host-environment secret
store. Values are still resolved only for scoped credential consumers; they
are not projected into a shared runtime environment. secrets: 1password
selects the installed and locked official 1Password plugin. Its mapping accepts
only:
| Option | Type | Default |
|---|---|---|
vault | Non-empty string | OP_VAULT; required if the environment does not set it |
field | Non-empty string | credential |
secrets:
1password:
vault: Production Runtime
field: credentialSecret values never belong in agent.md. See
Configuration And Credentials for broker lifecycle,
sandbox leases, and secure source-to-sink transfer.
Observability
Omission configures no external sink. The runtime still keeps its durable run
records. Install and lock the official plugin with assembly-line add otlp agent, then observability: otlp selects its package-owned instrumentation
factory and requires OTEL_EXPORTER_OTLP_ENDPOINT.
| Option | Type | Default |
|---|---|---|
serviceName | Non-empty string | Agent name |
captureContent | off, usage, content, full, or policy mapping | usage, which records usage but no model content |
recordInputs | Boolean | false |
recordOutputs | Boolean | false |
The captureContent mapping requires level and accepts these exceptions:
| Option | Type | Default |
|---|---|---|
level | off, usage, content, or full | Required |
maxChars | Positive integer | 8000; 200000 for full |
redact | Boolean | true |
redactKeys | List of non-empty strings | Empty |
sampleRate | Number from 0 through 1 | 1 |
includeToolIO | Boolean | true for content and full; otherwise false |
observability:
otlp:
serviceName: coachgpt
captureContent:
level: usage
redact: true
sampleRate: 0.25
recordInputs: false
recordOutputs: falseUse an installed and locked qualified plugin contribution for a custom sink:
observability: company.telemetrySubagents
List child directory names in the parent:
subagents: [researcher, reviewer]Each name must use letters, numbers, underscores, or hyphens and must contain
subagents/<name>/agent.md. A child is a complete MD-first surface with its
own required model, non-empty instructions, and required description.
Children may declare their own channels, capabilities, Skills, automations,
sandbox, context, composition, and output schema. The deployed root still owns
the physical runtime, state adapter, and blob adapter.
Named sandbox YAML
Use sandbox/<name>.yaml when a provider scalar is not enough. The filename is
the profile name; do not repeat name inside the file.
# sandbox/coding.yaml
adapter: e2b
image: node:22
workingDirectory: /workspace
env: [GITHUB_TOKEN]
snapshot:
mode: on_failure
retainLast: 2
environment:
context: coding-environment
dockerfile: Dockerfile
verifyCommand: node --versionSelect it with:
sandbox: coding| Field | Type | Default |
|---|---|---|
adapter | Non-empty provider name | Required |
image | Non-empty image or provider image reference | Provider default |
workingDirectory | Non-empty path | /workspace |
env | List of environment variable names to project | Empty |
snapshot.mode | never, manual, on_failure, or always | No snapshot policy |
snapshot.retainLast | Non-negative integer | Runtime snapshot retention default |
snapshot.reason | Non-empty string | Unset |
snapshot.metadata | JSON mapping | Empty |
environment.context | Contained relative directory | Required when environment is present |
environment.dockerfile | File inside context | Dockerfile |
environment.verifyCommand | Non-empty string | Unset |
The compiler contains and hashes every environment file. Symlinks, traversal,
YAML aliases, anchors, and explicit tags fail. A named sandbox YAML file is the
only declarative surface where snapshot-owned metadata is accepted.
Other agent files
| Surface | Omitted behavior | Add it when |
|---|---|---|
| Markdown body | Invalid when empty | Always; permanent trusted system instructions |
skills/*/SKILL.md | No root Skills | The model needs optional procedures or reference material |
plugins/<name>/plugin.json | No local plugin | The agent needs custom executable capability code |
plugins.lock | Published plugins cannot resolve | CLI workflows install and pin a published or local plugin |
automations.yaml | Inline automations only | automations.include points to this contained map |
subagents/<name>/agent.md | No child | The parent lists the child under subagents |
evals/ | No evaluation cases | The agent needs regression tests; eval files do not affect deployment revision |
assets/ | No extra resources | Instructions or plugins need stable source assets |
migrations/ | No agent-owned migrations | A state provider needs additional deployment migrations |
Root agent.ts, gateway.ts, context.ts, instrumentation.ts, tools/,
hooks/, connections/, channels/, automations/, schedules/, lib/, and
other executable conventions are rejected. Put executable behavior in a local
or published plugin.