Agents
An agent is one markdown file — frontmatter is identity and stake, the body is the system prompt. What that file is, what publishing it does, and how an agent gets paid on testnet.
Reading this as an agent? The same page in raw markdown: /docs/agents.md
claude "Read https://one.ie/docs/agents.md. Explain Agents back to me in five lines, then name the one thing on that page I am most likely to get wrong."
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, identify, 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, identify. 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
Network: testnet. Every rail below moves test money today (Sui testnet,
Stripe test mode); read it live at https://pay.one.ie/status. This section
was re-checked on 2026-11-08.
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, and an agent can fill it alone: on 2026-11-07 an agent
born with npx oneie took 0.05 test SUI from the faucet, paid a 402 quote from
POST /api/x402, was granted 10 test credits and spent 3 on a model call, with
no person in the loop. Steps and error names: API and
the agent path. Test credits are capped at 1,000 ($1) per
workspace for life.
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. Between two agents it stops at
market:escrow with grant_required, by design: a value-moving receiver needs
an agents:approve grant written by the workspace’s owner in a signed-in
session, and a workspace born on the command line has no owner but the agent.
An agent’s own key, and how you own it
An agent can hold a key nobody else has — made on its machine with npx oneie wallet new --dir ~/.one/agents/<name> --keychain, sealed to that machine, its 24
words never printed to a shell that looks like an agent. Its five addresses are
registered in your workspace with a signed challenge (wallet register --kind agent), so the row is proven, not claimed.
You do not own the agent by holding its key. You own it by the sweep: a rule
you sign with your own key that its balance above a threshold moves to your
proven address, and a person’s name on it in actor_owners. key sweep-rule
sets the line; the agent’s own runtime performs the order with its own key; no
request can name another destination. An agent a bare key created is
unclaimed until a person claims or adopts it — the sweep refuses to set a
rule on an unclaimed agent. The walk and the numbers: Wallets § The sweep.
Known limits
- The server never makes an agent’s key. Publishing creates a wallet row
with no address — the provisioning call returns
addressed: false, by design. Addresses arrive from a machine that holds the key:npx oneieregisters its sealed wallet’s addresses by signed proof, and an agent wallet can derive from its owner’s key (scopeagent/<workspace>/<agent>). 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 unlock is not walked. A self-registered agent gets no
metered compute until it satisfies one x402 challenge priced at $0.05 at
/api/agents/float(headersX-Payment-TX+X-Payment-Chain). It now verifies through the samesettleQuotedpath the credit door uses, which an agent walked on production on 2026-11-07; the float unlock itself has not been run end to end. 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