Profile System

Profiles are directory packages that configure Claude Code execution. Each profile is a directory containing a profile.yml for Agenticore metadata and a .claude/ directory with native Claude Code configuration files.

Agenticore does not bundle any profiles. Profiles come from two external sources: your agentihooks integration and your user directory.

Profile Directory Layout

<profiles-dir>/{name}/
├── profile.yml          # Agenticore metadata (model, turns, auto_pr, etc.)
├── .claude/
│   ├── settings.json    # Hooks, tool permissions, env vars
│   ├── CLAUDE.md        # System instructions for Claude
│   ├── agents/          # Custom subagents
│   └── skills/          # Custom slash-command skills
└── .mcp.json            # MCP server config merged into the job

profile.yml Schema

Field Type Default Description
name string directory name Profile identifier
description string "" Human-readable description
claude.model string sonnet Claude model
claude.max_turns int 80 --max-turns
claude.output_format string json --output-format
claude.permission_mode string bypassPermissions --permission-mode
claude.timeout int 3600 Process timeout in seconds
claude.worktree bool true Pass --worktree to Claude
claude.effort string/null null --effort (e.g. high)
claude.max_budget_usd float/null null --max-budget-usd
claude.fallback_model string/null null --fallback-model
auto_pr bool true Create PR on success
extends string/null null Inherit from another profile

Profile Discovery

Profiles are loaded from two directories. Later sources override earlier ones when names collide.

{AGENTICORE_AGENTIHOOKS_PATH}/profiles/   ← agentihooks integration
~/.agenticore/profiles/                   ← user profiles (always checked)

agentihooks is the authoritative source for organisation-wide profiles. It owns the full profile authoring pipeline — hook wiring, MCP categories, system prompts, and the build_profiles.py generator. It ships as a PyPI package (agentihooks) and is a pip dependency of agenticore — so profiles are already available in the installed package. Set AGENTICORE_AGENTIHOOKS_PATH only when you want to overlay the PyPI install with a live local checkout for development.

User profiles (~/.agenticore/profiles/) are for personal overrides and local experimentation. They always take highest priority.

agentihooks Profiles

Profiles come from the installed agentihooks package, which owns the full profile authoring pipeline. The .claude/settings.json and .mcp.json inside each profile directory are pre-built by agentihooks’ scripts/build_profiles.py, which merges profiles/_base/settings.base.json (hook wiring, permissions) with any per-profile overrides.

In Kubernetes deployments agentihooks is installed from PyPI as part of the container image — no clone, no watcher. Pod restart picks up a new release (bump the agentihooks floor in agenticore’s pyproject.toml or rebuild to pull a newer PyPI version). Set AGENTICORE_AGENTIHOOKS_URL to override with a one- shot clone + editable install for bleeding-edge testing; set AGENTICORE_AGENTIHOOKS_PATH for a dev loopback over a mounted checkout. AGENTICORE_AGENTIHOOKS_SYNC_INTERVAL is retained as a deprecated no-op for backwards compatibility.

Agentihooks Bundle

The agentihooks-bundle repo (AGENTICORE_AGENTIHOOKS_BUNDLE_URL) provides companion configuration passed to agentihooks init --bundle <path>. Unlike agentihooks itself (a PyPI dep), the bundle is still a content repo cloned at startup and has its own background watcher controlled by AGENTICORE_AGENTIHOOKS_BUNDLE_SYNC_INTERVAL (default 300s, 0 disables).

Agentihub — direct provisioning

In Agent Mode, agent packages come directly from agentihub — not from agentihooks profiles. Agenticore’s agent_mode/initializer.py clones agentihub and copies agents/{name}/package//app/package/. A background watcher refreshes the clone every AGENTICORE_AGENTIHUB_SYNC_INTERVAL seconds (default 300, 0 disables).

agenticore   = execution engine (this project)
agentihooks  = hook system + MCP tools (guardrails, integrations)
agentihub    = agent identities (CLAUDE.md, prompts, evaluation)
Variable Description
AGENTICORE_AGENTIHUB_URL Git URL for the agentihub repo
AGENTICORE_AGENTIHUB_PATH Explicit path override (skips cloning)
AGENTICORE_AGENTIHUB_SYNC_INTERVAL Hot-reload interval in seconds (0 disables)
AGENTIHUB_AGENT Agent name to load (matches agents/{name}/ directory)

