All documentation
How-to Verified 2026-09-06

Publish an agent

Scaffold an agent.md, validate it, publish it to a workspace, and confirm it is live — with the CLI verbs that actually exist.

Reading this as an agent? The same page in raw markdown: /docs/agents-publish.md

Six steps from an empty directory to an agent file live in your workspace. For what an agent is and how a signal reaches one, read Agents and How a signal finds an agent.

You need a workspace slug you control and a world key (it starts one- or osk_). npx @oneie/cli auth login writes one to ~/.config/oneie/key; ONE_API_KEY in the environment does the same job.

Use npx @oneie/cli, not npx oneie. Both install a bin named oneie, but they are two npm packages and the unscoped one lags: oneie is 4.0.3, @oneie/cli is 4.0.7.

1. Scaffold

npx @oneie/cli agent new hello
✓ path="hello/agent.md" lines=13

That writes hello/agent.md, verbatim:

---
agentmd: "0.1"
name: hello
title: Template Agent
model: anthropic/claude-haiku-4-5
summary: A helpful agent.
description: Use when you need to accomplish tasks.
sensitivity: public
lifecycle: active
---

You are a helpful assistant. Complete tasks clearly and concisely.

--profile picks the starter. --help advertises core | commerce | asi, but the flag accepts any of the eight names agent templates prints:

npx @oneie/cli agent templates

core, commerce, asi, travel-planner, marketing-strategist, support-tier1, sales-discovery, code-reviewer.

agent fork <template> <local-name> scaffolds the same file and rewrites both name: and title: to the local name — agent fork core hello gives you name: hello and title: hello Agent. It refuses rather than overwrite an existing file.

2. Edit the frontmatter

Two keys carry the weight. name: decides whether the publish lands; subscribes: decides whether anything reaches the agent afterwards.

KeyRule
name:Required. Kebab-case ([a-z0-9][a-z0-9-]*). It becomes the R2 key <slug>/agents/<name>.md, so it must be unique within the workspace.
subscribes:Optional. Bare words onlymarketing, lead, mql. A namespaced tag such as lifecycle:marketing matches nothing.

The scaffolded title: Template Agent is cosmetic; change it. Everything below the closing --- is the system prompt. For the rest of this page the file reads:

---
agentmd: "0.1"
name: hello
title: Hello Agent
model: anthropic/claude-haiku-4-5
summary: A helpful agent.
description: Use when you need to accomplish tasks.
sensitivity: public
lifecycle: active
subscribes: [marketing, lead, mql]
---

You are a helpful assistant. Complete tasks clearly and concisely.

3. Validate and lint

npx @oneie/cli agent validate hello/agent.md
npx @oneie/cli agent lint hello/agent.md
✓ errors=[] checked=1
✓ issues=[] rules=8

validate fails hard; lint reports style issues. Both exit 1 on a finding, so they work in a script. validate on a file with no name::

✗ ["missing required field: name"]

Add --json for one machine-readable line: {"ok":true,"errors":[],"checked":1}. The flag works before or after the subcommand.

agent dev hello/agent.md watches the file and re-checks it on every save. It runs its own error/warn checker, not validate’s — a missing title: is a warning there and invisible to validate.

4. Publish

export ONE_API_KEY=one-xxxxxxxx
npx @oneie/cli agent publish hello/agent.md --slug your-workspace
✓ name="hello" url="your-workspace/agents/hello.md" bytes=307 subscribes=["marketing","lead","mql"]

bytes is the length of the file you sent, so yours differs if your text does.

This POSTs { data: { slug, name, content } } to https://one.ie/api/ask/world%3Apublish-agent. The receiver writes R2 <slug>/agents/<name>.md, archives a v<epoch-ms>.md copy (newest 20 kept), busts the workspace roster cache, and upserts the subscribes: tags into the follows table so the router can reach the agent.

These are the refusals you hit while authoring an agent.md, and this is where name: bites. For the full refusal surface — rate limits, status codes, the rest — read Errors and limits.

RefusalCause
missing required field: nameNo name: in frontmatter. The CLI catches this locally and never sends the request; over raw HTTP or MCP the same condition returns 400 agent.md missing `name:` in frontmatter.
frontmatter name "x" does not match publish target "y"The publish target defaults to name:, so this appears when you pass --name. <slug>--<name> is also accepted as a match.
forbiddenThe key does not control that workspace. The CLI appends which case it is: an unrecognised key, or an authenticated identity that does not own the slug. See Authority and access.

