Symbol Docs
Concepts

Agents

Turn a capsule into a Claude Code subagent and deliver it three ways — project files, ephemeral session JSON, or a plugin — from one source of truth.

A skill is a capability your assistant loads and follows. An agent goes further: it is a whole subagent — its own system prompt, its own tools, its own model — that Claude Code can hand a task to. Symbol lets a capsule be that subagent definition, so the capsule stays the single source of truth and the running agent never drifts from it.

You mark a Type as Agent packaging (the "Publish as → Agent" option, editable on the Type cover page at creation and later on the Type's Options tab). Agent packaging is a default for new capsules — turning it on seeds each capsule created afterwards as an agent, and never repackages capsules that already exist. Any single capsule can also be included or excluded individually from its detail page, regardless of its Type's default. Each eligible capsule packaged as an agent — one that has both an alias and a summary, since a subagent needs a name and a description — becomes an agent definition:

  • the capsule body is the subagent's system prompt,
  • the capsule alias (slug) is the subagent's name (its invocation identity),
  • the capsule summary is the required description,
  • typed fields become the subagent frontmatter (tools, model, …).

Running one: claude --agent

Once an agent is available to Claude Code, the quickest way to use it is to start a session as that agent:

claude --agent forge

Claude Code's startup banner showing the session identity @dbprosites:forge, above a status line reading manual mode on · ? for shortcuts · ← for agents.

The whole session then is that agent: it runs on the agent's system prompt and configuration rather than being a general session that hands work to it. Claude Code shows which one it started as in the header, and opens the agent picker. One field is known not to carry on this path — see the note below.

The agent has to be there first. --agent only finds agents Claude Code already knows about, so one of the delivery paths below has to have happened first: the plugin installed from a marketplace (which is how the @dbprosites:forge in the screenshot above got there), the definitions synced into the repo, a definition in your user directory (~/.claude/agents/), or the JSON passed in with --agents. With none of those, the flag has nothing to match.

The machine in the screenshot happens to have the plugin installed; an agent delivered by project sync launches exactly the same way.

You can also stay in a normal session and hand one task to a subagent — "use the forge subagent to…" — which is what Claude Code's own invoking subagents explicitly covers. Use --agent when the whole session should be that agent; ask for it by name when only one step should be.

One difference worth knowing: skills an agent preloads apply when it is spawned as a subagent, and currently do not when it runs as the main session via --agent. That is observed Claude Code behaviour rather than a documented guarantee, so treat it as something to verify rather than to design around.

Which delivery path do I use?

There are three ways to get a Symbol agent into a Claude Code session. They exist because they solve different problems — pick by how the agent will be run.

PathWhat it isReach for it when
1. Project syncWrite <name>.md files into .claude/agents/Humans, day to day.
2. Ephemeral --agentsEmit JSON for claude --agents at launchThe automated pipeline.
3. PluginPackage the set as an installable pluginDistribution to machines without the repo.

Path 1 — project sync (the default for people). symbol-agents sync writes one <name>.md per agent into a directory in your repo (default .claude/agents/symbol/). The definitions live in version control next to the code, teammates get them by cloning, and Claude Code hot-reloads edits without a restart. Because the artifacts are ordinary files in your repo, this is also the only path where you can hand-author frontmatter Symbol does not render — hooks (see what each path carries below; mcpServers is now rendered, see MCP servers). Put hooks in a separate, hand-written agent file: editing a synced one makes sync --check report it as drifted.

Path 2 — ephemeral session definitions (the pipeline path). symbol-agents emit prints the JSON document claude --agents "$(…)" accepts and writes nothing to disk:

claude --agents "$(node packages/mcp/bin/symbol-agents.mjs emit)"

The definitions are always current (they are rendered fresh from the capsules at launch), and — crucially — --agents definitions sit above project and user scope in the precedence order, so a stale on-disk file cannot shadow them. A scripted run should not depend on a sync step having happened first, which is exactly why the pipeline uses this path. emit --manifest <file> records the capsule id + content hash of each agent it launched, so a run can log which capsule versions it actually used.

Path 3 — plugin distribution. Publishing a workspace's marketplace packages its agents alongside its skills into an installable plugin (/plugin marketplace add <url>). This gets agents onto machines that do not have the repo checked out and versions the whole set as a unit. It has a real ceiling — see what the plugin path drops below. The Plugin Marketplace page covers enabling a marketplace, the install command, and protecting, rotating, and recovering the install URL.

Recommendation: the automated pipeline uses Path 2, humans use Path 1, and distribution to repo-less machines uses Path 3.

Precedence: what shadows what

When two definitions share a name, Claude Code resolves the conflict by scope, in this order (highest wins):

  1. Managed settings (organization deployment) — not used by Symbol.
  2. --agents CLI definitions (Path 2) — above project and user scope.
  3. Project .claude/agents/ (Path 1), closest-to-cwd first for nested dirs.
  4. User ~/.claude/agents/.

Plugin agents (Path 3) are always namespaced as plugin-name:agent-name, so they never collide with a local definition. The namespaced name always resolves; a bare name also resolves when nothing else installed answers to it — the screenshot above shows --agent forge starting @dbprosites:forge on a machine with no local forge. Use the namespaced form when more than one source could match, since that resolution is observed behaviour rather than a documented rule.

The dangerous case is two files in the same project tree with the same name: Claude Code loads only one, chosen by filesystem read order. Nothing warns you. That is why name identity matters (next section).

Names and collisions

A subagent's name is its invocation identity (@-mention, --agent <name>), so Symbol uses the capsule's alias verbatim rather than a generated id — a human-chosen, charset-compatible name. Aliases are unique within a Type, so the source cannot produce a collision inside one Type. Two different agent Types can still hold a capsule with the same alias, so symbol-agents sync:

  • refuses a fetched set that contains duplicate names (naming the offenders) rather than letting filesystem read order silently pick a winner, and
  • refuses to overwrite a <name>.md file its manifest does not own, so a hand-written agent is never clobbered by a sync.

Am I still in sync? (drift detection)

symbol-agents sync writes a sidecar manifest.json recording, for every materialized agent, its capsule id, ref, and content hash. symbol-agents sync --check renders nothing — it compares the live export against that manifest and the files on disk, and exits non-zero listing anything that is:

  • stale — the source capsule changed, or the file was hand-edited;
  • missing — an agent exists in Symbol but not on disk;
  • orphaned — a file the manifest owns whose capsule is no longer an agent.

A clean --check is your proof that a materialized definition still matches its capsule.

What each path carries

Not every frontmatter field survives every path. In the table, ✅ means the delivered definition still carries the field — Claude Code honors it on that path:

FieldRendered by Symbol?Project (1)--agents (2)Plugin (3)
tools, disallowedTools✅ v1 field
model, maxTurns, skills, color✅ v1 field
effort, memory, background, isolation✅ v1 field
permissionMode✅ v1 fielddropped
mcpServers✅ v1 fielddropped
hooks❌ not in v1hand-authored onlydropped

Two separate limits are at work, and it is worth keeping them apart:

  • Symbol's v1 allowlist decides what a capsule can express. hooks is not a v1 field, so no path gets it from a capsule — on Path 1 you can hand-author it into your own agent file, and there is no equivalent on Paths 2 and 3.
  • The plugin ceiling decides what survives delivery. Plugin subagents ignore hooks, mcpServers, and permissionMode. Two of those, mcpServers and permissionMode, are v1 fields, so they are the ones you can actually trip over: publishing an agent that sets one succeeds but surfaces a warning, and the plugin-delivered definition is rendered without it — the plugin path cannot carry them. If you need permissionMode honored or the agent to reach an mcpServers server, deliver that agent via Path 1 or Path 2.

If a Type packaged as Agent omits an alias or a summary on a capsule, that capsule is skipped (a subagent must have a name and a description). The export reports each skip so nothing fails silently.

MCP servers (mcpServers)

An agent authored in Symbol is written against the capsules it will read, so it needs a way to reach Symbol's own MCP server. Declare that with the mcpServers field (Type field name mcp_servers, or agent_meta.frontmatter). Its value is a list of server names, not a config object:

  • The reserved name symbol is expanded by the renderer into the inline definition of Symbol's hosted endpoint (type: http, url: https://mcp.symbol.chat/mcp). No credential is written into the artifact. The installing user authenticates at first use through the endpoint's OAuth 2.1 flow, so the grant is per-user and revocable and nothing secret is distributed in the (git-backed) marketplace repo.
  • Any other name (for example github) renders as a plain reference to a server the installer has already configured under that name. Symbol cannot know a third party's config, so arbitrary inline server definitions are not authorable in v1.

A capsule with mcp_servers: ["symbol", "github"] renders:

mcpServers:
  - symbol:
      type: "http"
      url: "https://mcp.symbol.chat/mcp"
  - "github"

Plugin delivery drops mcpServers. Claude Code ignores the field on plugin-delivered subagents (a security ceiling, the same as permissionMode), so a plugin-delivered Symbol agent cannot reach Symbol this way. Publishing still succeeds and warns you; deliver via Path 1 (project sync) or Path 2 (--agents) when the servers must arrive with the agent.

Composed-body edge. A capsule whose body is composed from its fields (a borrowing/fielded agent Type) is exempt from the disallowed_field check, so it may already carry a field named mcp_servers. Once this field is honored, such a capsule's rendered frontmatter gains the mcpServers: block — the renderer honoring a value its author wrote, which is the intended behavior.

Symbol feature fields (symbol_default)

Symbol feature fields are reserved, symbol_-prefixed field names that configure Symbol-aware behavior instead of carrying content or frontmatter. The first member is symbol_default: the agent's default Symbol workspace.

Declare it as an ordinary text field (new agent Types seeded by the create wizard already carry it). The stored value is either the literal "personal" or an organization workspace UUID — never the display name, so a workspace rename never stales the value. In the web app the field renders as a workspace picker; over the API/MCP, pass "personal" or a UUID from list_workspaces.

When a served agent capsule sets the field, the renderer prepends one line to the system prompt, naming the workspace by its current name:

References to Symbol MCP Types and capsules, unless otherwise stated, belong to the "Databoy Pro Sites" workspace.

so agent prose can say "save it to the Tasks Type" without repeating the workspace each time. The line is body text, not frontmatter — symbol_default never appears in the rendered YAML, and the raw stored value never appears in the prompt.

Behavior at the edges:

  • Unset or removed — nothing is added; the rendered artifact is byte-identical to one without the field. If you do not want the agent body altered, simply remove the field (or leave it blank).
  • Write validation — a value that is not "personal" or the UUID of a workspace you can access is rejected at save with a field-scoped error.
  • Workspace later deleted — the agent still renders and serves, just without the preamble line, and the publisher report / plugin sync surfaces a warning naming the capsule. An environment change degrades the enhancement; it never delists the agent.
  • Content hash — setting the field changes the rendered artifact, so symbol-agents sync --check reports a one-time drift for capsules that adopt it. That is correct: the served definition genuinely changed.

Because the names are reserved by prefix, a user field named symbol_default on any Type acquires this validation on write.

Tools: foreground vs background

If a subagent omits tools, it inherits every tool available — least surprise, but maximum privilege. List an explicit tool set on any agent that should run with least privilege. Note also that a background subagent keeps its MCP tools but runs with only a fixed built-in subset of the standard tools; a foreground subagent uses the full set. The definition is identical either way — the effective tools differ by how Claude Code runs it, so an agent that must write files should not be launched in the background.

On this page