Writing a Profile

Minimal profile.yml:

name: code
description: "Autonomous coding worker"

claude:
  model: claude-sonnet-4-6
  max_turns: 80
  permission_mode: bypassPermissions
  timeout: 3600
  worktree: true

auto_pr: true

The .claude/settings.json and .mcp.json files are generated by the agentihooks build script. If writing a user profile (~/.agenticore/profiles/), create them manually. Example .claude/settings.json:

{
  "permissions": {
    "allow": [
      "Bash(*)",
      "Read(*)",
      "Write(*)",
      "Edit(*)",
      "Glob(*)",
      "Grep(*)",
      "Task(*)"
    ]
  }
}

And .claude/CLAUDE.md providing system instructions:

# Agenticore Worker

## Guidelines
- Commit with descriptive messages
- Do NOT create PRs — the system handles that
- Focus on the task, be thorough, test your changes

Profile Inheritance

A profile can extend another using the extends field:

name: code-strict
extends: code          # inherits all settings from 'code'

claude:
  max_turns: 20
  effort: high

Child values override parent defaults. The .claude/ files are layered (child overlays parent) during materialization — files present in the child profile replace the parent’s versions; files only in the parent are kept.

Materialization

Before each job, materialize_profile() resolves the profile directory path for tracking and .mcp.json merging.

Claude Code uses ~/.claude/ by default — CLAUDE_CONFIG_DIR is not set. Agentihooks installs profiles into ~/.claude/ at container startup via agentihooks global. The CLAUDE_CODE_HOME_DIR env var is set as a safeguard pointing to the home directory root (e.g., /shared).

Simple profiles (no extends) — zero I/O

The profile directory path is returned for tracking only. No files are copied and no env var override is applied. Claude Code reads config from ~/.claude/.

Profiles with extends — isolated merge directory

When a profile uses extends, the chain must be merged. The merged output goes to an isolated per-job directory for .mcp.json content, which the runner injects into the job CWD.

Local / Docker mode (no AGENTICORE_SHARED_FS_ROOT):

/tmp/agenticore-jobs/{job-id}/
├── .claude/
│   ├── settings.json    ← base first, child overlay on top
│   └── CLAUDE.md
└── .mcp.json            ← mcpServers merged across chain

Kubernetes / shared FS mode (AGENTICORE_SHARED_FS_ROOT set):

/shared/jobs/{job-id}/
├── .claude/
│   ├── settings.json
│   └── CLAUDE.md
└── .mcp.json

The job_config_dir is stored on the job record for auditing. The repo clone is always left clean.

Profile to CLI Args

profile.yml (claude section)
       │
       ▼
build_cli_args()
       │
       ▼
claude --worktree
       --model claude-sonnet-4-6
       --max-turns 80
       --output-format json
       --permission-mode bypassPermissions
       -p "<task>"

The build_cli_args() function in profiles.py converts the claude section of profile.yml into CLI flags. The task is always last with -p.

Profile Resolution and Routing

Request arrives (profile="" or profile="code")
         │
         ▼
 ┌───────┴────────┐
 │   router.py    │
 │   route()      │
 └───────┬────────┘
         │
   ┌─────┴──────┐
   │             │
   ▼             ▼
profile       no profile
specified?    specified
   │             │
   ▼             ▼
validate      use default
exists?       (agentihooks_profile)
   │             │
   └──────┬──────┘
          │
          ▼
   resolved profile name

If the requested profile doesn’t exist, the router falls back to agentihooks_profile (default: coding, set via AGENTIHOOKS_PROFILE).

Template Variables

The --append-system-prompt flag receives dynamic context built from the job at execution time:

Variable Value
JOB_ID Job UUID
TASK Task description
REPO_URL Repository URL
BASE_REF Base branch

These are passed via --append-system-prompt "Job: {id} | Task: {task} | …".