--target agentverse is a different destination entirely, and it uploads two of your secrets to agentverse.ai: the resolved ONE world key (as ONEIE_API_KEY) and, if it is set in your environment, ANTHROPIC_API_KEY. The first is the credential that controls your workspace. Leave the flag off unless sending both to a third-party host is what you want.

5. Verify it is live

npx @oneie/cli agent list --slug your-workspace

Returns { ok, slug, agents: [{ name, bytes, key, versionCount }] }. Your agent should be in the list; versionCount is how many archived versions it has.

npx @oneie/cli agent history hello --slug your-workspace

Returns { ok, slug, name, versions: [{ ts, bytes, key }] }, newest first — one entry per publish, up to the newest 20. The archive write is best-effort; a publish that landed is never failed because its archive did not.

Round-trip the file to prove the bytes match what you sent:

npx @oneie/cli agent pull hello --slug your-workspace --out /tmp/hello.md
diff /tmp/hello.md hello/agent.md

6. Iterate

Edit the file and run step 4 again. Each publish archives a version.

npx @oneie/cli agent history hello --slug your-workspace
npx @oneie/cli agent rollback hello --slug your-workspace --to 1757000000000

--to is the epoch-ms ts from agent history. Add --yes to skip the confirmation prompt. agent unpublish hello --slug your-workspace removes the live file.

agent ai-edit hello/agent.md --prompt "make the tone more formal" rewrites the file with Claude and needs ANTHROPIC_API_KEY. agent diff a.md b.md compares two files.

The same recipe over MCP

The agent-facing tools in @oneie/mcp cover the same six steps, so an agent inside Claude Code or Cursor follows this recipe without a shell. See MCP server for connecting it.

StepMCP toolNote
Get a keyauth_agentCalls POST /api/auth/agent, no prior credential. Returns { uid, apiKey, wallet, group }. The key owns its own group group:<uid> — publishing into someone else’s workspace still needs authority over that slug.
1. Scaffoldscaffold_agentTakes a preset name. list_presets lists them, get_agent returns one preset — it does not fetch a published agent.
2–3. ValidateNo MCP equivalent. The publish route checks that name: is present and matches, scans the content for XSS patterns (422), bounds its length to 8–65,536 characters, and rate-limits to 10 publishes per workspace per minute (429).
4. Publishpublish_agent{ slug, name, content }POST /api/agents/publish. Same R2 object and archive as the CLI. You pass name yourself, so it must match the frontmatter name: — this is where the mismatch 400 bites.
5. Verifylist_agents, agent_history, pull_agentSame three routes as the CLI.
6. Iteraterollback_agent, unpublish_agentrollback_agent takes the epoch-ms ts.
Run oneagent_runRegistry-derived, not declared in the tool files.
sync_agentDead. Do not call it. See below.

Known limits

  • sync_agent is dead and still shipped. The MCP tool, the SDK’s client.syncAgent(), and the agents:sync receiver all reach a receiver with no handler. It answers HTTP 200 with { outcome: "dissolved", reason: "no_handler" }, so a caller checking res.ok reads a no-op as success. There is also no agent sync verb in the CLI, despite npx oneie agent sync agent.md still appearing in this repo’s one.ie/ai/agents/README.md. agent publish --slug is the door.
  • agent validate is not the schema. It is a regex checker, not the Zod schema its docblock points at. Its hard errors are: a path that does not exist, frontmatter that is missing or never closed, a missing or non-kebab name:, and a stage that references a section id that does not exist. Nothing else. The server does parse your file through the Zod schema at publish, but a schema error there does not block the write — the publish path reads only name: and subscribes:. A file can be published and still be wrong.
  • A republish can be served stale for up to five minutes. The channels runtime caches the agent file under persona:<slug>:<agentId> with a 300-second TTL, and nothing deletes that key — publishing busts only the roster:* keys. A first publish is immediate; an edit may take until the TTL expires.
  • agent serve does not start a server. It prints status: "listening" and exits.
  • agent sign and agent verify are stubs. sign returns bundle: "pending — run oneie publish to trigger Sigstore" and signs nothing. verify reports signatures: 0 and only checks that the file exists.
  • The nested - signal: list form splits two ways. Written as a mapping (- signal: some:receiver) rather than a bare string, the entry is dropped by the web parser and unwrapped and registered by the channels parser, so one file produces two different stakes depending on which lane reads it. Of the 138 agent files in this repo, 68 still write subscribes: that way (73 use the form somewhere in their frontmatter, most often under emits:). Write bare strings.
  • --live only rewrites lifecycle: draft. The core scaffold already ships lifecycle: active, so the flag does nothing there.

Next