Skip to content

Agent definitions

An agent definition is an ordinary outline page in the agents/ directory. Its metadata.agent block declares how the agent behaves, and its page body holds the prompt. Because the definition is a page, you edit it in the outliner, diff and restore its versions, dump it to disk through the OKF bridge, and deploy it with GitOps.

The metadata.agent object is validated with Zod at the boundary. Invalid definitions are skipped by the runtime and listed with their error in the commands settings.

Field Type Meaning
prompt string The instruction template. {{arg}} placeholders interpolate args at invoke time. Often the page body rather than a metadata string.
tools string[]? Registry allowlist. Absent means all built-in tools. See tools & approvals.
caps string[]? Capability grant set. Absent means the union of the allowlisted tools’ requires; naming caps only narrows reach.
skills string[]? Bind skills by name. Absent means all skills.
mcp string[]? Bind MCP servers by page. Absent means none (opt-in).
fns string[]? Bind custom functions as fn.<name> tools. Absent means none (opt-in).
argsSchema object Named args: { name: { type: string | number | boolean | page, description?, required? } }.
model string? Override the run model. Falls back to SUBSPACE_MODEL (default claude-sonnet-5).
approvalPolicy 'none' | 'outbound' | 'all' Gating policy. Default outbound. See approvals.
schedule cron? Run on a timer through the scheduler.
on string[]? Event-type bindings. Run automatically on matching events. See triggers.

A minimal definition page looks like this in OKF:

---
agent:
tools: [kb.search, kb.read, mail.send]
model: claude-sonnet-5
approvalPolicy: outbound
argsSchema:
person: { type: page, required: true }
depth: { type: string, description: "brief | full" }
---
# prep-meeting
You are preparing for a meeting with {{person}}. Produce a {{depth}} brief:
recent threads, open tasks, and CRM notes. Cite pages with [[links]].

Every home has an agents/default page, ensured on boot along with the agents/ directory. When you press ⌘↵ (or ctrl+↵) on a plain bullet, its text is sent as the instruction to the default agent (a built-in DEFAULT_AGENT constant is the fallback if the page is missing). Edit agents/default to change what the default keystroke does: its prompt, its tool allowlist, its model, and its approval policy.

Named agents join the slash menu with a violet agent badge (custom functions keep a fn badge). Picking one inserts /name followed by argument-placeholder chips drawn from argsSchema.

  1. Placeholder chips

    Required args come first, then the rest alphabetically (jsonb metadata does not preserve authored key order). A page-type arg gets [[ autocomplete, so /prep-meeting [[Marcus Feld]] binds the person arg to that page and the depth chip stays as a hint until you fill it.

  2. Binding args

    key: value tokens bind named args explicitly; any other token fills the next positional slot. So /prep-meeting [[Marcus Feld]] depth: full sets both args.

  3. Invoke

    Enter or ⌘↵ on a completed /name … call invokes agents.invoke {agentPageId, args, pageId, nodeId}. The prompt renders with {{arg}} interpolation, and the run’s violet output bullets grow under the invoking node.

agents.list is the registry endpoint behind the slash menu and the settings page.

/settings/commands (sidebar entry commands) is the command directory: every custom function (code/) and agent (agents/) with its name, a type badge, description, and args summary. Agent rows also show model, approval policy, schedule, and tools. A row navigates to the underlying definition page (a directory of pages, not a bespoke editor). Invalid agent definitions show their Zod validation error inline, so a broken metadata.agent block is visible without reading logs.

A definition can change between runs, so every run records exactly which version configured it. At run start, startRun snapshots the definition page (and every bound skills//mcp//code/ page) into kb.page_versions and stamps ops.agent_runs.definition_hash. The hash is computed by the page-versioning canonicalizer, so an unchanged definition used a hundred times deduplicates to one stored version, and every version that ever configured a run is retained.

Definition as of this run

The run tree’s anchor row shows a definition vN chip when the run’s definition_hash resolves. Clicking it renders the pinned canonical OKF from the store: the exact definition that configured the run, whatever the live page says now. Bound skill, MCP, and fn chips on the same anchor resolve the same way through ops.run_page_versions.

Config pages (anything under agents/, skills/, mcp/, judges/, evals/, code/, settings/, plus any page with metadata.versioned: true) show a header chip like v7 · 12 versions (or edited since v7 when the live content is unsaved). The versions panel lists versions newest-first with a source chip (manual, ⟳ run …, or ⚙ plugin@ver) and a “used by N runs” count, defaults to a structural outline diff with an exact-text toggle, and offers Restore: it confirms with the diff, then applies client-side as one ordinary edit, so a restore is optimistic, synced, and on the ⌘Z undo stack. Pre-versioning runs are backfilled: a boot sweep reconstructs their definition contract from the journaled first message and inserts it as a source: backfill version, so old runs group and open like any other.