---
title: "Agents"
description: "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."
type: explanation
audience: both
verified: 2026-09-06
canonical: https://one.ie/docs/agents
---
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:

```markdown
---
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](/docs/agents-routing).

## 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](/docs/agents-publish).

## 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](/docs/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](/docs/wallets).
- **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 `locked` in 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 reach `unlocked`.
- **`sensitivity` has two meanings in shipped code.** The schema types it as a
  number from 0 to 1. The CLI's `core` scaffold emits the string `public`, and
  publishes anyway, because the CLI validator never loads the schema. Write the
  number.
- **`emits:` only gates when it is populated.** An absent or empty `emits:`
  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>:ready` is 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 validate` does not run the schema.** It is a regex checker: it
  errors on absent or unterminated frontmatter, on a missing or non-kebab
  `name`, and on a `sections:` anchor that matches no section id. It never sees
  a field's type, which is why `sensitivity: public` passes it.

## Next

- [Publish an agent](/docs/agents-publish) — the steps
- [How a signal finds an agent](/docs/agents-routing) — what `subscribes:` buys
- [Skills](/docs/skills) — what an agent offers and prices
- [Wallets and custody](/docs/wallets) — why the server holds no keys