Bootstrap Files: What Each Agent File Owns
Your agent’s personality, safety rails, memory index, and local runbook should not all live in one giant prompt. Split them by job so compaction, cache invalidation, and handoffs stay sane.
What this is
Most always-on agent stacks end up with a small pile of bootstrap files: AGENTS.md, SOUL.md, USER.md, TOOLS.md, MEMORY.md, and friends. This guide explains what each file should own, what should never go there, and how to keep the set public-safe when pieces get copied into guides or templates.
The examples assume OpenClaw as the orchestrator, but the split works for Claude Code, Codex, Hermes, and other file-backed agent harnesses. If you want this layout installed rather than copied by hand, use Brigade.
Why this way
There are three common shapes:
| Shape | Feels easy | Breaks because |
|---|---|---|
One massive AGENTS.md | Everything is in one place | Every small edit invalidates the prompt cache and makes rules harder to audit |
| Tool notes mixed into memory | Fast during a crisis | Durable memory fills with stale commands, ports, and temporary paths |
| Separate files by responsibility | More ceremony | Each file has a stable job, so updates are smaller and easier to review |
The stable split is:
- identity and voice in one place
- operating rules in one place
- local commands in one place
- memory index as pointers, not prose
- cross-harness rules duplicated only where the harness actually reads them
Prerequisites
- An agent workspace loaded into the prompt at session start
- A way to search or inspect the workspace files
- A content scrubber or pre-push scanner before publishing examples
- Comfort treating local bootstrap files as sensitive operational material
Before / After
Before: every correction becomes another paragraph in AGENTS.md. Tool commands, host notes, user preferences, and memory cards drift into each other. The agent gets slower, rules conflict, and public docs risk leaking private paths.
After: every bootstrap file has one owner role. Prompt-cache churn is lower, durable memory is easier to search, and public templates can be sanitized without stripping the system’s actual architecture.
Implementation
1. Keep the core file contract explicit
Use this ownership map:
| File | Owns | Does not own |
|---|---|---|
AGENTS.md | Work rules, safety rules, delegation rules, recurring workflow requirements | Long personal biography, changing tool commands, large memory entries |
CLAUDE.md | Claude Code-specific global or repo rules | OpenClaw-only config, duplicate memory cards |
SOUL.md | Voice, pacing, personality, interaction style | Commands, secrets, project state |
USER.md | Stable user preferences and durable user context | Temporary task plans, private credentials |
TOOLS.md | Commands, ports, scripts, service notes, runbooks | Personal preferences, long architecture essays |
MEMORY.md | Short index pointing to memory cards and daily notes | Full memory content, transcript dumps |
IDENTITY.md | Short agent identity and role | Long behavioral policy |
HEARTBEAT.md (optional) | Heartbeat cadence and recurring status expectations, only if your harness sends heartbeat polls | General cron inventory |
SAFETY_RULES.md | Hard boundaries that should stay visible | Soft style preferences |
DREAMS.md | Generated reflections or dreaming output | Canonical facts without review |
INSTALL_FOR_AGENTS.md | How another agent should enter the workspace | Project documentation for humans |
If a note fits two files, prefer the one that changes least often. For example, a one-time port fix belongs in TOOLS.md, and a standing rule to verify ports before publishing belongs in AGENTS.md.
HEARTBEAT.md is the one file in this table I no longer keep. My install has neither HEARTBEAT.md nor BOOTSTRAP.md, and bootstrap-doctor status reports both as OPTIONAL rather than missing. The heartbeat interval is 0m (disabled) in my config, and periodic checks run as scheduled jobs instead (see cron patterns), where the timing is exact and the history is isolated. If your harness still polls a heartbeat and you want a one-line acknowledgment contract, keep the file. If not, don’t create it for symmetry.
2. Split global and repo-level rules
Global files should hold rules that apply everywhere:
~/.codex/AGENTS.md
~/.claude/CLAUDE.md
Repo-level files should hold project-specific rules:
<repo>/AGENTS.md
<repo>/CLAUDE.md
<repo>/.claude/CLAUDE.md
If both AGENTS.md and CLAUDE.md exist in a repo, use AGENTS.md as the shared source of truth and let CLAUDE.md carry only Claude-specific differences.
3. Keep MEMORY.md as an index
MEMORY.md is loaded early and often, so treat it like a table of contents:
## Card Categories
- `memory/cards/security.md` - hardening decisions and recurring incident patterns
- `memory/cards/publishing.md` - publish pipeline constraints and scrub rules
- `memory/cards/agent-routing.md` - model routing and escalation notes
Full explanations belong in cards, daily notes, or handoff files. Raw transcripts belong in the transcript store, not the memory index.
4. Route durable updates through handoffs
When a side harness learns something durable, it should create a handoff instead of editing canonical memory directly.
Good handoff targets:
TOOLS.mdfor commands, service paths, ports, and setup notesUSER.mdfor stable preferencesrules/*.mdfor recurring workflow policy.learnings/ERRORS.mdfor concrete failure records- memory cards for architecture, root causes, and reusable concepts
This lets the canonical memory owner review and route the update.
5. Keep each file under a size budget
Every bootstrap file is injected into every session, so its size is a per-turn cost. bootstrap-doctor is the tool I use to watch that:
bootstrap-doctor status # read-only size report against soft and hard limits
bootstrap-doctor runtime # what OpenClaw actually injected into the last compiled prompt
bootstrap-doctor lint # lifecycle and context drift report
bootstrap-doctor audit # heuristic shortlist plus an LLM judge of what could move out (no mutations)
The defaults are 17,000 characters soft and 40,000 hard per file, 120,000 total. My current numbers: AGENTS.md 21.8K characters (over the soft limit, flagged SOFT), TOOLS.md 11.5K, USER.md 11.3K, SOUL.md 3.1K, IDENTITY.md 0.3K, MEMORY.md 4.3K, 52K of 120K total. A file over the soft limit still loads in full. The warning is a prompt to move a section into a card or a rule file before the hard limit starts truncating it.
AGENTS.md grows fastest, because every correction wants to become a standing rule. Treat that as the signal to split by job: workflow policy that only matters during one kind of task belongs in rules/*.md or a skill, loaded when relevant, not in the always-on file.
6. Scrub before publishing
Bootstrap files often contain private hostnames, local paths, channel IDs, phone numbers, and personal context. Never copy them directly into public docs.
Use deterministic replacements:
| Sensitive shape | Public placeholder |
|---|---|
| private hostnames | the host, your-host, agent-host |
| private IPs | 192.0.2.10 or [redacted-ip] |
| phone numbers or account IDs | [redacted-identity] |
| local home paths | ~/.openclaw/workspace/... or [redacted-path] |
| secret env vars | EXAMPLE_API_KEY |
Verification
Check the file set:
for f in AGENTS.md CLAUDE.md SOUL.md USER.md TOOLS.md MEMORY.md \
IDENTITY.md SAFETY_RULES.md DREAMS.md INSTALL_FOR_AGENTS.md; do
test -f "$HOME/.openclaw/workspace/$f" && echo "ok $f" || echo "missing $f"
done
# HEARTBEAT.md and BOOTSTRAP.md are optional. Absence is fine.
Check sizes and what is actually injected:
bootstrap-doctor status
# Expected: a row per tracked file, a "summary:" line with 0 over hard and 0 missing;
# HEARTBEAT.md and BOOTSTRAP.md show OPTIONAL when absent
bootstrap-doctor runtime
# Expected: after at least one full agent turn, each file shows disk and injected sizes;
# before that, "evidence not_observed" and UNKNOWN per file (cron and heartbeat runs don't count)
Check that memory is not turning into a giant prompt payload:
wc -c ~/.openclaw/workspace/MEMORY.md
find ~/.openclaw/workspace/memory/cards -type f -name '*.md' | wc -l
Run a public-safe scan before publishing copied examples:
rg -n '([0-9]{1,3}\.){3}[0-9]{1,3}|localhost:[0-9]+|@[A-Za-z0-9._-]+|token|secret|password' .
Gotchas
CLAUDE.md is not automatically equivalent to AGENTS.md. Different harnesses read different files. If a rule must apply everywhere, put it in the file that harness actually loads or use a short repo-level bridge file.
Editing bootstrap files mid-session can blow the prompt cache. If the file is part of the cached prefix, a tiny change can invalidate the whole prefix. Batch edits at session boundaries when possible.
MEMORY.md bloat is quiet until it hurts. A memory index that grows into a narrative file makes every session heavier. Promote content into cards and keep the index as pointers. Mine stood at about 4.2KB in October 2026 after an automatic promotion job once pushed it past 40KB.
A file on disk is not proof it was injected. Truncation happens at injection time. bootstrap-doctor status reads the files on disk, and bootstrap-doctor runtime compares them with the compiled prompt. When a rule seems ignored, run runtime before rewriting the rule.
An always-on file past its soft limit is a refactor ticket. AGENTS.md is the usual offender. Move task-specific policy into rules/*.md or a skill rather than raising the limit.
Personality files are not safety boundaries. SOUL.md can make an agent more pleasant and consistent, but hard policy belongs in AGENTS.md, SAFETY_RULES.md, hooks, or tool permissions.
Public templates need placeholders, not your real setup. The shape is useful. The exact hostnames, channels, account IDs, and paths are not.
Templates
../templates/bootstrap/- sanitized skeletons for the core workspace files.HEARTBEAT.mdis marked optional there.
Related
memory-architecture.md- where bootstrap files sit in the trust hierarchymemory-token-optimization.md- keeping loaded memory smallclaude-code-memory-handoffs.md- routing durable knowledge back to the canonical memory owner../ai-stack/prompt-caching.md- why mid-session edits to these files cost you the cache