Agent Profiles
Profiles define agent behavior: prompts, hooks, environment defaults, and reintegration policy. The canonical format is Markdown with YAML frontmatter. Profiles are runtime-agnostic by default; runtime-specific limitations are enforced through explicit feature compatibility checks, not profile-level allowlists.
Profile Format
---
name: worker
description: Implement the next ready bead.
skills: []
mcp_servers: []
permission_mode: bypassPermissions
memory: project
hooks:
on_start:
- type: command
command: caco agent status set "starting"
timeout: 10
post_tool_use:
- matcher: "Bash|Edit|Write"
hooks:
- type: command
command: caco msg speak "Continuing work"
timeout: 5
reintegration:
mode: direct
---
Implement the assigned bead, validate it, and report completion.
Profile Fields
Frontmatter
| Field | Type | Description |
|---|---|---|
name | string | Profile identifier (e.g., worker) |
description | string | Human-readable description |
agent_types | string[] | Deprecated legacy metadata. It is shown for inspectability only and does not restrict which first-party runtime may spawn the profile. |
skills | string[] | Skill references |
mcp_servers | string[] | MCP server references |
permission_mode | string | Permission model |
memory | string | Memory scope (project, global) |
hooks | object | Hook definitions by phase. Matchers are pipe-separated exact tool names such as Bash|Edit|Write|NotebookEdit; absent tool context does not match. Hook timeout values are enforced by terminating timed-out commands. Codex-backed profiles that declare hooks are bridged with Codex hook features enabled so the runtime exposes /hooks, lists untrusted or modified hooks, and blocks untrusted hooks until reviewed. |
reintegration | object | Reintegration policy. Fields: mode (one of direct, pr_auto_merge, pr_review, pr_submit, artifact_only, none), optional allowed_modes (string[]) restricting which modes the agent may pass at completion time, optional push_tags, optional auto_rebase_retry_limit, and optional dry_run_gate. Modes can carry the ,recorded suffix to require a session-recording summary artefact. |
reintegration.dry_run_gate | object | Pure configuration for future dry-run-preview hook gates. require_clean defaults to false; stage accepts before_reintegration or before_complete; on_missing accepts block or warn. Validation warns when the gate is a no-op and errors when a strict missing-preview gate is paired with auto_rebase_retry_limit: 0. |
disallowedTools | string[] | Claude-runtime tool blocklist. Bridge layer adds these to the runtime's allow/deny configuration so read-only profiles (e.g. bead-drafter, narrator) cannot invoke mutating tools (Edit, Write, Bash). Composes additively with the entries the bridge derives from banned_modes. |
type | string | Runtime hint (e.g. pi, claude, codex). When set, the daemon prefers the matching runtime when launching this profile; omitted profiles are runtime-agnostic and dispatched per the project default. |
environment | map<string, string> | Alias for env retained for compatibility with older inline-config profiles. Prefer env for new profiles. |
restart | string | Persistent-agent restart policy: failure (default), always, or never. See Persistent Agent Restart Policy. |
stop_hook_delay | integer | Seconds the daemon waits after firing on_stop hooks before forcibly terminating the agent process. Lets nudge / save / unclaim hooks finish cleanly. |
persistent | bool | Whether the agent runs indefinitely (default: false). Persistent agents typically use reintegration: { mode: none } and live across reboots; the daemon reuses the same checkout instead of completing the agent. |
banned_modes | string[] | Modes the agent must not enter (e.g., plan) |
authorization | object | Authorization scope ceiling. Field scope takes one of worker (default: own lifecycle and bound-project surfaces only), project_controller (per-project orchestration: spawn/stop/coordinate agents within bound project), or cluster_controller (cluster-wide: cross-project bead filing, fleet-wide remediation). Daemon-enforced; profiles cannot escalate beyond their declared scope. See Authorization Scopes. |
watchdog | object | Watchdog timeout configuration |
stop_nudge_text | string | Text shown when a stop-hook nudges a non-terminal agent |
git_remotes | map<string, string> | Named git remotes to configure in agent checkouts (e.g. github: ssh://git@github.com/org/repo.git). Added via git remote add after checkout creation so agents can push directly. |
shell_rc | string | Inline shell init content sourced into agent sessions. Layered by concatenation: global → project → profile. Must be shell-agnostic (bash and zsh). |
include_caco_instructions | bool | Whether to embed canonical role-appropriate Cacophony instructions (default: true): common guidance plus the resolved one-shot worker or persistent-worker layer, while controller scopes receive common guidance only. Set to false only for a fully custom profile that owns every required tool, safety, and lifecycle instruction. |
stale_timeout_secs | integer | Per-profile override for how long a live agent can go without tool activity before a no-tool-activity advisory is recorded. It does not by itself transition a live tmux/runtime session to stale. Falls back to the global agents.stall_timeout_secs when omitted. |
reintegration_checks | object | Automated pre-reintegration validation gate. Fields: rebase_check (bool), test_command (string), check_command (string, e.g. cargo check --workspace --tests), clippy (bool), abort_on_failure (bool). Env overrides: CACO_REINTEGRATION_TEST_CMD, CACO_REINTEGRATION_CHECK_CMD, CACO_REINTEGRATION_CLIPPY. |
hook_mixins | string[] | Named hook mixin scripts to compose into the profile's hook pipeline (e.g. fast-test-gate). |
env | map<string, string> | Extra environment variables exported into the agent process. Layered after global and project env. |
message_subscriptions | object[] | Per-profile inbox subscription rules (kinds, project filters) used by caco msg delivery. |
comms | object | Per-profile messaging defaults (e.g. allowed broadcast scope, narration verbosity). |
model / provider / effort | string | Override the agent runtime's model selection, provider, and effort/quality knob. Used by Claude/Codex/Pi bridges. |
lifecycle | object | Worker-lifecycle policy: claim mode, completion behaviour, restart policy. See WorkerLifecycle. |
composes | string[] | Profiles loaded before this one, transitively and once each (a cycle is an error). The stack then merges by field type: prompts and hook lists concatenate, lists deduplicate, env lets later profiles win, and policy singletons must agree. |
composes_well_with | string[] | Advisory metadata: profiles known to compose cleanly with this one. Resolver does not enforce; informs operators and profile-creator suggestions. |
completion | object | Custom completion criteria (e.g. exit codes, file presence) consumed by the lifecycle layer. |
nudges | map<string, object> | Named nudge specs the watchdog can post into the agent pane on idle / stale conditions. |
permitted_actions | string[] | Whitelist of high-level operator-action verbs the agent is allowed to invoke (subset of the full action catalog). |
allowed_lifecycle_operations | string[] | Lifecycle verbs (complete, discard, reintegrate, restart) the profile may run on itself or other agents. |
cross_project_bead_permissions | object[] | Per-other-project bead-mutation grants (e.g. controller agents updating beads in projects they don't own). |
background_image | object | Background image source for surfaces that render an agent backdrop (TUI badge, web detail, android). |
voice | string | Per-profile TTS voice override (e.g. nova, sage); narration and message-speak default to this when set. |
initial_prompt | string | Inline first user-turn message injected after agent setup, before main work. |
pi_extra_config_dirs | string[] | Additional Pi config directories to include when launching a Pi-backed agent (skills, prompts, themes). |
override_pi_agent_dir | string | Read-only operator-owned Pi agent directory passed directly to Pi instead of materializing managed .pi-agent configuration. |
warmup_cache_injection | object | Disabled-by-default advisory warmup-cache prompt policy. Optional fields are exactly enabled, max_age_seconds, and max_chars; unknown keys are rejected, default policy values are false, 86400, and 2048, and age/character budgets are clamped to at least 1 when materialized. The map merges as a singleton after composed profiles; later profiles override earlier profiles, and profile values overlay project defaults per subfield. This policy can permit bounded, labeled startup hints from matching cache artifacts but does not authorize live session extraction or arbitrary prompt injection. |
short_name_strategy | string | Controls how short / spoken names are derived for agents using this profile. Defaults to adj_noun when omitted. |
Cross-project bead permission mixins are intentionally explicit. cacophony-bead-scope grants least-privilege Cacophony-only mutation for orchestration/runtime follow-up work, while global-bead-scope grants create/update/claim/close permissions across all currently configured projects for dedicated global board triage or repair roles. Composing multiple bead-scope mixins intersects their grants rather than unioning them, so use one scope mixin at a time unless the intersection is intentional.
Release skew. Daemons resolve shipped profiles from the default branch, which can be newer than the release they run.
At runtime, a value outside that release's closed vocabularies (reintegration modes, MCP servers, permission modes, hook mixins, authorization capabilities, message subscriptions, lifecycle operations, bead actions, and inbox sender classes) is ignored with a one-line daemon warning instead of failing the profile, and an allowlist left empty denies everything.
Scalars whose default would be broader, such as authorization.scope, stay strict.
caco config validate stays strict too, and repository validation rejects a shipped profile that uses a value caco_profile::vocabulary::VOCABULARY_INTRODUCTIONS records as newer than the workspace release: land the code first and change the profile after the next release ships.
Body
The Markdown body after the frontmatter contains prompt material and instructions for the agent.
Hook Phases
| Phase | When It Runs |
|---|---|
on_start | After agent setup, before main work |
on_stop | When the agent is being stopped (can nudge non-terminal agents back to work via stop_nudge_text; respects stop_hook_delay) |
on_session_end | When the agent session ends (can block premature exits by emitting a non-zero hook decision) |
on_notification | When the agent receives a notification (inbox arrival, watchdog wake, choice resolution) |
post_tool_use | After each tool invocation (supports matcher filters) |
before_tool | Before each tool invocation |
after_tool | After each tool invocation |
before_complete | Before completion is recorded |
before_reintegration | After before_complete succeeds but before git merge/PR operations. Intended for pre-merge validation (config validate, template linting). Failures block reintegration. |
on_merge_commit | After successful merge commit (non-fatal) |
on_complete | After successful completion |
on_failure | After a failure |
Hook Entry Structure
hooks:
on_start:
- type: command # Required: action type
command: caco bd claim # For type=command
timeout: 10 # Optional: timeout in seconds
post_tool_use:
- matcher: "Bash|Edit|Write" # Optional: limit to matching tools
hooks: # Nested hooks
- type: command
command: caco msg speak "Continuing"
timeout: 5
Hook Action Types
Currently supported:
command— executes a shell command in the agent's context
Unknown action types are rejected at validation time, not silently dropped. Tool matchers use pipe-separated exact names; nested matcher groups skip all child hooks when the current tool is absent or does not match. Codex-managed profiles use Codex’s own trust surfaces: operators may persist [hooks.state."<hook-key>"].trusted_hash in config or define managed hooks in requirements.toml with [features].hooks = true and [hooks].managed_dir; Cacophony does not silently forge trust for non-managed hooks.
Reintegration Modes
| Mode | Behavior |
|---|---|
direct | Land without review: a squash merge into the target with backend: local_merge, or a PR with GitHub native auto-merge armed with backend: pull_request |
pr_auto_merge | Open PR, merge when allowed |
pr_review | Open PR, stop after reporting |
pr_submit | Publish one ready PR on a stable per-bead head, record it on the beads, and stop |
artifact_only | Record branch/commits without merging |
none | No reintegration (read-only agents like controllers and analyzers) |
Modes can be composed with the recorded suffix (e.g. direct,recorded) to require a session-recording summary artefact at .cacophony/agent/<id>/summary/<NNNN>/summary.md before the merge proceeds. See the session-recording mixin.
Project config can also set projects[].integration.default_intent and projects[].integration.backend. For example, a profile whose default mode is pr_review opens a review PR by default, while backend: pull_request lets even direct/review intents use the declared fork/upstream topology rather than ad hoc checkout remotes.
Agent Compatibility
The canonical profile format is Claude-style (Markdown + YAML frontmatter). Agent types that don't natively consume this format are supported through bridges:
| Tier | Description | Agent Types |
|---|---|---|
| Tier 1 | Direct profile consumption | Claude-compatible agents |
| Tier 2 | Bridge layer adapts profile | Codex, Pi |
Example: Default Agent Profile
---
name: agent
description: General-purpose Cacophony agent.
hooks:
on_start:
- type: command
command: caco agent status set "starting"
timeout: 10
on_complete: []
reintegration:
mode: direct
---
You are a Cacophony agent. Follow the project conventions,
implement assigned work, validate with tests, and report completion.
Example: Bead Implementer Profile
---
name: worker
description: Claim and implement the next ready bead.
hooks:
on_start:
- type: command
command: caco bd claim
timeout: 10
on_complete:
- type: command
command: 'caco bd close "${CACO_BEAD_ID:?set bead id}"'
timeout: 10
reintegration:
mode: direct
---
Claim the next ready bead, implement it following the project spec,
run tests, commit with the bead ID, and report completion.
Validation belongs to branch protection and CI: there is no daemon reintegration gate, and a PR merges only once its required checks pass. See the delivery model.
Embedded Profiles, Flows and Pi Overlays
The caco binary embeds the checked-in .cacophony/profiles catalog, the Cacophony project flows and the .cacophony/pi overlays at build time, so an agent needs no Cacophony canonical checkout to compose its profile.
A profile's pi_extra_config_dirs entry that names a canonical-checkout overlay is materialized from the binary into <agent>/pi-overlays/<name> at spawn, relaunch and refresh, replacing stale files; any other entry, such as a developer's own worktree overlay, is used as written.
caco profile list and caco profile show report which repository, user or embedded source each profile came from.
Config-Based Profiles
Profiles can also be defined inline in config.yaml:
profiles:
- name: agent
hooks:
on_start:
- type: command
command: caco agent status set "starting"
timeout: 10
on_complete: []
reintegration:
mode: direct
Persistent Agent Restart Policy
Persistent agents support a granular restart policy that controls when the daemon auto-restarts the agent after it exits:
| Policy | Behavior |
|---|---|
failure (default) | Restart only on failure/crash. Does not restart after operator stop or pause. |
always | Restart on any non-running state (failure, stop, pause, daemon restart). |
never | Never auto-restart. |
The restart policy is set on the persistent agent declaration itself, often inside a projects.yaml entry (for this repository, in the separate cacophony-config repository) under projects.<project>.agents.persistent:
projects:
cacophony:
agents:
persistent:
my-agent:
imports:
- agents/persistent.yaml
profile:
- technical-writer
restart: failure # failure | always | never
restart_backoff_secs: 30 # minimum backoff between restart attempts
Composed reintegration policy carries mode, allowed_modes, push_tags, auto_rebase_retry_limit and dry_run_gate, each taken from the most specific profile that sets it.
To see how a stack resolves without launching anything, run caco profile compose --dry-run <profile>… (or --agent <declaration>). Each field shows its value, the profiles that contributed and the merge rule used: concatenation, union or intersection for lists such as skills and MCP servers, last-wins for maps and scalars, and must-agree fields flagged when profiles conflict. --summary-only shows just the counts; --show-prompt adds the composed prompt.
Shared agent snippets under agents/ in the config repository are role-scoped rather than one universal persistent stack.
| Snippet | Compose it for |
|---|---|
pi-common.yaml | Pi runtime mixins |
base.yaml | workers that inherit reintegration, session-recording and lifecycle guidance |
persistent.yaml | endless dev workers that keep generic worker-pool behaviour |
persistent-specialist.yaml | long-lived specialists whose role profile owns their work selection; adds the persistent-specialist guard, so surface owners such as caco-vm reintegrate scoped work without draining the generic queue |
persistent-observer.yaml | observers and controller-style agents that may have no bead; adds the persistent-observer guard and must not import base.yaml or persistent.yaml, so they never get worker-only completion instructions |
log-monitor.yaml and other role snippets | the final composition of that role |
default-codex.yaml, codex-dev.yaml | persistent dev declarations on the Codex runtime with the shared dev, auto-claim and session-recording stack |
- Claim precedence: When the general
devprofile is composed withauto-claim, the intended precedence is explicit user/controller instruction first; if no instruction is pending, the worker may claim ready queue work from the open board instead of waiting indefinitely. If that worker deliberately unclaims an out-of-lane bead, the follow-up no-ID claim may use the request-scoped--exclude-bead-idskip list so the same worker does not immediately reclaim the bead it just declined. - This fleet's config: The shared
pi-common.yamlnow composespi-home-overrideso checked-in Pi workers pass the operator-owned~/.pi/agentdirectory directly instead of materializing a managed copy. The checked-in phone-classsgu24dev workers are split intocaco-dev-s24-codexandcaco-dev-s24-pi, both composing thecaco-mobilemixin so local heavy builds/tests/emulators are avoided and validation is routed to queued or stronger-node surfaces.
Git Remotes
Profiles can declare named git remotes that the daemon automatically configures in agent checkouts after creation:
---
name: update-helper
git_remotes:
github: ssh://git@github.com/org/repo.git
---
This eliminates the need for agents to manually run git remote add. The agent can immediately push via git push github main. When multiple profiles are composed, remote maps are merged with later profiles winning on key conflict.
Shell RC
Profiles can include inline shell initialization content that is sourced into every agent shell session before the runtime launches:
---
name: worker
shell_rc: |
gh() { if command -v gh >/dev/null 2>&1; then command gh "$@"; else echo "gh not available"; fi; }
export EDITOR=vim
---
Shell RC content is layered by concatenation across config levels: global shell_rc → project shell_rc → profile shell_rc. Later layers can override earlier functions. Content must work in both bash and zsh.
Persistent, One-Shot, Composite, and Mixin Profiles
Profile lifecycle and runtime-stack guidance has moved to Profile Lifecycle and Runtime Stacks to keep lifecycle guidance separate from routine field and shipped-profile reference updates. In short: one-shot workers claim and finish one bead, interactive composites wait for operator direction, persistent specialists/observers run indefinitely under role-scoped snippets, and mixins add behavior through composes without becoming standalone launch targets.
The companion page covers compact autonomous role guidance, daemon-native heartbeat/direct delivery, retired compatibility mixins, intentional pi-home-override, and supported prompt/resource refresh without disturbing operator sessions.
Shipped Profiles
ambient-modeauto-claimbasebead-drafterbead-dreamerbead-filercaco-androidcaco-doctorcaco-ioscaco-ios-releasercaco-macoscaco-macos-releasercaco-mobilecaco-profilecaco-releasecaco-surfacescaco-transcriptioncaco-tuicaco-vmcaco-wearablecaco-webcacophony-bead-scopecacophony-fast-testscacophony-queue-workerchangelog-managerchoiceschutzpahcluster-debuggercollab-modeconfig-helpercontrollercorpus-compactorctxdevendlessfilerflow-integratorflow-residentgeneralistgh-pagesgit-checkgithubgithub-read-budgetglobal-bead-scopeimagesinteractiveios-devlight-devlog-monitormaintenance-auditormaintenance-fixermerge-queuenarratorobserver-global-bead-scopeoperatorpebblepersistent-observerpersistent-specialistpersonalizationpersonalization-privatepi-caco-commandspi-checkout-guardpi-heartbeatpi-homepi-home-overridepi-image-guardpi-inboxpi-inbox-disabledpi-looppi-model-selectionpi-self-compactpi-self-nudgepi-self-opspi-sudo-runnerpi-tool-emit-guardplanningpr-monitorpr-queue-fixerpr-queue-mergerproject-bootstrapproject-controllerproject-creatorproject-healthrealtimereflect-sessionrelease-patchrepo-healthrouterself-improvementsession-recordingslack-watcherspeakspec-builderspec-gap-fixerstale-checktechnical-writertendril-mcptest-usertmux-clitranscript-bead-filertranscript-narratortui-animation-optimisertui-optimizerupdate-helpervisitingworkerworkiq-watcherThe canonical controller and project-controller profiles carry the narrow agent_spawn_overrides capability. Other agent callers retain mandatory project spawn defaults and may only add an allowed profile specialization.
The persistent test-user declaration uses a lightweight agent-default snippet instead of the generic dev-worker base. It composes Pi runtime, narration, Tendril, image, tmux, and git-check helpers, but deliberately omits implementation/reintegration mixins such as self-improvement, merge-queue, session-recording, and reflect-session. The role is read-only from a source-code perspective: exercise product surfaces, keep scratch state, speak findings, and file beads for observed issues.
The canonical cluster controller profile treats major remediation as operator-gated work: before mass revival, destructive recovery, or other cluster-wide corrective action, it should narrate the issue and block on caco choices. Only a narrow emergency fallback is allowed when the system is too degraded to present choices, capped at one caco restart attempt and then at most one caco update attempt. It also treats brief daemon unreachable and hook/request failures immediately after restart, update, or config rollout as transient restart-window symptoms that should be retried and rehydrated before being escalated into a new incident. It may also use mid-flight caco agent reintegrate for operator-directed controller-profile, documentation, or control-plane maintenance changes without stopping.
The canonical project-controller profile requires explicit startup wake-path handling for Pi-managed persistent runs. It should inspect the first-party /loop state, create profile-required runtime loops when absent, and report newly armed loops as armed / verification pending until a real loop prompt fires. When the pi-loop mixin is composed, /loop is the preferred runtime-native per-agent wake path; do not create daemon-global caco cron entries for self-check or burndown loops unless the host profile or operator explicitly asks for daemon cron. The repo-owned /loop extension suppresses duplicate prompt deliveries for the same prompt/delivery mode when catch-up or duplicate ticks fire within the bounded duplicate-delivery window, while still allowing the next normal interval delivery. Report degraded autonomy only when no runtime-native wake path can be established or verified in-session. Unmanaged shell-loop or cron-like workarounds that bypass first-party control-surface policy are forbidden.