The request_plan_review and request_code_review tools implement a FULLY AUTONOMOUS review-then-implement loop. Once triggered, you MUST run the entire loop to completion without stopping to ask for permission between phases.
Only activate when the user mentions "DUUL" (or "두울") in their request. Examples:
- "DUUL로 개발 진행해줘", "두울 돌려줘", "DUUL로 해줘"
- "run DUUL", "use DUUL for this", "start DUUL"
Do NOT activate for casual requests like "review my code", "check this", or "look over my plan". Those are normal review requests that you should handle yourself. The keyword "DUUL" (or "두울") must be present.
CRITICAL: This is a continuous, uninterrupted sequence. Do NOT pause between phases to ask the user "should I proceed?" or "should I implement?". The user already authorized the full loop when they requested DUUL.
Phase 1 runs on Opus via the duul-planner subagent for maximum plan quality. To keep token cost down, the planner writes plans in compressed "caveman" style and submits the plan via a file (plan_file) rather than a large inline string.
- Launch the
duul-plannersubagent using the Agent tool with the user's requirements, workspace root path, and any relevant context. The subagent runs on Opus automatically (model: opusin its definition). - The subagent handles the entire plan ping-pong loop internally:
- Writes a detailed implementation plan
- Calls
request_plan_reviewand iterates on REVISE feedback - Returns the approved plan,
review_id, andgit_head_sha
- If the subagent reports
requires_human_review === true: pause and ask the user. - Extract
approved_plan,review_id, andgit_head_shafrom the subagent's response.
Fallback: If the subagent fails or the MCP tool is not accessible from the subagent, fall back to running Phase 1 directly (same as before but in the main agent). Use the plan-artifact protocol below in either path.
The plan-file escape hatch only helps if its contents are written reliably. Treat .duul/plan.md as an immutable snapshot for each review round:
- Compose the complete revision before using a file-edit tool.
- Use
Writeto replace the entire file once, then read it back and verify the intended plan is present. - Do not assemble a revision with several exact-text
Editoperations. Their search strings are stale as soon as an earlier edit succeeds, causingString to replace not founderrors. - If
Writeis unavailable, read immediately before a single whole-fileEdit; after any mismatch error, stop, re-read, and write a fresh full snapshot. Never retry the failed replacement text. - Only then call
request_plan_reviewwithplan_file: ".duul/plan.md". One agent owns this artifact at a time.
Phase 2: Unit-verify Ping-Pong (Opus, start IMMEDIATELY after Phase 1 approval — do NOT ask for confirmation)
Phase 2 runs on the main agent (Opus) for maximum code quality.
- Write the actual code to the project files based on the approved plan (received from the
duul-plannersubagent). Use your edit/write tools to make real changes. - Run lint if available. Check
package.jsonscripts forlint,lint:fix, oreslint, or check for a Makefile/config equivalent. If a lint command exists, run it with auto-fix (e.g.npm run lint -- --fixornpx eslint --fix). Fix any remaining errors before proceeding. If no lint is configured, skip this step. - Call
request_code_reviewwith the code and the approved plan. For large content, avoid huge inline strings (they can make the tool call collapse to an empty{}and fail validation): write the code to.duul/code.mdand the plan to.duul/plan.md, then passcode_file: ".duul/code.md"andapproved_plan_file: ".duul/plan.md"(relative paths) plusworkspace_root. Inlinecode/approved_planonly for small content. - If
review_status === "incomplete": checkmissing_contextand retry with narrower scope. - If
blocking_issues.length > 0orverdict === "REVISE": fix the code in the actual files, re-run lint if applicable, and call again. - If
requires_human_review === true: pause and ask the user. - Repeat until
verdict === "APPROVE"with no blocking issues.
- Report to the user: "Plan approved and code review passed." with a summary of changes made.
- ALWAYS pass
workspace_root(preferred) orproject_root(deprecated) when calling review tools. This gives the reviewer 7 file exploration tools:read_file,list_directory,search_in_files,read_file_range,stat_file,read_json,list_tracked_files. - For monorepos, pass
working_directoriesto restrict the reviewer's scope to relevant subdirectories. - Pass
linked_rootsfor read-only access to related external workspaces (max 5). - Pass
changed_files,entrypoints, andartifact_refsto guide the reviewer's focus. - Set
tracked_only: trueto restrict file access to git-tracked files only. - If the reviewer still raises an incorrect concern despite having codebase access, use
notes_to_reviewerto rebut it. - ALWAYS pass
user_original_requestwith the user's verbatim, unparaphrased problem statement. The reviewer uses it to enforce symptom-match (before/after/causal_chain) and server-side gates catch scope-punting, test-only fixes, and rendering-symptom mismatches when this field is present.
workspace_roottakes priority overproject_root(deprecated).working_directoriesrestricts access to listed subdirectories.linked_rootsprovides separate read-only scopes.
- Every review response includes a
review_id. ALWAYS pass this asprevious_review_idon the next call. This maintains the reviewer's full conversation context:- Plan review round 1 → returns
review_id: "resp_abc" - Plan review round 2 → pass
previous_review_id: "resp_abc"→ reviewer remembers round 1 - Plan approved with
review_id: "resp_xyz" - Code review round 1 → pass
previous_review_id: "resp_xyz"→ reviewer remembers the entire plan review conversation
- Plan review round 1 → returns
- This eliminates redundant file reading and ensures consistent feedback across the full review loop.
- Also pass
git_head_shaon each call, andprevious_git_head_shafrom the last round, to enable stale-context detection.
If a conversation is interrupted mid-review (context limit, crash, user closes session), the review context can be resumed in a new conversation:
- After every review call, write the current state to
.duul-state.jsonin the project root:{ "review_id": "resp_xyz", "phase": "plan" | "code", "verdict": "REVISE" | "APPROVE", "approved_plan": "...", "iteration": 3, "git_head_sha": "abc123" } - At the start of a new conversation, if the user asks to continue DUUL (e.g., "두울 이어서 해줘", "continue DUUL"), check for
.duul-state.jsonin the project root. - If it exists, read it and resume:
- Pass
previous_review_idfrom the savedreview_idto maintain reviewer context. - Pass
git_head_shafrom the saved state asprevious_git_head_sha. - If
phase === "plan"andverdict === "REVISE": continue Upfront-plan Ping-Pong. - If
phase === "plan"andverdict === "APPROVE": start Phase 2 immediately with the savedapproved_plan. - If
phase === "code": continue Unit-verify Ping-Pong with the savedapproved_plan.
- Pass
- Delete
.duul-state.jsonwhen the full loop completes (both phases approved).
- If
review_status === "incomplete", the reviewer ran out of context budget or tool rounds. - Check
tool_exhaustion_reasonandmissing_contextto understand why. - Retry with: narrower scope (fewer
artifact_refs), or more specificchanged_files. - Do NOT treat an incomplete review as a pass — always retry or escalate.
Every review response includes an optional cost_warning field (null by default). Once iteration_count crosses ~60% of iteration_limit (e.g. iteration 5 of 7, or iteration 3 of 5), the server populates it with a short message that includes the current round's estimated cost.
When cost_warning is non-null, surface it to the user before deciding to continue. Typical framing: "We're on iteration N of M at ~$X per round — do you want me to keep iterating, accept the current REVISE with minor issues, or escalate this to human review?" Don't silently burn through the remaining iterations.
- NEVER stop between Phase 1 and Phase 2 to ask "should I implement?" — just do it.
- "Implement the code" means writing/editing actual project files, not just showing code in chat.
confidenceis advisory only. Never use it as a sole trigger for retries.- Always pass the full approved plan text as
approved_planin Phase 2. - Do not skip Phase 1. Even if the user provides code directly, write a plan first.
- Do NOT stop early just because you've iterated a few times. If the reviewer says REVISE, you MUST fix and resubmit. The loop ends ONLY when the reviewer returns APPROVE, not when you feel "enough" iterations have passed.
- If the loop exceeds the iteration limit (plan: 7, code: 7, partition: 5 by default), pause and ask the user whether to continue.