Bundles
A bundle is a single external directory containing all your personal agentihooks customizations — custom profiles, MCP configs, skills, agents, commands, and rules. Agentihooks is the engine; the bundle is your data.
For purpose built swarms, use Swarm Overlays or the packaged swarm-maker skill to add capabilities to the package base roles, including documentation.
Public example:
agentihooks-bundle-example— a minimal, working bundle you can clone, inspect, and fork as the starting point for your own.
Quick Start
# No bundle yet: lay out an empty one, git init it and link it
agentihooks bundle new ~/dev/my-tools
# Add an overlay profile worn on a base role, then validate it
agentihooks overlay new backtest-tuner --wears engineer
agentihooks overlay check backtest-tuner
# Commit it: a rendered agent records the bundle HEAD at render time; uncommitted edits are not in it
git -C ~/dev/my-tools add README.md enforcements.json .claude profiles
git -C ~/dev/my-tools commit -m "Add backtest-tuner overlay"
# Link your bundle and install (one command)
agentihooks init --bundle ~/dev/my-tools
# See everything available
agentihooks --list-profiles
# Use a bundle profile
agentihooks init --profile my-custom-profile
# Chain multiple profiles (comma-separated)
agentihooks init --profile coding,my-custom-profile
# Update bundle from remote
agentihooks bundle pull
Bundle Layout
my-tools/ <- the bundle directory
├── enforcements.json # Bundle-global enforcements (optional "matcher" per entry)
├── .claude/ # Bundle-global assets (layer 2 of 3-layer merge)
│ ├── .mcp.json # Bundle MCP servers
│ ├── CLAUDE.md # OPTIONAL — shared directives prepended ahead of every profile
│ ├── skills/ # Bundle-global skills
│ ├── agents/ # Bundle-global agents
│ ├── commands/ # Bundle-global commands
│ ├── rules/ # Bundle-global rules
│ └── conditions/ # Bundle-global conditions, run in place (docs/hooks/conditions.md)
└── profiles/
├── infra-ops/ # Custom profile
│ ├── CLAUDE.md # System prompt (at profile ROOT)
│ ├── profile.yml # name, description, otel config, allowedOverlays, claude launch config; an overlay sets kind: overlay and wears: [base roles]
│ ├── enforcements.json # Profile enforcements
│ └── .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
│ └── conditions/ # Profile conditions; same filename overrides the bundle's
└── restricted/
└── ... # Same structure
How It Works
agentihooks init --bundle <path>stores the bundle path in~/.agentihooks/state.jsonand runs the global install- On subsequent
agentihooks initruns, the linked bundle is automatically used - Profiles inside
profiles/are auto-discovered by--list-profiles agentihooks init --profile <name>checks built-in profiles first, then bundle
Only one root bundle can be linked at a time. Two more slots sit beside it: a shared bundle for a team’s common configuration, and a personal user bundle:
agentihooks bundle link --shared ~/dev/team-bundle # between the root and the user bundle
agentihooks bundle list --shared
agentihooks bundle unlink --shared # the other links and every bundle file stay
agentihooks bundle link --user ~/dev/my-user-bundle # or: agentihooks init --user-bundle <path>
agentihooks bundle list --user
agentihooks bundle unlink --user # the root link and every bundle file stay
--shared and --user are never combined, and one directory fills one slot only.
The root bundle holds the core configuration, the shared bundle a team’s, and the user bundle personal configuration. Sources apply in the order package, root bundle, shared bundle, user bundle, profile chain, so content is later-wins: a shared-bundle file wins over a root-bundle file of the same name and a user-bundle file wins over both. Profile names are first-wins in the order built-in, root, shared, user, linked, package role, so neither a shared nor a personal bundle can shadow a core profile. A configured bundle whose directory is missing is reported on init and not applied. AGENTIHOOKS_SHARED_BUNDLE_PATH and AGENTIHOOKS_USER_BUNDLE_PATH name a shared or user bundle when none is linked. A machine with no shared bundle resolves exactly as before.
3-Layer Merge
When agentihooks init runs, skills, agents, commands, rules, and MCP servers are merged from three layers:
| Layer | Source | Description |
|---|---|---|
| 1 (base) | agentihooks .claude/ | Built-in assets from the agentihooks repo |
| 2 (bundle) | bundle .claude/ | Bundle-global customizations |
| 2a (shared bundle) | shared bundle .claude/ | Team customizations, applied after the root bundle |
| 2b (user bundle) | user bundle .claude/ | Personal customizations, applied after the shared bundle |
| 3 (profile) | profiles/<name>/.claude/ | Profile-specific overrides |
Later layers override earlier ones. This lets you start with the agentihooks base, add team customizations via the bundle, and fine-tune per profile.
For settings, the merge order is: _base/settings.base.json -> bundle -> shared bundle -> user bundle -> profile .claude/settings.overrides.json -> OTEL. Hook arrays append in that order.
For MCP servers: agentihooks + bundle .claude/.mcp.json + shared bundle .claude/.mcp.json + user bundle .claude/.mcp.json + profile .claude/.mcp.json.
Shared CLAUDE.md
~/.claude/CLAUDE.md is assembled from up to three sources, in this order:
[ bundle .claude/CLAUDE.md ] -> [ profile CLAUDE.md (one per chained profile) ] -> [ all enabled bundle manifestos ]
An optional <bundle>/.claude/CLAUDE.md (and the shared bundle’s, then the user bundle’s, after it) holds directives every profile should share, so they are written once instead of duplicated into each profile. It is prepended exactly once per install — including for a chained --profile a,b — inside these markers:
<!-- BEGIN BUNDLE CLAUDE.md (auto-injected by agentihooks init) -->
...
<!-- END BUNDLE CLAUDE.md -->
Because profile content is written after the shared block, a profile can still override shared guidance by restating it. Keep profile CLAUDE.md files to what is genuinely profile-specific.
A worker pod and a remote hive carry only the root bundle, so a seat is refused, before any order or pod is created, when a user bundle is registered on the host that launches it and the seat would run on another machine (bundle unlink --user clears it). A seat on the launching hive keeps its user bundle. The refusal applies to new placements: a launch order published before the user bundle was registered is answered as it stands and is not revoked, so unlink before launching to be sure nothing is placed remotely without its user source.
A linked shared bundle never refuses a launch or a remote placement. It is carried the way the root bundle is: by repository and commit, in the revision the launch records. A host that has no shared bundle linked prints a warning that names the recorded commit and renders without it.
A pinned render (--bundle-revision, used by swarm overlay launches) rebuilds from the recorded commits, copies each into the profile home and records each in its render stamp (bundle_commit, shared_bundle_commit when a shared bundle is linked, user_bundle_commit). It never reads the live shared or user bundle. The revision has three parts whenever a shared bundle is linked, <root>:<shared>:<user>, with an empty user field when no user bundle is linked (<root>:<shared>:), and the two parts <root>:<user> otherwise; a three-part revision never parses as <root>:<user>. The shared field is - when a shared bundle is linked but has no commit to record, such as a directory that is not a git repository: <root>:-:<user>, or <root>:-: with no user. That launch prints one warning and is not refused, and a render of - prints a warning and renders without the shared bundle. A two-part revision recorded before the shared bundle was linked, rendered while one is linked, renders with the shared bundle’s current commit, pinned like a recorded one, prints a warning and stamps shared_bundle_commit with the commit it used; root and user stay pinned. A render refuses a revision that names no user commit while a user bundle is linked, and a three-part revision with an empty shared field (<root>::<user>) while a shared bundle is linked.
Re-running agentihooks init replaces the block in place — it never stacks. If the bundle has no .claude/CLAUDE.md, or no profile in the chain has a CLAUDE.md to prepend onto, the step is a no-op.
Every *.md file under the linked bundle’s manifestos/ directory, and under the shared and user bundles’ manifestos/ directories, is appended in filename order; README.md is excluded. Set AGENTIHOOKS_SKIP_MANIFESTO=uno,dos,tres to omit named manifestos. Names may include or omit .md. CI_MANIFESTO_PATH remains a single-file compatibility override, and MANIFESTOS_DIR can replace the bundle directory. A later bundle’s manifesto replaces an earlier one with the same file stem (root, then shared, then user); MANIFESTOS_DIR and CI_MANIFESTO_PATH bypass the shared and user bundles.
deps.json is read from the root bundle, then the shared bundle, then the user bundle; a later entry replaces an earlier one with the same id, and the dependency stamp is invalidated by a change to any of the files.
A manifesto may open with front matter naming the package roles that receive it:
---
roles: [engineer, planner, qa, master]
---
# Development Manifesto
A rendered profile home receives a manifesto when the chain’s package role (package:<role>) is in its roles, or when the manifesto has no roles key, so manifestos without front matter reach every role. A chain with no package role receives every manifesto. Valid front matter never reaches rendered text; a block that is not a YAML mapping is left in place and its manifesto reaches every role. A bundle profile overrides the roles by name in profile.yml; profiles apply in chain order and a later one wins:
manifestos:
include: [CI-GUARDRAILS-MANIFESTO]
exclude: [DEVELOPMENT-MANIFESTO]
CI_MANIFESTO_ENABLED: "false" in a chain profile’s settings env renders no manifestos for that chain. agentihooks init applies the same filter to the installed profile chain. agentihooks manifestos list [--bundle PATH] prints the roles each manifesto declares, before profile overrides.
Profile Resolution Order
When you run agentihooks init --profile X:
- Check
profiles/Xin the agentihooks repo (built-in) - Check
profiles/Xin the linked bundle - Check
profiles/Xin the linked shared bundle - Check
profiles/Xin the linked user bundle - Check profiles registered with
link-profile, then the packaged base roles - Error if not found
Built-in profiles always take precedence. Name your bundle profiles to avoid conflicts; a name defined in more than one bundle resolves to the earliest of root, shared and user, and init warns.
Permission Tiers
Built-in profiles define escalating permission tiers:
| Profile | Mode | Deny | Ask |
|---|---|---|---|
| default | auto | Push to main/master, force push | (empty) |
| coding | acceptEdits | Protected branch pushes, merge, gh CLI | git push, rm -rf, docker, kubectl |
| admin | bypassPermissions | (none) | (empty) — all tools explicitly allowed |
Evaluation order: deny > ask > allow (first match wins).
New Machine Setup
# 1. Install agentihooks
git clone https://github.com/The-Cloud-Clockwork/agentihooks
cd agentihooks
uv venv .venv
uv pip install --python .venv/bin/python -e ".[all]"
# 2. Clone your tools repo and install with bundle
git clone https://github.com/you/my-tools ~/dev/my-tools
agentihooks init --bundle ~/dev/my-tools --profile default
# 3. Reload shell
source ~/.bashrc
# Done -- all profiles, skills, agents, commands, rules, and MCPs active