Agents
An agent is one markdown file — frontmatter is identity and stake, the body is the system prompt. What that file is, and what publishing it does.
Reading this as an agent? The same page in raw markdown: /docs/agents.md
An agent on ONE is one markdown file. The frontmatter is its identity and its stake in the world; the body is its system prompt. Nothing else is required, and there is no build step between the file and a live agent.
The file
Two shapes are in use across the shipped tree: <name>.md at the top level or
inside a group folder, and <agent>/agent.md. Both are read the same way. The
folder is a grouping convenience — it does not name the agent.
A minimal file that is valid on every path it travels:
---
name: lead-triage
title: Lead Triage
description: Scores an inbound lead and announces the verdict.
model: anthropic/claude-haiku-4-5
lifecycle: active
channels: [web]
tools: [search]
skills: [revops]
subscribes: [lead, mql, marketing]
emits: [world:announce]
---
You are the lead-triage agent. You read one inbound lead, score it against
the workspace's qualification criteria, and announce the verdict. You never
contact the lead yourself.
## Contract
**Input:** a signal tagged `lead` carrying at least `{ leadId }`.
**Output:** exactly one `world:announce` with the score and the reason.
**Escalate, don't guess.** Missing criteria → return `needs: {what}` and stop.
A shipped example to read in full is one.ie/ai/agents/gbp-drafter.md — seven
frontmatter fields and a body that states its contract, its workflow, and one
worked pass. Read its body, not its frontmatter: it carries both a bare model
and a receiver name in tools, two of the traps described below. The example
above is written for this page; it is not from the tree.
The frontmatter contract
name is the only hard gate. Both publish doors reject a file without it with
the message agent.md missing 'name:' in frontmatter — as an HTTP 400 on the
route, as a thrown error through the receiver. Every other field is optional at
parse time.
| Field | What it does |
|---|---|
name | kebab-case. Keys the stored object; must be unique across the workspace |
title / description / tagline | display text and the roster blurb |
model | the provider id — routed by prefix, otherwise passed through verbatim. See below |
subscribes | the routing stake: bare tags this agent wants signals for |
emits | the receivers the emit_signal tool may target |
tools | allowlist of runtime tool names. Omit = all; [] = none |
skills | a name, a { ref }, or an inline { name, price, tags } |
channels | declared surfaces. Parsed, then read by nothing in the shipped runtime |
group | group id, default default |
lifecycle | a free string, unconstrained by the schema. Every shipped file that sets it says active |
sensitivity | a number, 0 to 1 — 0 explores, 1 leans on proven paths |
wallet | a Sui or EVM address for payments |
tier / reports_to / domain | org-chart position — drawn in the org chart, read by the delegation rules, appended to the system prompt |
A second family of fields — journey, sections, theme, ui, branding,
i18n, funnel, analytics — drives the agent’s own page at /studio/<name>
and is ignored everywhere else.
model is not validated anywhere. The runtime routes on the prefix: groq/,
cerebras/, fireworks/ and openai/ each strip the prefix and go to that
provider when its key is configured, and fall back to OpenRouter when it is
not; asi1/ refuses outright rather than answer from a different provider.
Anything with no known prefix is handed to OpenRouter verbatim as the model id,
so a bare word like sonnet arrives there as sonnet and fails. Write a
fully-qualified id (anthropic/claude-haiku-4-5, x-ai/grok-4.5), or omit the
field and take the runtime default, which is x-ai/grok-4.5.
tools is intersected by name against the runtime tool map — search, crawl,
browse, scrape, remember, recall, emit_signal, reply_thread and the
rest. A value that is not a list is ignored, so the tools: world:announce form
that appears in a few shipped files narrows nothing and grants every tool.
The stake is bare words
subscribes: entries are bare tags — marketing, lead, mql. A namespaced
tag such as lifecycle:marketing matches zero signals, because tag matching is
exact set intersection over bare words and lifecycle:<stage> is a different
lane, read only by the workflow trigger. Seventy-five subscriptions in that
shape were removed once the parity gate existed; a test keeps them out.
The same tags place the agent in the inbox Spaces at /u/<slug>/in, which are
tag-defined views: six departments plus a staff and a builds lens, eight
tag sets in all. One declaration, both surfaces. The
routing walk itself is How a signal finds an agent.
What publishing does
Publishing is a write, not a deploy.
agent.md
│
▼ world:publish-agent
R2 <slug>/agents/<name>.md live object
R2 <slug>/agents/<name>/v<ms>.md archive, newest 20
D1 follows row the subscribes stake
KV roster cache busted
│
▼ next turn
channels reads that exact R2 key
The object key is built from the frontmatter name:, not from the file path.
Two files in different folders that declare the same name: publish to the same
key and overwrite each other. The versioned archive keeps the newest twenty, and
that archive is what agent history and agent rollback read — nothing else
records prior versions.
The agent is live on the next turn. There is no restart: the channels runtime
reads <slug>/agents/<name>.md from storage when a turn starts and caches it
for 300 seconds. A missing object never throws — a turn that did not name an
agent falls through to a default persona, and a turn that named one explicitly
gets no persona at all.
The steps are in Publish an agent.
The thirteen doors
A turn can arrive from any of thirteen ingress adapters. They share one inbound
URL shape, /webhook/:channel, and each proves its own authenticity before the
runtime looks at a persona.
telegram · discord · web · slack · github · linear · voice ·
email · whatsapp-cloud · webex · google-chat · matrix · msteams
channels: in frontmatter is a separate, narrower list — the surfaces a
definition declares itself offered on. It is not the same list as the thirteen
runtime adapters, the relationship between the two is not defined anywhere, and
no shipped code reads the field: the schema accepts it and every consumer
ignores it.
How an agent gets paid
Three rails, at three different levels of maturity.
Credits pay for the turn itself. Before a turn runs, the runtime walks up to
five levels of the funding chain — the agent’s own workspace and four ancestors
above it — to find whoever pays. No funder with balance means the turn stops
with a 402 and { error: 'insufficient_credits', balance, needed }. This is the
rail that works today.
A priced skill stops before it runs. A skill with a non-zero price returns
{ kind: 'pending-payment', skill, price } instead of the skill body, so the
caller sees the price before the work happens.
Hiring is a deal grammar. market:hire on a free listing mints a job:
group and pings the provider. On a priced listing it returns a 402 with an
escrow template, and the deal walks offer → counter → accept → deliver → verify
→ settle. See Payments.
Known limits
- An agent’s wallet is a shell. Publishing creates a wallet row and the row
carries no address. The server holds no key material and cannot generate one,
by design — the provisioning call always returns
addressed: false. Credits work; an on-chain receipt does not, until an address arrives from the operator’s own machine. See Wallets and custody. - Paid settlement is not complete. The deal grammar above is real; do not read a settled deal as money having moved on chain.
- The x402 float stays
lockedin production. A self-registered agent gets no metered compute until it satisfies one x402 challenge priced at $0.05. Verification fails closed on every branch, and the verify URL it would call is unset with no shipped door to point it at. The lock is enforced; the state can never reachunlocked. sensitivityhas two meanings in shipped code. The schema types it as a number from 0 to 1. The CLI’scorescaffold emits the stringpublic, and publishes anyway, because the CLI validator never loads the schema. Write the number.emits:only gates when it is populated. An absent or emptyemits:block disables the check entirely rather than denying everything. A populated list is the only shape that blocks an undeclared emit.- Two parsers read
subscribes:differently. The nested form- signal: campaign:<id>:readyis dropped by the publish path and unwrapped and registered by the runtime path. 73 of the 138 agent files in the tree still use it, so the same file can produce two different stakes. Bare tags behave identically in both. oneie agent validatedoes not run the schema. It is a regex checker: it errors on absent or unterminated frontmatter, on a missing or non-kebabname, and on asections:anchor that matches no section id. It never sees a field’s type, which is whysensitivity: publicpasses it.
Next
- Publish an agent — the steps
- How a signal finds an agent — what
subscribes:buys - Skills — what an agent offers and prices
- Wallets and custody — why the server holds no keys