Profiles

Table of contents

  1. What is a profile?
  2. Profile structure
    1. _base/settings.base.json
    2. CLAUDE.md (at profile root)
    3. .claude/settings.overrides.json
    4. profile.yml
  3. 3-layer merge
  4. Built-in profiles
  5. Listing profiles
  6. Profile resolution order
  7. Switching profiles
  8. Settings profiles — independent settings layer
    1. Two-axis model
    2. Environment variable
    3. State tracking
  9. Querying the active profile
  10. Launching Claude with profile flags
  11. Creating a custom profile
  12. Profile chaining
    1. How chaining works
    2. Per-entity behavior in chains
    3. CLAUDE.md concatenation
    4. Querying the active chain
    5. Example: combining a coding base with operator customizations
    6. Linking external profile dirs into the chain

What is a profile?

A profile is a named configuration bundle that controls:

  • Which agent system prompt (CLAUDE.md) Claude Code loads
  • Which settings overrides are applied (permissions, env vars)
  • Which MCP tool categories are enabled (via MCP_CATEGORIES)
  • Which skills, agents, commands, and rules are symlinked
  • Model selection, turn limits, permission mode, and timeout settings (via agentihooks claude)

Profiles are stored under profiles/<name>/ in the repository or in a linked bundle.


Profile structure

profiles/
├── _base/
│   └── settings.base.json          # Canonical settings (hooks, permissions, MCP servers)
├── default/
│   ├── CLAUDE.md                    # Agent system prompt (at profile ROOT)
│   ├── profile.yml                  # agentihooks metadata + claude launch config
│   └── .claude/
│       ├── settings.overrides.json  # Per-profile settings overrides
│       ├── .mcp.json                # Profile MCP servers
│       ├── skills/                  # Profile-specific skills
│       ├── agents/                  # Profile-specific agents
│       ├── commands/                # Profile-specific commands
│       └── rules/                   # Profile-specific rules
└── coding/
    └── ...                          # Same structure

_base/settings.base.json

This is the single source of truth for all settings. It contains:

  • Hook event wiring (hooks -> shell commands)
  • Tool permission allowances
  • MCP server definitions

All paths use /app as a placeholder. The install script substitutes /app with the real repo path at render time.

CLAUDE.md (at profile root)

The agent’s system prompt. This file lives at the profile root (not inside .claude/). The install script copies it to ~/.claude/CLAUDE.md (as a real file, not a symlink, for WSL/Windows compatibility). In chain mode, multiple profiles’ CLAUDE.md files are concatenated.

.claude/settings.overrides.json

Per-profile settings overrides that are merged on top of _base/settings.base.json during install. Lives inside the .claude/ subdirectory.

profile.yml

Contains fields for both agentihooks and the agentihooks claude launcher:

agentihooks fields (read by install.py):

  • name – profile identifier
  • description – shown by --list-profiles
  • mcp_categories – comma-separated tool categories to enable (default: all)

claude launch fields (read by agentihooks claude):

  • claude.model, claude.max_turns, claude.timeout – passed as Claude Code CLI args
  • claude.permission_mode – e.g. bypassPermissions maps to --dangerously-skip-permissions
# agentihooks fields
name: coding
description: "Autonomous coding agent"
mcp_categories: aws,utilities,observability

# claude launch config
claude:
  model: sonnet
  max_turns: 80
  permission_mode: bypassPermissions

3-layer merge

When agentihooks init runs, skills, agents, commands, rules, and MCP servers are merged from three layers:

  1. agentihooks built-in.claude/ in the agentihooks repo
  2. Bundle global.claude/ in the linked bundle root
  3. Profile-specificprofiles/<name>/.claude/

Later layers override earlier ones. This lets you start with a shared base, add team customizations via the bundle, and fine-tune per profile.


Built-in profiles

Profile Mode Secrets Deny Ask Best for
default auto standard Push to main/master, force push (empty) General use — autonomous but protected branches are sacred
coding acceptEdits strict Protected branch pushes, merge, gh CLI git push, rm -rf, docker, kubectl Feature branch development, safe coding
admin bypassPermissions warn (none) (empty) Infrastructure, admin tasks, full trust

