dt orchestrates two LLM agents: a Writer (default: Claude) that implements code, and a Reviewer (default: Gemini) that reviews the changes against your linters and tests. This guide covers every command and mode.
Bare dt in an initialized repo opens a persistent session. Both models keep their context for the whole session — follow-up tasks build on what was just done, on both the writer and reviewer side.
$ dt
dt ❯ add a dark mode toggle
dt ❯ now persist it to localStorage ← full memory of the previous task
Session commands:
| Command | Effect |
|---|---|
/auto |
Toggle autonomous looping on/off |
/image <path> |
Stage a screenshot for the next task (repeatable; handles drag-and-drop paths and ~/) |
/paste |
Stage the image currently on the OS clipboard (Cmd+Ctrl+Shift+4 → snip → /paste) |
/plan <task> |
Plan first, then execute after approval |
/review [task] |
Review uncommitted changes |
/help, /quit |
Help / leave |
Ctrl+C |
Stop the running task/review (the session stays); press again to quit |
Anything else you type is sent to the duet as a task.
dt "add input validation to the signup form"Interactive flow (default):
- The writer reads your codebase and makes the change.
dtshows the diff stat and asks:review changes with gemini? (y/n)- Your configured checks run; the reviewer gets the diff, check results, and the writer's own explanation of what it did.
- Verdict:
APPROVED(and checks pass) → done. Otherwise:let claude fix the issues? (y/n)and the loop continues.
Auto flow (--auto): same loop with no questions — the models iterate until mutual approval or the round budget (max_rounds) runs out. If the reviewer keeps raising the same blockers or the writer stops making progress, dt pauses, shows you the disputed blockers, and asks for one clarification; your guidance is injected into both models' prompts and the loop resumes (budget extends once, up to 2×max_rounds).
dt "add input validation" --autoQuestion tasks: if the task is a question ("do we have performance issues?"), the writer answers without touching code — and the reviewer then gives a second opinion on the answer itself (auto: always; interactive: you're asked). Wrong or incomplete answers loop back for revision like code does. An answer verdict reads SOUND / UNSOUND, not APPROVED: it judges the answer, not the code the answer is about.
Questions about a pull request: name the pull requests in the task ("review https://github.com/acme/api/pull/529") and dt fetches their diffs with gh pr diff, so the reviewer judges the answer against the change rather than against your current branch. Without a fetchable change the review is refused rather than run against the wrong revision — gh pr checkout <number>, or authenticate gh, and try again.
The answer to a pull request task opens with a verdict block, before any analysis:
VERDICT: NO-GO
BLOCKER: Saved credentials break when the key and the agent id are in different scopes.
WARNING: Redaction can corrupt a tool schema that has a parameter named "token".
NO-GO whenever there is at least one BLOCKER. Each line is one plainly worded sentence, so the merge decision is readable on its own; the full analysis follows underneath, unchanged. Anything the writer only suspects is kept out of the block and raised in the analysis. The VS Code panel renders the block as a summary card.
Use the full URL. The trigger is the same detector that fetches the diff, and it reads github.com/owner/repo/pull/123 only — owner/repo#123 is a label dt prints, never one it parses, so a task written that way gets neither the verdict block nor a fetched diff.
What a review will and will not block on: a blocker has to be a defect the reviewer can say how to reach — the input, state, or call order that makes it fail. A principle cited without a failure behind it, anything the diff left unverifiable, and anything the reviewer would merely have written differently all come back as suggestions. Optional findings are prefixed Nit: and raised freely; they never block.
Reviews declare what they read, and dt checks it. Every review ends with a FILES READ: line. dt records the files the reviewer actually opened from its own tool calls and warns when the two disagree:
⚠ gemini listed services/billing.py as read, but opened only utils/vapi.py this turn
— treat that part of the review as unverified
The warning does not fail the review — the finding underneath may still be sound — but it is the one part of a review that is a record rather than a claim. FILES READ: none is a legitimate answer; a review of the diff alone is still a review.
Standalone reviews start clean. dt review and the panel's review button reset the reviewer's model session first, so the verdict is a fresh judgement on the code as it stands rather than a continuation of whatever that model last concluded about an earlier diff. Rounds inside one task keep resuming, which is where the continuity is worth having — the reviewer is watching its own findings get addressed.
Role flip / images / continuity:
dt "fix bug" --writer gemini # Gemini writes, Claude reviews
dt "match this" --image mockup.png # attach screenshots
dt "fix the test" -c # carry previous session's contextFor large or ambiguous tasks: plan before touching files.
dt plan "refactor the database connection logic"- The writer produces a Markdown plan (no code changes).
review this plan with gemini? (y/n)— the reviewer critiques the approach.execute this task? (y/n)— on yes, implementation starts with the approved plan as context (add--autoto run the execution loop unattended).
Review uncommitted changes you wrote yourself — including new untracked files, which are folded into the diff.
dt review
dt review --task "add OAuth login flow" # verify against your intent
dt review --reviewer claudeCreates .duet/config.toml (checks, models, policy) and .duet/prompts/ (editable prompt templates), and gitignores .duet/sessions/.
Verifies: git repo, .duet/config.toml parses, Claude CLI presence and auth, ANTHROPIC_API_KEY fallback, GEMINI_API_KEY, prompt templates.
Deletes all session logs, or one named session — its directory name under .duet/sessions, or a unique prefix of it (dt clear 20260805-1936). An ambiguous or unknown name lists what is recorded instead of deleting.
Runs dt as a JSON-lines server on stdin/stdout for GUI frontends — this is what the VS Code extension talks to. Commands in (task, plan, review, answer, ping, quit), events out (round_started, stream_chunk, verdict, ask, task_done, …). Adapters persist across tasks, so context carries exactly like the interactive session.
| Flag | Applies to | Description |
|---|---|---|
-a, --auto |
dt <task>, dt run, dt plan, bare dt |
Loop without per-round prompts until both models approve |
--writer <model> |
task commands, dt serve |
Which model writes (claude/gemini); the other reviews |
--reviewer <model> |
dt review |
Which model reviews |
-t, --task <desc> |
dt review |
Intent for the reviewer to verify against |
--image <path> |
task commands | Attach one or more images |
-c, --continue-session |
task commands | Include the previous session's context |
-v, --verbose |
all | Full untruncated output and diagnostics |
Run before every review; failures are shown to the reviewer and block approval until fixed.
[checks]
test = "npm test"
lint = "npm run lint"
typecheck = "npx tsc --noEmit"[policy]
max_rounds = 4 # round budget per task
auto = false # true = behave as if --auto was always passed
allow_dirty_worktree = true[claude]
mode = "auto" # "cli" (Claude Code CLI, keeps its session via --resume),
# "api" (direct Anthropic API with message history),
# "auto" (CLI first, API fallback)
skip_permissions = true # let the writer edit files without interactive promptsEdit the files in .duet/prompts/ to add project rules (e.g. "always use Tailwind utilities"). Available variables: implement.txt — {task}, {context} · review.txt — {task}, {diff}, {checks}, {writer_notes} · fix.txt — {task}, {review_feedback} · plan.txt — {task}, {context}.