dagain
v0.1.4
Published
DAG-based orchestration CLI for coding agents (Codex, Claude Code, Gemini).
Maintainers
Readme
title: "dagain"
status: active
date: "2026-02-04"
parents: []
tags: ["dagain", "docs"]
input: "GitHub/npm readers and CLI users"
output: "User-facing docs for dagain"
position: "Repo/package README and primary usage reference"
dagain
DAG-based orchestration for coding agents (Codex, Claude Code, Gemini).
Dagain runs a work graph (nodes + deps) stored in SQLite, and executes each node with a configured runner. It’s built to keep agents “fresh”: context is loaded from the graph/DB when needed, not carried indefinitely in prompts.
Docs
- https://knot0.com/writing/dagain
Install
Once published to npm:
npx dagain --helpOr install globally:
npm i -g dagain
dagain --helpQuickstart (in a repo you want to work on)
# 1) init state + config (creates .dagain/ and a new session)
dagain init --goal "Add a CLI flag --foo and tests" --no-refine
# 2) run the supervisor (defaults to 3 workers; drops into chat on completion)
dagain run --live
# disable post-run chat:
dagain run --no-post-chat
# 3) in another terminal: check status / interact
dagain status
dagain chat # TUI by default on a real terminal
dagain chat --plain # force the plain readline REPL (useful for piping / non-TTY)
dagain control resume # enqueue resume (auto-starts supervisor if stopped; add --no-start to enqueue only)
# 4) optional: live dashboards
dagain tui # terminal dashboard + chat (shows a GUI URL)
dagain ui # web dashboard (chat + DAG + node logs, sessions drawer, pan/zoom+fit, controls)Tip: in a fresh repo you can also just run dagain (no args) or dagain ui — both will auto-initialize a session with a placeholder goal if no state exists yet.
Note: if you run dagain as root (e.g. via sudo) inside a repo, it will prefer executing runners as the repo owner to avoid root-owned outputs.
Sessions
dagainstores state per session under.dagain/sessions/<sessionId>/.dagain init --goal "..."creates/updates the current session goal (.dagain/GOAL.md). If the current session already has state and is “inactive” (all nodesdone), it auto-creates a new session unless you pass--reuse.- Use
--new-sessionto force a new session even if the current one still has unfinished work.
Common chat controls
Inside dagain chat (both TUI and --plain):
/status— print graph status/run— start supervisor/pause//resume— stop/resume launching new nodes (in-flight nodes finish)/workers <n>— set concurrency (default: 3)/replan— force plan node (plan-000) to reopen and block launches until it completes/cancel <nodeId>— cancel a running node (best-effort)/answer [nodeId] <answer...>— answer a checkpoint and reopen aneeds_humannode/artifacts [nodeId]— show run artifact paths (and last stdout/result for a node)/memory//forget— inspect/reset chat memory stored in SQLite KV
Concepts
Nodes and dependencies
Dagain is a DAG of nodes:
plannodes decompose goals into taskstasknodes do work (code, analysis, etc)verifynodes check task outputsintegratenodes merge/roll up resultsfinal_verifynodes do final checks
Dependencies live in the deps table. A dep can require:
done(default): upstream must bedoneterminal: upstream must be terminal (doneorfailed) — useful for “investigate failure” / escalation flows
External memory (SQLite)
All durable state is session-scoped under .dagain/sessions/<sessionId>/state.sqlite.
For backwards-compat and convenience, .dagain/state.sqlite is a symlink to the current session DB:
nodes/deps— the DAG and statuseskv_latest/kv_history— durable “memory” and artifactsmailbox— supervisor control queue (pause/resume/workers/replan/cancel)
Chat memory is stored in KV under node_id="__run__":
chat.rollup— rolling summary (router-maintained)chat.turns— last ~10 turnschat.last_ops— last emitted ops JSONchat.summary— last assistant reply
Safety model: “ops, not commands”
Dagain keeps the model from directly mutating state by having it emit ops. The host applies them safely.
In dagain chat, the router can emit:
control.*ops (pause/resume/workers/replan/cancel)ctx.*ops (read-only context requests likectx.readFile,ctx.rg,ctx.gitStatus) — Dagain executes these and re-invokes the router with resultsnode.add,node.update,node.setStatusdep.add,dep.removerun.start,run.stop,status
Runners (Codex / Claude / Gemini)
Runners are just shell commands that receive a {packet} filepath and should print:
<result>{...json...}</result>Configure them in .dagain/config.json:
{
"version": 1,
"runners": {
"codex": { "cmd": "codex exec --yolo --skip-git-repo-check -" },
"claude": { "cmd": "claude --dangerously-skip-permissions -p \"$(cat {packet})\"" },
"gemini": { "cmd": "gemini -y -p \"$(cat {packet})\"" }
},
"roles": {
"planner": "codex",
"executor": "codex",
"verifier": "codex",
"integrator": "codex",
"finalVerifier": "codex"
}
}Notes:
- Dagain strips Claude’s
--dangerously-skip-permissionswhen running as root. - For speed, you can set
defaults.verifyRunnertoshellVerifyso verification doesn’t use an LLM.
Parallelism and worktrees
dagain run --workers Nruns up toNnodes concurrently (subject to ownership locks). If you omit--workers, Dagain defaults to at least 3 workers.- For conflict-prone code edits, set
supervisor.worktrees.mode="always"to run executors in worktrees and merge serially. - If a node reaches
needs_humanin a non-interactive context, the supervisor waits up tosupervisor.needsHumanTimeoutMs(default: 30 minutes), then auto-answers with “decide safest default” and reopens the node so planning can continue. - To unblock
needs_humanimmediately, usedagain answer --node <id> --answer "..."or (in the web UI chat) send/answer [nodeId] <answer...>.
State layout
Dagain stores state in:
.dagain/config.json— runner + role configuration (shared across sessions).dagain/current-session.json— pointer to the current session id.dagain/sessions/<sessionId>/— session storage (goal + db + runs + logs + artifacts).dagain/state.sqlite/.dagain/workgraph.json/.dagain/runs//.dagain/artifacts//.dagain/checkpoints//.dagain/memory/— current-session “view” (symlinks).dagain/GOAL.md— current session goal (symlink)
The UI “Log” panel shows human-readable result output (status/summary, checkpoint question) derived from result.json by default; raw stdout is still available in .dagain/runs/*/stdout.log.
Publishing
GitHub (knot0-com org)
gh repo create knot0-com/dagain --public --source=. --remote=origin --pushIf you don’t have permission to create repos in knot0-com, create a staging repo under your user and transfer it:
gh repo create <you>/dagain --public --source=. --remote=origin --pushThen transfer via GitHub UI: Settings → General → Transfer ownership.
npm + npx
Automated publish (recommended)
Publishing is automated via GitHub Actions on version tags (vX.Y.Z). The tag must match package.json.version.
Configure npm Trusted Publishing (OIDC) for
knot0-com/dagainand workflow filenamenpm-publish.yml(npmjs.com → package Settings → Trusted Publisher).Cut a release:
npm version patch
git push --follow-tags- Verify:
npx dagain --helpManual publish
- Ensure you’re logged in:
npm whoami- Publish:
npm publish --access publicThen:
npx dagain --help