We’d been building Tony’s runtime for months. Every feature, every fix, every config tweak was wired directly into the codebase. Tony’s name was in the logs. His personality was in the prompt assembly. His skills were hardcoded into the command router. The runtime and the agent were the same thing.
Then we needed to run a second agent.
The Problem
When your agent’s identity lives inside the runtime code, scaling from one agent to two means forking the entire codebase. You end up maintaining two copies of the same transport layer, the same memory system, the same health checks — all because the personality is tangled into the infrastructure.
The symptoms show up everywhere:
- Agent name appears in log strings, error messages, and startup banners
- Personality text is embedded in prompt construction functions
- Skills are imported directly by the command router
- Config files reference agent-specific paths and behaviors
- Adding a second agent means duplicating the whole project
This isn’t a scaling problem you hit at agent number fifty. You hit it at agent number two.
Why This Happens
Most agent projects start as a single-agent prototype. You wire the personality into the prompt, hardcode the skills, and ship it. That’s fine for getting something running. But the architecture implicitly assumes there will only ever be one agent, and that assumption calcifies into every file.
The runtime becomes the agent. The agent becomes the runtime. Separating them later means surgery on a codebase that was never designed to be split.
The Fix
Draw a clean line between machine and mind.
The harness owns infrastructure:
// index.js — the harness never mentions an agent by name
const profile = await AgentLoader.load(config.agent.profileDir);
const transport = loadTransport(config.transport);
const claude = new ClaudeService(config.anthropic);
transport.onMessage(async (userId, text, reply) => {
const history = memory.getHistory(userId);
const response = await claude.chat(profile.systemPrompt, history, text);
await reply(response);
});
The agent owns identity:
agents/your-agent/workspace/
SOUL.md — personality, voice, operating rules
IDENTITY.md — name and role (parsed at startup)
MEMORY.md — institutional knowledge
PRIORITY.md — how to triage competing tasks
TASKS.md — current active work
skills/ — agent-specific commands
The connection contract is one config line:
{
"agent": {
"profileDir": "/path/to/agents/your-agent/workspace"
}
}
Change the path. Restart. Different agent, same machine.
What the agent loader does at startup:
- Read
SOUL.md— becomes the core system prompt - Parse
IDENTITY.md— extract name and role - Read optional files (
MEMORY.md,PRIORITY.md,TASKS.md,CALENDAR.md) — append to system prompt in sections - Scan
skills/directory — discover commands from subdirectory names andSKILL.mdfrontmatter
The assembled system prompt follows a strict order: personality first, identity metadata, then context layers. Every interaction gets full context without the harness knowing anything about who it’s serving.
Key Takeaway
The harness is the machine. The agent is the mind. Keep them separate and you get portability for free — same code runs any agent, any agent runs on any machine. The moment you hardcode personality into infrastructure, you’ve built a prison that fits exactly one occupant.