Standing agents (Naga, Veda, Reed, etc.) run through the HAAK agent runner, not Claude Code's session management. The runner owns the message array and manages context. Subagents continue to use Claude Code.
#Turn loop
- Load agent manifest: name, role, mandate (from roster + mandate file)
- Load history file:
data/runner-history/<agent-name>.jsonl(raw turns for now, distilled later) - Check mailbox: any messages addressed to this agent inject as user turns
- Wait for input (or use injected mailbox turn if no user present)
- Construct messages array:
- System message: mandate text
- History turns (from history file)
- Injected mailbox messages (as user-role turns, prefixed
[mailbox from X]) - Current user message
- Call Anthropic API (model from roster entry, default claude-opus-4-5)
- Receive response, print to terminal
- Append turn pair to history file
- Handle tool calls (see Tools)
- Repeat
#Tools available to standing agents
Two tools exposed via API tool_use:
spawn_subagent(prompt: str) -> str— runsclaude --print --output-format text -p "<prompt>"as subprocess, returns stdout. Blocks until complete.sendmessage(to: str, subject: str, body: str)— callspython3 scripts/agentmailbox.py send <to> <subject> <body>, returns confirmation.
#History format
data/runner-history/<agent-name>.jsonl — one JSON object per line:
{"role": "user", "content": "...", "ts": "..."}{"role": "assistant", "content": "...", "ts": "..."}
No distillation yet — raw history. Distillation (Method 49) plugs in here later.
#Mailbox injection
On each turn, check python3 scripts/agent_mailbox.py read --session-id <agent-name> --unread-only. For each unread message, mark as read and inject as a user turn: [mailbox from <sender>] <subject>\n<body>. Inject before the human's turn.
#CLI
python3 scripts/agent_runner.py <agent-name>
Loads agent from roster, runs interactive loop. Ctrl-C to exit cleanly (appends nothing on interrupt).
Architecture 35 — Agent Runner — 2026 — Zachary F. Mainen / HAAK