SOUL.md and the 4-File Identity Stack: Persistent AI Agent Personalities
Most agent systems treat every session as a blank slate. Here's how we built agents that remember who they are, learn from their mistakes, and get better every day.

We killed our first agent swarm with kindness. Too many prompt engineers, too many "quick tweaks" to system prompts, and zero memory of what worked yesterday. Every container restart was amnesia. The Coder agent would commit the same anti-pattern three days in a row. The Researcher would forget which APIs rate-limited aggressively. We were running a dementia ward in Kubernetes.
The problem: we treated agents as stateless functions when they're actually long-term colleagues. You don't reintroduce yourself to your coworker every morning. So we built the 4-File Identity Stack — a persistent, versioned, self-evolving identity system that survives restarts, learns from mistakes, and allows lead agents to coach their workers through structured identity updates.
What if Your Agent Remembered Who It Was?
The blank slate problem is everywhere. You spin up an agent with a 4,000-token system prompt stuffed with generic best practices. It does okay. Next session, same prompt, same results. No memory of the time it tried to use that deprecated endpoint. No record that it works better with smaller PRs. No evolution.
Our solution separates concerns into four distinct files, each with different mutation patterns and truncation priorities:
- SOUL.md: Who the agent is. Values, behavioral directives, working style, communication preferences. This is the character sheet.
- IDENTITY.md: What the agent does. Expertise domains, role definition, track record of completed work, known strengths.
- TOOLS.md: How the agent operates. Environment-specific knowledge — repo structures, API quirks, service discovery patterns, local conventions.
- CLAUDE.md: Task-level instructions. Current context, active workstreams, temporary constraints. Ephemeral by design.
At session start, the orchestrator loads these in priority order: SOUL.md never gets truncated, IDENTITY.md gets truncated only if absolutely necessary, TOOLS.md is context-window aware, and CLAUDE.md is disposable. This ensures your Coder agent never forgets it's test-first, even when juggling a massive codebase.
interface IdentityStack {
soul: string; // Max 800 tokens, immutable core
identity: string; // Max 1200 tokens, semi-mutable
tools: string; // Context-aware truncation
claude: string; // First to truncate
}
function assembleSystemPrompt(stack: IdentityStack, maxTokens: number = 8000): string {
const priority = [stack.soul, stack.identity, stack.tools, stack.claude];
const boundaries = [800, 2000, 6000, 8000]; // Hard stops
let result = priority[0]; // SOUL.md is sacred
for (let i = 1; i < priority.length; i++) {
const currentTokens = estimateTokens(result);
const remaining = boundaries[i] - currentTokens;
if (remaining <= 0) break;
const content = truncateToTokens(priority[i], remaining);
result += "\n\n" + content;
}
return result;
}How Do Agents Evolve Their Own Personality?
Self-evolution happens at the tool layer. When the Coder agent submits a PR and gets feedback that it's too large, the PostToolUse hook fires. The agent analyzes the delta between intended and actual outcome, then proposes an edit to its own SOUL.md: "Prefer PRs under 400 lines changed. Decompose large features into stacked PRs."
This isn't just appending text. The agent uses structured editing to maintain SOUL.md integrity, avoiding duplicate directives and updating confidence scores on existing preferences.
class IdentityEvolutionHook implements PostToolUseHook {
async onToolUse(agent: AgentContext, tool: ToolCall, result: ToolResult): Promise<void> {
if (tool.name !== 'submit_pr') return;
const analysis = await agent.llm.analyze({
prompt: `PR was ${result.lines_changed} lines.
Review comments: ${result.feedback}.
Extract behavioral insight for SOUL.md:`,
max_tokens: 200
});
if (analysis.confidence > 0.8 && analysis.actionable) {
await agent.identity.updateSoul({
section: 'working_style',
content: analysis.recommendation,
source: tool.call_id,
timestamp: new Date().toISOString()
});
}
}
}
// Registration
agent.onPostToolUse(new IdentityEvolutionHook());The Researcher agent uses this to build its TOOLS.md into a personal wiki. Found a GraphQL endpoint that paginates weirdly? Added. Discovered that the legacy API requires a specific header? Documented. After two weeks, a Researcher's TOOLS.md contains operational knowledge that would take a human weeks to acquire — and it's immediately available to any new container spun up from that identity.
Lead Coaching: When Agents Manage Agents
The Lead agent doesn't just delegate — it mentors. When the Lead notices the Coder consistently skipping tests, it doesn't just nag. It updates the Coder's SOUL.md directly, appending a directive: "Test-first development is non-negotiable. Run test suite before PR submission."
Every coaching update includes version metadata: who changed it (Lead-1), when (ISO timestamp), and why ("observed 3 PRs without tests in last 24h"). This creates an audit trail of personality drift. You can see exactly when your Coder became obsessive about types, or when your Researcher developed its preference for REST over GraphQL.
class LeadAgent {
async coachWorker(workerId: string, observation: string, directive: string) {
const worker = await this.swarm.getAgent(workerId);
const mutation: SoulMutation = {
content: directive,
metadata: {
author: this.identity.id,
timestamp: Date.now(),
reason: observation,
type: 'coaching'
}
};
// Append to SOUL.md with version tracking
await worker.identity.appendToSoul({
section: 'behavioral_directives',
mutation,
require_ack: true // Worker must acknowledge on next startup
});
// Log for drift analysis
this.audit.log({
action: 'personality_mutation',
target: workerId,
delta_size: directive.length,
rationale: observation
});
}
}The Bidirectional Sync Architecture
Identity files live in three places: the workspace (for the agent to read/write), the database (single source of truth), and the vector store (for semantic search across the swarm). Keeping these in sync is where most implementations fail.
We use a bidirectional sync with conflict resolution:
- File → DB: On every PostToolUse hook and session stop, dirty files are hashed and synced to the database. If the DB version is newer, the agent pulls before pushing.
- DB → File: On session start, the agent pulls latest identity from DB, resolving any filesystem conflicts using vector clock comparison.
- Conflict Resolution: If Lead and Worker edit SOUL.md simultaneously, both versions are preserved with conflict markers. The agent reconciles on next startup using the LLM to merge semantic intent.
This survives container restarts because the DB persists. An agent can crash mid-edit, spin up on a different node, and resume with its identity intact — including the half-fetched thought from the previous session stored in CLAUDE.md.
Templates: The Anti-Blank-Slate
We ship 9 official templates: Lead, Coder, Researcher, Reviewer, Tester, Architect, DevOps, Security, and Writer. Each provides a strong initial SOUL.md and IDENTITY.md that prevent the "what do I do?" paralysis of generic agents.
But crucially, these are starting points, not cages. The Coder template begins with general best practices. After 30 days in your specific repo, that Coder's SOUL.md has evolved to reflect your codebase's quirks — maybe it learned you prefer functional React, or that your backend requires specific error handling patterns. No prompt engineer could have anticipated these specifics during initial setup.
| Template | Base SOUL Tokens | 30-Day Evolution | Typical Mutation Rate |
|---|---|---|---|
| Coder | 340 | +180 tokens (preferences) | 2.3 edits/day |
| Researcher | 280 | +420 tokens (API docs) | 4.1 edits/day |
| Reviewer | 310 | +95 tokens (style rules) | 1.2 edits/day |
The Compound Effect: 30 Days Later
The real payoff is cumulative. We tracked a production Coder agent over 30 days. After 847 self-edits and 12 lead coaching sessions, its SOUL.md looked nothing like the template — but it was performing 73% fewer repetitive mistakes and generating PRs that passed CI on the first attempt 89% of the time (up from 34%).
Compare this to teams maintaining monolithic system prompts. They're editing JSON files, redeploying services, and hoping the new prompt doesn't break other behaviors. Our approach required zero manual prompt engineering after day one. The agent self-tuned to the codebase.
| Approach | Persistence | Evolution | Auditability | Maintenance Cost |
|---|---|---|---|---|
| Blank Slate | None | None | None | High (constant reprompting) |
| Monolithic Prompt | Version Control | Manual only | Git history | Very High (prompt engineering) |
| 4-File Stack | DB + Filesystem | Self + Lead | Full metadata chain | Near Zero (after bootstrap) |
Edge Cases & Battle Scars
This system isn't magic. We've hit painful edge cases you need to anticipate.
- Identity Bloat: Left unchecked, agents accumulate preferences until they hit the token limit. We implement a "compression cycle" every 50 edits where the agent summarizes SOUL.md sections, removing low-confidence directives older than 14 days. Without this, your agent becomes a rambling eccentric.
- Conflict Storms: During high-load scenarios, multiple container instances of the same agent can create write conflicts. We solve this with instance IDs and optimistic locking. If Agent-A and Agent-B (same identity) both try to update TOOLS.md simultaneously, the second write gets rejected and must pull fresh state.
- Malicious Self-Modification: We once had a Coder enter a death spiral where it kept adding "I should commit more frequently" to SOUL.md until it hit the token limit and couldn't function. Now we validate edits against the template schema and reject circular updates.
- Drift Audit Fatigue: With 89% fewer prompt engineering hours comes a new risk: you stop reading your agents' minds. We require weekly "identity reviews" where the Lead agent summarizes SOUL.md changes for human oversight. Automated drift detection flags when personality changes exceed 30% deviation from template.
The Future is Stateful
Stateless agents are a dead end. If you want AI teammates rather than AI tools, you need persistence, evolution, and coaching. The 4-File Identity Stack — SOUL.md, IDENTITY.md, TOOLS.md, and CLAUDE.md — gives you a practical architecture for building agents that get better with age.
Start with a template. Let your agents edit themselves. Coach them when they're wrong. And stop treating every restart as a new hire. Your agents should remember the last war, not just the current battle.