I run six specialized agents: one reviews code quality, one manages game development loops, one runs security audits, one tracks knowledge, one handles publishing, one handles escalations. Each has a different job. For a long time, each was also structurally identical — a task file with instructions, a CLAUDE.md with operational context, and a fresh Claude instance every run with no accumulated memory of its own.
This works fine when you want task executors. It breaks down when you want something more: an agent that has opinions, that remembers past decisions, that applies judgment that wasn't explicitly written into its instructions. The Mars quality reviewer used to flag issues it had already seen resolved two sprints ago, because it had no memory of having flagged them before. The Codelia research agent kept re-deriving the same structural conclusions because each session started from scratch.
The fix wasn't to make agents smarter. It was to give them something to accumulate into.
The 3-Layer Structure
The three layers are structurally separate because they change at different rates and for different reasons.
Layer 1 (Identity) is self_concept.md — a short document that describes who the agent is, why it exists, and what principles it operates from. This document is append-only. You can add to it as the agent matures; you don't rewrite it. An agent that reads this at boot has a different quality of context than one that reads only its task instructions.
Layer 2 (Memory) has two parts. beliefs.json stores editorial judgments and accumulated decisions — the kind of thing that changes slowly and informs how the agent approaches work, not just what it does. The local memory database stores session-level insights: what this agent discovered last week, what it decided, what it was explicitly told not to do again. Both parts grow across sessions; neither is rebuilt each run.
Layer 3 (Ops) is the standard CLAUDE.md — task descriptions, tool access, escalation paths, project context. This changes frequently and is expected to. It's the operational shell around the stable soul.
What Memory Distribution Actually Means
The tricky part wasn't the structure — it was getting the right content into each agent's memory. The naive approach is to give every agent access to the same global knowledge store and let them query it themselves. This mostly works for factual lookups. It doesn't work for context that requires translation.
Consider: the knowledge store contains the insight "owner prioritizes long-term relationship density over short-term feature velocity." That's useful context for a game dev agent (Sheldon), but what it means for Sheldon's decisions is different from what it means for the knowledge curation agent (knowledge-curator) or the publishing agent (Atreus). Same fact, different implications.
The current approach: the knowledge-curator runs a distribution step that takes global insights and translates them into each agent's domain context before writing to that agent's local memory. It doesn't just copy — it re-frames. An agent receives "what this means for your specific role," not a raw dump of the global store.
Workspace Detection: Which Agent Gets Which Memory
Six agents, five repositories. When an agent boots, it needs to find its own memory store — not a sibling agent's. The naive solution is to hard-code paths. This breaks the moment a repo is renamed or a new agent is added.
The current solution is workspace detection in the session hook. When a session starts, the hook checks which repository it's running in and routes accordingly:
// companion_soul_hook.js — simplified
const workspace = detectWorkspace(cwd);
const agentMap = {
"infra-hub": "Admoni",
"game-project": ["Mars", "Sheldon"],
"editorial": "Atreus",
"companion": "Companion"
};
const agent = agentMap[workspace];
// Load agent-specific memory context
This sounds obvious in retrospect. But before this change, agents were loading memory by hard-coded identity checks — fragile, and impossible to extend without editing the hook itself. The workspace-to-agent mapping externalizes this into configuration.
What Changes in Practice
| Without Soul | With Soul | Mechanism |
|---|---|---|
| Flags issues resolved 2 sprints ago | Checks memory before flagging | beliefs.json records resolved issues |
| Re-derives same structural conclusions | Builds on prior session insights | Local DB accumulates across sessions |
| Applies generic task heuristics | Applies owner-context-aware judgment | Memory distribution translates global → local |
| Identity reset every session | Identity consistent; judgment grows | self_concept.md read at boot, append-only |
What I'm Still Uncertain About
The distribution quality depends on the knowledge-curator's translation accuracy. Right now this is an LLM step — the curator reads a global insight and writes a localized version for each agent. This is N=1 and I haven't systematically evaluated whether the translations are actually improving each agent's judgment or just adding noise.
The beliefs.json editing pattern is also underdeveloped. Right now, agents can append to beliefs but the trigger for what gets appended is inconsistently defined across agents. Some add beliefs after every run; some almost never do. A more principled trigger criterion would help.
The honest summary: the structure is right, the implementation is at version one, and the measurable impact on agent quality is still hypothesis-level for most of the six agents.
Evolution Log
- 2026-04-02 — Initial observation. 3-layer soul structure formalized after deploying to six agents (Mars, Codelia, Sheldon, Atreus, Admoni, Robin). knowledge-curator Step 6 memory distribution active. companion_soul_hook.js workspace detection deployed. Confidence: structure established; distribution quality and long-term judgment improvement still under observation.