DuoDuo is a filesystem-first, event-sourced host runtime. The split is simple: the agent does the thinking. The daemon keeps events safe, routes work to the right session, wakes things on time, and gets results delivered.
Invariant 1: WAL before execute#
The Spine is the event bus every channel, the cadence, and every session feed: everything that arrives or happens becomes a canonical event on one durable, replayable log. It has two faces. The Spine WAL—an append-only JSONL log—is the source of truth for what happened; audit, the daemon's own bookkeeping, and the subconscious's reading all live there. A best-effort live feed accelerates running components but never carries correctness.
A long-lived system will be stopped mid-turn—by a crash, an upgrade, a closed laptop lid. Writing the event down before acting on it means the record of what was asked exists whether or not the answer ever did.
An incoming channel message follows one ordering: spine → mailbox → drain.
- Normalize the incoming message into a canonical event.
- Persist the raw ingress payload and append the event to the Spine WAL.
- Route the event into the owning session's mailbox.
- The session runner drains the mailbox and executes the turn.
- Outputs are recorded and delivered through the channel outbox.

The event exists on disk before the model acts. Be precise about what that buys. The WAL is the cold record of what crossed the machine's boundaries: it is what audit, duoduo spine, and the subconscious read, and what the daemon replays to rebuild its routing and scheduling state after a restart. The live conversation each runtime resumes is hot session state, kept in that runtime's own session files. The WAL does not replace it—a session continues from its own state, and the WAL tells you what happened to it.
Read the log#
A busy day of history is tens of megabytes of JSONL, and most of its lines are tool plumbing. duoduo spine is the read surface built for that:
duoduo spine cat --date 2026-09-04 --session <key>
duoduo spine cat --date 2026-09-04 --count-only
duoduo spine show <event-id>
cat prints what people and agents actually said in full, and folds each tool call together with its result into one line you can drill into. The header and the closing END spine footer make a complete read distinguishable from a truncated one, and --after <event-id> resumes strictly after a given event so a long scan continues instead of starting over. show prints one event whole, nothing elided.
Two behaviors are worth knowing before you script against it. --count-only sizes a window without printing a body, which is how you check before pulling a day into an agent's context. And a cat with no narrowing filter refuses to print a body unless you pass --unfiltered—context-window protection, not a permission check.
Invariant 2: durable state lives in files#
The project files the agent is reading and changing. The session stays attached to this workspace.
duoduo spine, and the subconscious read, and what the daemon rebuilds its routing from after a restart. Each runtime keeps the conversation it resumes in its own session files beside it. The kernel's Git history records every change to memory and configuration. Locks, PIDs, and heartbeat bookkeeping live separately as ephemeral files—safe to clear, never needed to recover. Run duoduo daemon config to see the paths used on this machine.Different kinds of state change at different speeds, and mixing them is how systems become unrecoverable. So the installed system separates files by how they may change:
- Kernel (
kernel_dir) is a Git repository: channel-kind defaults, the memory graph and intuition layer, subconscious partition prompts, and the playlist. Its commit history records every change to them. - Durable runtime state (
runtime_dir/var) holds ingress records, the Spine WAL, mailboxes, sessions, jobs, the outbox, and usage ledgers. This is what the daemon rehydrates from. - Ephemeral state—locks, PIDs, heartbeat bookkeeping, queue offsets—is kept separately and can be cleared at any time; rehydration never depends on it.
- Workspace is the project the agent is actually reading and changing. It belongs to you, not to the runtime.
In-memory indexes accelerate routing, but they are derived views. On restart, the daemon rehydrates from the Spine WAL and durable state, then resumes scheduling. Run duoduo daemon config to see the resolved paths on a machine.
Invariant 3: one actor owns each stream#
A foreground conversation, scheduled job, and subconscious partition each have their own session identity and lifecycle. Lease locks prevent two runners from owning the same active stream at once. Per-session mailboxes preserve ordered work without merging unrelated conversations.
Externally this still feels like one agent. Internally, explicit actors keep concurrency understandable and recoverable.
Cadence and the subconscious#
Cadence emits a heartbeat independently of channel traffic. On each tick, the meta-session reads the playlist, selects the next enabled partition that is not cooling down or backed off, and runs that partition with its own prompt and time budget.
For a completed run, the runtime records and settles success, timeout, invalid output, or error before advancing the playlist and partition state.
Partition configuration lives in Markdown frontmatter. The prompt body defines what the partition does; the schedule fields control whether and when it becomes eligible. Because the prompt is read from disk for each tick, a reviewed local prompt edit can affect the next eligible run without replacing the daemon.
Memory returns to the foreground#
The memory graph has several layers:
- evidence fragments distilled from external event history
- entity dossiers and topic dossiers
- verified correction paths called lessons
- repeated procedures distilled as grooves
- a compact broadcast intuition layer loaded into future foreground sessions
Each layer has exactly one writer, so two partitions never contend over the same file. The committer's only operations are git add and git commit: it records what changed and does not judge it, because a quality gate there would guard the Git history while every foreground session is already reading the working tree. Foreground sessions load the compact broadcast layer, not the entire transcript archive.
Runtime selection#
Claude, Codex, Grok, and Pi are peer execution backends where installed. A channel, job, or subconscious partition can select one. /model and /effort adjust the current foreground session without changing the entire host. Every runtime takes images as ordinary turn input.
They share one DuoDuo interface rather than sitting beside each other. A session on any of them sees the same DuoDuo tools, the same kind/instance/job/partition prompt layering—including whether the prompt is appended or replaces the runtime's own—and the same mid-turn steering. Token and cost accounting is normalized, so the numbers stay comparable.
Availability is checked against what is actually present, and it fails closed: asking for a runtime that is not usable gives you a clear error, never a silent fall back to another one.
The same holds for a runtime name DuoDuo does not know. A typo such as runtime: codx is refused with a sentence that names it and lists the valid ones, never replaced by the next layer down: an unknown ALADUO_DEFAULT_RUNTIME stops the daemon from booting, an unknown value in a channel descriptor refuses that channel's turns, a job with one fails, and a partition with one is skipped. And a session stays bound to the runtime that created its history—moving it to another runtime needs /clear first, because one harness's history is meaningless to another.
Available is not the same as ready. Codex and Grok are found by their installed CLI. Pi is embedded, so it is always available—but a Pi session needs a model named for it, and until one is, it refuses each message with the fix in the reply rather than quietly answering as Claude.
Peer does not mean identical. What each one brings that the others cannot:
| Runtime | How DuoDuo runs it | What only it has |
|---|---|---|
| Claude | Embeds the Claude Agent SDK at a pinned version | Model profiles: per-model context windows, endpoints, and tier aliases |
| Codex | The Codex CLI you installed | Its own loop and tool-calling convention |
| Grok | The Grok CLI you installed | Native X search from Grok Build |
| Pi | Embeds the Pi coding agent at a pinned version | Your own Pi: extensions, skills, prompt templates, AGENTS.md |
DuoDuo strips the parts of a runtime that would duplicate what it already owns—a runtime's own scheduler and background-task tools give way to DuoDuo's jobs, so there is one place where scheduled work lives. Memory follows the same rule: Claude Code's own auto memory is off in every Claude session DuoDuo starts, because it would keep a second memory store beside DuoDuo's.
See Mental Model for why these are separate runtimes rather than one harness with four logins.
Recovery and inspection#
Use duoduo daemon status for health, cadence, subconscious progress, and the tool calls running right now. Use duoduo daemon config to see effective paths and runtime settings. Use duoduo spine to read the history itself. DuoDuo Manager reads the same daemon APIs for sessions, jobs, partitions, usage, and the event stream.
After upgrading the package, restart the daemon so the detached process loads the new code — duoduo upgrade does both. Kernel prompts are preserved separately because they may have changed on this host.
The daemon's control interface is a unix socket with owner-only permissions; the loopback TCP port serves the dashboard read-only. See Host Operations.