On this page

What a persona is

A persona is a stored launch recipe, not a chat character: boot prompt, model, identity for the breadcrumb, runtime defaults (default_sandbox, default_dangerous, default_polling), competency skill_tags, and two sub-agent flags. One command builder renders it; nothing about a launch is hardcoded in the browser.

The registry is database-authoritative with a code factory reset. public/personas.factory.json is the builtin image: missing builtins are inserted at boot, and a pristine builtin is refreshed when the factory version rises. A row with operator_modified = true is never overwritten. GET /api/personas lists enabled personas; GET /api/personas/:id adds that persona's bound skill_packs and mcp_bindings.

PATCH /api/personas/:id edits an allowlisted field set and takes expectedUpdatedAt for optimistic concurrency (RFC 7232 semantics) — a stale write is refused 409. A persona whose source is governed is publisher evidence mirrored from the DataShield Auth control plane and is **read-only here**: any patch beyond the local narrowings (enabled, subagent_eligible, can_use_subagents) is refused 403, naming the refused keys. A patch made only of those narrowings does not mark a pristine builtin operator_modified, so enabling or disabling a persona keeps it eligible for factory refreshes.

Prompt history is append-only

A boot-prompt change writes a new persona_prompt_history version (author, optional note) and never overwrites a prior one; GET …/prompt-history returns versions newest-first. POST …/prompt-revert with {version} deletes nothing — it appends that version's text as the newest one, noted as a revert. A prompt above the configured byte ceiling is refused 400.

Skill packs

A skill pack is a Claude Code skill the launcher can bind to a persona. lib/skills.js discovers packs under SKILLS_DIR (named default: the instance's .claude/skills, path-delimited for several roots) as a directory (description read from its SKILL.md or README.md), a .zip, or a single .md, and upserts them into commander.skill_packs. A pack that disappears from disk is not deleted — a persona may still bind it. GET /api/skill-packs is the catalog of enabled packs, distinct from a persona's bound set.

PUT /api/personas/:id/skills replaces the bound set transactionally with {skill_packs: [{skill_pack_slug, load_mode, importance}]}; load_mode is preload or on_demand, and anything else resolves to on_demand.

MCP bindings

PUT /api/personas/:id/mcp-bindings (and the equivalent PUT /api/mcp/bindings/:personaId) replace a persona's bindings: server_slug, an optional tool_allow array, require_approval. Bindings are narrow-only against what the instance is entitled to; credentials are commander-mcp's subject.

The cc_config layers

Runtime config resolves through **four layers, low to high: instance ← account ← persona ← session**. Each layer is a flat {tunable_key: value} map, validated against the served capability descriptor (GET /api/cc/capability) on write as well as at launch — fail-closed at both ends. Account and persona maps live behind GET/PUT /api/cc/config/account/:id and …/persona/:id (expectedUpdatedAt409); a session override is persisted on the session row so --resume re-emits it.

POST /api/cc/sessions/:id/launch-config resolves the effective plan and returns { enabled, flags, settingsPath, agentsPath, agentsGated, bootCommands, dropped }. Default-off: with the instance toggle unset it answers enabled: false and an empty plan, so the command builder emits nothing new. The descriptor in this version declares seven tunables:

KeyTypeScopesEmitted as
permission_modeenuminstance, account, persona, sessionCLI flag
effortenuminstance, account, persona, sessionCLI flag
allowed_toolslistpersona, sessionCLI flag
additional_dirslistpersona, sessionCLI flag
output_styleenumaccount, personasettings file
co_authored_byboolinstance, account, personasettings file
startup_commandslistpersona, sessionboot commands

Values that emit to a settings file are written to a per-session settings.json at mode 0600 and passed by path — free-form values are file-delivered, never inlined on a command line. Every refusal is reported in dropped[] with a reason (unknown tunable, scope not permitted, invalid value, a requires CC <version>+ gate, no emit rule): nothing is dropped silently. Emission follows descriptor order, so a plan is deterministic.

A boot command is a literal Claude Code slash-command line typed into the TUI before the boot prompt. A free-form line is honoured only when it starts with / and its first token is a command name the descriptor knows (43 commands in this version); anything else is rejected at write time and again at launch, with the reason in dropped[].

Sub-agent projection and the no-nesting refusal (FEAT-052)

The catalog is the union of discovered .claude/agents/*.md definitions (frontmatter plus a Markdown body that is the agent's system prompt) and personas marked subagent_eligible, projected at read time — personas are never written into the agent table. Projection is refused, and logged, when the persona has no boot prompt or no model: a sub-agent always names its model rather than inheriting the orchestrator's.

A projected persona's prompt is wrapped in a tagged sub-agent boot: the base boot inside <base-boot>, then a contract stating that the delegated task arrives as the first user message, that the default mode is APPEND (the task is done within the base role and its guardrails), and that a delegating agent may opt into REPLACE with a marker — the base boot's guardrails always holding.

Two gates apply to every launch. No nesting: every emitted sub-agent gets the spawn tool unioned into disallowedTools and that tool stripped from any explicit tools allowlist, so the CLI is never handed an allowlist contradicting its denylist — a sub-agent cannot spawn sub-agents. Orchestrator opt-in: the fleet is serialized only when the orchestrating persona has can_use_subagents, decided server-side (a client toggle mirrors it and is not the authority). Bound agents without that opt-in come back agentsGated: true with a warning, never silently; a persona is also filtered out of its own fleet, so there is no self-delegation.

GET /api/cc/agents is the catalog; GET/PUT /api/personas/:id/cc-agents the per-persona fleet (agent_slug, enabled, importance, overrides). Emitted entries are filtered to the descriptor's declared field set, so an unknown frontmatter key cannot ride into a launch.

commander-sessions (the lifecycle a persona launches into), commander-mcp (how a bound server reaches the session), commander-security (operator-hand settings).

You've seen the proof

Ready for a number? Scope your deployment and we'll price it against your own economics.

Get your quote →