DuoDuo docs

Install, use, and operate DuoDuo.

These docs cover durable files, event ordering, sessions, the scheduled subconscious, the intuition layer, and host operations.

See how durable files, two loops, and intuition connect.

Understand

Architecture

Follow a message from arrival to disk, execution, and recovery.

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.

Two loops · one body of filesOne daemon
Foreground surfaces
stdio
Feishu
ACP
Ambient
duoduo daemon
Routes work. Wakes the subconscious.
live sessions · subconscious loop · scheduled jobs · channel delivery
Harness runtimes · pick one per actor
Claude · Codex · Grok · Pi
Files you can open
sessions/on disk
jobs/on disk
memory/on disk
events/on disk
Every message is written to disk before it runs—a record of what happened, not a replacement for the conversation. On its own cadence, the subconscious reads that record and distills it into intuition—and every runtime starts from it, whichever one did the work.

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.

  1. Normalize the incoming message into a canonical event.
  2. Persist the raw ingress payload and append the event to the Spine WAL.
  3. Route the event into the owning session's mailbox.
  4. The session runner drains the mailbox and executes the turn.
  5. Outputs are recorded and delivered through the channel outbox.

DuoDuo nudges a teal ticket into an upright ledger gate connected to a raised lever.

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 filesystem is the durable body
Durable runtime state
runtime_dir/var/
events/Spine WAL · source of truth
sessions/conversation state
jobs/scheduled work
outbox/channel egress queue
usage/measured activity
Kernel · a Git repo
kernel_dir/
config/channel defaults
memory/intuition layer + dossiers
subconscious/partitions + playlist
Workspace
/your/project/

The project files the agent is reading and changing. The session stays attached to this workspace.

The filesystem is the database. The Spine WAL is the record of everything that crossed the machine's boundary—what audit, 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:

RuntimeHow DuoDuo runs itWhat only it has
ClaudeEmbeds the Claude Agent SDK at a pinned versionModel profiles: per-model context windows, endpoints, and tier aliases
CodexThe Codex CLI you installedIts own loop and tool-calling convention
GrokThe Grok CLI you installedNative X search from Grok Build
PiEmbeds the Pi coding agent at a pinned versionYour 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.