These are settings profiles — they control permissions and tool access. Combine them with any persona profile (which controls rules, CLAUDE.md, behavioral instructions) using the two-axis model.


Listing profiles

agentihooks --list-profiles

Example output:

Available profiles:
  default
  coding
  admin

Profiles from both the agentihooks repo and linked bundle are listed.


Profile resolution order

When agentihooks init runs, the profile is resolved through this precedence chain:

  1. --profile CLI flag — explicit selection
  2. AGENTIHOOKS_PROFILE env var — useful in CI/Docker
  3. state.jsontargets.global.profile — previous install remembered
  4. Interactive prompt (if TTY) — defaults to default on empty input
  5. Hardcoded default (non-interactive) — headless fallback

If --profile is passed with an empty value (--profile ""), it is treated as unset and falls through to step 2+. This means the default profile is always the final fallback — no profile flag, empty profile flag, or missing env var all resolve to default.

agentihooks init                    # → resolves to "default" (or previous install)
agentihooks init --profile ""       # → resolves to "default" (empty = unset)
AGENTIHOOKS_PROFILE= agentihooks init  # → resolves to "default"

The default profile ships with agentihooks at profiles/default/. Bundle profiles (in a linked bundle’s profiles/ directory) are also discoverable — built-in profiles are checked first, then bundle profiles.


Switching profiles

Re-run init with --profile:

agentihooks init --profile coding

Or set the AGENTIHOOKS_PROFILE environment variable so you don’t have to pass --profile every time:

export AGENTIHOOKS_PROFILE=coding
agentihooks init

This is especially useful in CI/Docker automation where the profile is set once in the container environment.

Either way, the command atomically:

  1. Writes ~/.claude/CLAUDE.md (copy for single profile, concatenation for chains)
  2. Updates MCP_CATEGORIES in the hook environment
  3. Re-merges settings overrides (sequential deep merge across all chained profiles)
  4. Re-symlinks skills, agents, commands, and rules (3-layer merge, additive across chain)
  5. Re-merges MCP servers (additive across chain)

The switch takes effect on the next Claude Code session.


Settings profiles — independent settings layer

Sometimes you want to change what tools and permissions are available without changing who the AI is. The --settings-profile flag provides a second axis:

# Full install: Anton persona + admin settings overlay
agentihooks init --profile anton --settings-profile admin

# Quick switch: change settings layer only, keep persona intact
agentihooks settings-profile admin

# Revert to persona defaults
agentihooks settings-profile --clear

Two-axis model

Axis Controls Source files
Persona (--profile) Rules, CLAUDE.md, skills, agents, commands CLAUDE.md, .claude/rules/, .claude/skills/, etc.
Settings (--settings-profile) Permissions, env vars, tool allowlists, MCP settings.overrides.json, .claude/.mcp.json

The settings profile overlay is applied after the persona profile’s settings overrides, so it wins on conflicts. Only settings.overrides.json and .mcp.json are read from the settings profile — rules, CLAUDE.md, skills, agents, and commands are ignored.

Environment variable

export AGENTIHOOKS_SETTINGS_PROFILE=admin
agentihooks init --profile anton  # automatically uses admin settings overlay

State tracking

Both axes are persisted in ~/.agentihooks/state.json:

{
  "targets": {
    "global": {
      "profile": "anton",
      "settings_profile": "admin"
    }
  }
}

The sync daemon watches both profile directories and re-applies the correct layers when source files change.


Querying the active profile

agentihooks --query

The command checks the current directory for .agentihooks.json first. If a local profile is set, it shows:

coding (local)

Otherwise it falls back to the global profile:

anton (global)

See Per-Project Configuration for details on local profile overrides.


Launching Claude with profile flags

The agentihooks claude command (alias: agenti) reads the claude: section from the active profile’s profile.yml and maps it to Claude Code CLI flags:

