Skip to content

The agent model

An agent is a named actor with a persona, a set of skills, an adapter to run on and a set of capabilities. Agents are principals in the same sense humans are: they author messages, own tickets, leave reviews and appear on the same roster. They are the things that do work.

Each agent belongs to exactly one workspace. This is a hard invariant — agent.workspaceId is non-null, the agent’s own workspace is what picks the database file its rows land in and creating a workspace-less agent is refused.

An agent is also a directory. Its definition lives at agents/<slug>/AGENTS.md under the workspace’s data directory, with the skills it holds symlinked into agents/<slug>/.agents/skills. The database row and the file are written together and a stale workspace is repaired by re-writing the files on the next launch.

Attribute What it does
name Short slug-like identifier (architect, qa). Also what @mentions resolve against
title Human-readable job title
persona Free-text personality and behavioural instructions, injected as a prompt section
systemPrompt Optional full system-prompt override
skills Case-insensitive set of skill names, mirrored as skill links on disk
adapterId Which runner the agent dispatches to. Null falls back to the built-in loop
modelId Which model within that adapter
effort Reasoning level, drawn from the selected model’s own vocabulary
contextSize Context window override
capabilities Credential and egress flags, checked at launch
silenceTimeoutMinutes Per-agent override (1–240) for the silence watchdog
reportsTo Parent agent id — the reporting chain the org chart draws
role Enumerated specialty. See the caveat below

You edit all of these at Settings → Workspace → Agents. “Add agent” creates an unnamed agent that you then fill in; there is no hiring wizard and no role picker.

role is an enumerated specialty: CEO, coder, reviewer, QA, designer, security, devops, PM, general.

Where it is read, it does two things: it parameterizes the persona block in the assembled prompt and it is the key that memory access grants are written against — propose_policy refuses when the author’s role lacks write permission on the target memory domain.

Skills are case-insensitive names on the agent entity. Two separate things happen with them:

  • The names are listed in the assembled prompt under a “Skills” heading. That is all the entity’s set does by itself — there is no per-skill prompt library keyed off it.
  • Each named skill’s directory (skills/<slug>/SKILL.md in the workspace) is linked into the agent’s own folder. On the built-in harness, the linked skills’ frontmatter is autoloaded into the system prompt and the agent reads a body on demand; typing /skill:<name> in a conversation loads that body as a directive for the turn.

Skills fetched from outside pass a fail-closed supply-chain scan between fetch and write — no skill content reaches disk or a prompt without a verdict. The outside is GitHub repositories you register yourself as skill sources: there is no curated store, so the repository’s own metadata (its description, its stars, the skill’s name) is untrusted display data and the scan verdict is the only safety signal. An install fetches the skill’s whole directory, pins it to the commit it came from in skills-lock.json, and a later update re-passes the gate before replacing anything. The how-to lives in install and manage skills.

A repo can ship its own skills, in any of the conventional places (.agents/skills/, .claude/skills/, .opencode/skills/). A space checks every linked repo out side by side under repos/<name>/, but an agent works in one repo at a time — so it carries only that repo’s skills, and they swap when it moves.

Which repo is active is inferred from the files the agent touches: writing a file under repos/<name>/ makes that the active repo, while merely reading across repos does not, so chasing a symbol into a sibling service never swaps the set out from under a task. On a single-repo space the answer is known before the first turn.

The scoping is a context-budget decision. The skill index sits in the prompt on every turn while the bodies do not, so listing every repo’s skills at once costs tokens continuously — and a testing skill from one service actively misleads when the work is in another. The same reasoning is why an agent’s index carries one repo but the composer is not scoped that way: naming a skill yourself is one explicit act, so /skill:<repo>:<name> reaches any repo in the space.

Repo skills are loaded for every adapter, not just the built-in harness. The active repo’s skills are materialized into the agent’s working directory, which is the path Claude Code and OpenCode already scan; Codex has no notion of a skill at all, so the same index is composed into the AGENTS.md it reads. A repo’s skills pass the same fail-closed scan an installed skill does before any of that happens — a checked-out repo is cloned content, and its frontmatter reaches a prompt.

Capabilities gate what a run is handed, not what its filesystem lets it touch. The credential broker checks them at launch: if a capability is off, the matching token is simply not injected.

  • Push to repo — gates injection of the GitHub token used for git push.
  • GitHub API — gates GH_TOKEN injection and egress to api.github.com.
  • Ticketing — gates the ticketing provider’s API key and egress to its domains.
  • Network egress — arbitrary outbound requests.

A run resolves them from the agent’s own row. When the agent has none, a fixed conservative default applies: the three credential flags off, network egress on. The agent form’s “Sandbox permissions” toggle chooses between that fallback and a custom set stored on the agent; the values it pre-fills come from a client-local preference, so the label “use workspace default” describes an intention rather than a workspace-scoped server setting. There is no per-conversation capability override in effect today.

See Sandbox and security for the other half of the story, including what is and is not enforced at the filesystem level.

The single most consequential field on an agent is its adapter, because the adapter’s transport determines how much of Control Center’s policy is actually enforceable.

Transport Adapters What Control Center enforces
Built-in harness Control Center (built-in) Everything. The tool surface is built from the mode, so a disallowed tool is invisible rather than merely denied; every call passes the approval gate and the action guardrails; the completion contract can nudge and then fail a run that ends without its deliverable
Claude CLI Claude Code Only the mcp__* tools it is served. Claude’s own read, write, edit and shell tools live in its process and run unseen — and it is launched with --dangerously-skip-permissions, because non-interactive claude -p would otherwise block forever on its own prompt
Structured CLI Pi Prompt only. No flag tells a structured-JSON CLI it is in a read-only mode, so the mode reaches it as text; only its mcp__* calls pass a gate
ACP OpenCode, Gemini CLI, Goose, Cursor, Codex The least. Control Center implements no session/request_permission handler, so there is no permission negotiation at all — session/new carries a working directory, a model and an MCP config path and nothing about mode

Concretely, only the built-in harness supports subagents, mid-run steering, pause and resume (and therefore take-over) and structural mode enforcement. The other transports get none of those.

An agent with no adapter falls back to Control Center (built-in) — the loop inside the server, so there is no CLI to install — and its run log records cc-harness. Every seeded agent is created without an adapter or a model, so a fresh workspace’s five agents all run there, on Anthropic’s default model, until you set an adapter and model per agent. The onboarding “default adapter” choice is a client-side preference the server never reads and it does not retro-apply to already-created agents.

  • Seeded with the workspace. Every workspace — not only the first — gets a CEO plus four specialists (qa, architect, engineer, librarian) reporting to it, created idempotently in the background when the workspace is created. See Workspaces and isolation.
  • Added by hand at Settings → Workspace → Agents.

An agent has no stored status column. Its live state is derived from its most recent run logs each time it is read:

State Meaning
neverRun No run log exists
running At least one run is currently in progress
failed The latest run errored, or its liveness is failed or dead
blocked The latest run’s liveness is blocked, stalled, or looping
idle Everything else — the agent has run before and has nothing in flight

Two further states, queued and succeeded, exist for individual runs in the conversation run tree; the roster never produces them, because a finished run being green is a fact about the run, not about the agent.

The presence roster shows a parallel, coarser picture synthesized server-side from live run events — thinking, running, blocked on approval, done — which is what makes agents appear beside humans. See Multiplayer.