agentihooks claude           # launch with profile settings
agenti                       # same thing (alias installed by init)

For example, permission_mode: bypassPermissions in profile.yml translates to --dangerously-skip-permissions.


Creating a custom profile

  1. Copy an existing profile:
    cp -r profiles/default profiles/myprofile
    
  2. Edit profiles/myprofile/profile.yml to set model, turns, and categories.

  3. Edit profiles/myprofile/CLAUDE.md with your custom system prompt.

  4. Optionally add profile-specific assets in profiles/myprofile/.claude/ (skills, agents, commands, rules, .mcp.json, settings.overrides.json).

  5. Install the new profile:
    agentihooks init --profile myprofile
    

Profiles affect the agent’s persona, tool access, and asset selection but not the underlying hook behavior. Hooks are always wired from _base/settings.base.json regardless of profile.


Profile chaining

You can combine multiple profiles by separating them with commas:

agentihooks init --profile coding,anton

Profiles are applied left to right — the last profile has highest priority for simple values, and all profiles contribute additively for hooks, rules, skills, agents, commands, and MCP servers.

How chaining works

Given --profile coding,anton:

settings.base.json                    ← base hooks + defaults
        ↓
coding/settings.overrides.json        ← merged on top (dicts merge, hooks append)
        ↓
anton/settings.overrides.json          ← merged on top (dicts merge, hooks append)
        ↓
~/.claude/settings.json               ← final result

Per-entity behavior in chains

Entity Chain behavior
Settings Sequential deep merge — each profile merges on top. Dicts combine, hooks append, simple values = last wins.
Rules/Skills/Agents/Commands Additive across all profiles. Same filename = later profile wins.
CLAUDE.md Concatenated into one file with --- separators and <!-- profile: name --> markers. All profiles’ system prompts are active.
MCP servers Additive — all profiles’ servers accumulate.
Connectors / OTEL Last profile in chain provides these.

CLAUDE.md concatenation

In chain mode, all profiles’ CLAUDE.md files are concatenated into a single real file at ~/.claude/CLAUDE.md. Each section is marked with an HTML comment:

<!-- profile: coding -->
# Coding Agent
...

---

<!-- profile: anton -->
# Anton Profile
...

Claude Code loads the entire file as its system prompt, so instructions from all chained profiles are active simultaneously.

In single-profile mode, CLAUDE.md is a direct copy of the source file. In chain mode, it’s a rendered concatenation. Both are real files (not symlinks) for WSL/Windows compatibility. The sync daemon detects source changes and re-renders automatically.

Querying the active chain

agentihooks --query

Single profile output:

anton

Chain output:

chain: [coding, anton]

Example: combining a coding base with operator customizations

agentihooks init --profile coding,anton

This gives you:

  • coding’s git safety rules, retry breaker, security rules
  • anton’s operator behavioral model, delegation map, response template
  • Both profiles’ settings merged (hooks appended, env vars combined)
  • Both profiles’ rules, skills, agents, commands all present

Linking external profile dirs into the chain

Profiles outside the agentihooks repo and the linked bundle can be added to the chain via link-profile:

# One-shot: register an external dir, append to chain, re-install
agentihooks link-profile link ~/dev/brain-profile

# If the dir basename collides with a built-in or bundle name, alias it
agentihooks link-profile link ~/dev/anton-fork --name anton2

# Inspect linked entries
agentihooks link-profile list

# Remove from chain (sweeps any orphan symlinks pointing into the linked dir)
agentihooks link-profile unlink brain-profile

By default link auto-appends the new name to the active chain and re-runs agentihooks init, so a single command takes a chain from anton to anton,brain-profile end-to-end. Use --no-append to register the path without modifying the chain, or --no-init to skip the immediate re-install. Built-in/bundle names always win on collision — the link is rejected unless you pass --name <alias>. Linked dirs whose path is later deleted from disk are warn-skipped from the chain on the next agentihooks init, with a hint pointing at link-profile unlink. See agentihooks link-profile in the CLI reference for full flag and state details.