Dual-Agent Collaborative Coding: From Spec Convergence to a Closed Testing Loop
This article explains why handing a coding request to a single AI agent produces code that compiles but misses the intent, and lays out a practical dual-agent workflow that fixes it. You will learn how to separate a planning/review role from an implementation role running in independent contexts, how to turn vague requirements into a spec skeleton with verifiable acceptance criteria and an explicit out-of-scope list, how to drive adversarial spec review to convergence using three objective rules, how to codify repeatedly violated rules in a versioned `ai-guide.md` file, and how to run vertical-slice development, cross audits, and jointly drafted tests. It also lists the machine-verifiable quality gates this approach depends on, plus the pitfalls that matter: consensus is not correctness, type checks and tests are the only reliable bar, and non-specialists should own intent, acceptance criteria, and trade-off calls rather than line-by-line coding.
1. Why a Single Agent Can’t Do It All
Plenty of teams are used to dropping a requirement into one AI coding assistant and letting it carry the work from design all the way to tests. In practice, three structural flaws show up again and again.
Self-review doesn’t work. Writing code and judging whether code is good are two different cognitive activities. When the same role in the same context is asked to produce an implementation and then review it, it tends to defend its own choices. That is not an attitude problem: it simply has no independent standing from which to judge. It is the classic trap of the athlete refereeing his own match.
Constraints get diluted in long sessions. The longer the conversation, the more the interface agreements, naming conventions, and error-code rules settled early on get buried under later implementation detail. The model treats the most recent content as the most important context, while the rules a project needs followed most strictly are often the dozens agreed upon at the very beginning.
Requirements are filled in by inference. The person asking for the requirement, especially if they are not a developer, usually can’t articulate the boundary conditions. The agent doesn’t stop to ask. It picks a path that “looks like it runs” and quietly fills the gaps with its own assumptions. The code executes, but it doesn’t match the real intent.
The conclusion is straightforward: split “thinking it through” from “writing it out” into two mutually independent roles, and carry the constraints in documents rather than in conversational memory.
2. Role Separation: Planner/Reviewer vs. Implementer
A workable division of labor looks like this:
- Role A (planning and review): turns vague requirements into a structured spec, hunts for problems, pushes back on the implementation, and drafts the test plan. In principle it does not write production code.
- Role B (implementation): builds strictly against the finalized spec and does not widen the scope on its own initiative.
- Shared constraint: the two contexts stay independent. A sees B’s output and B sees A’s objections, but neither inherits the other’s reasoning inertia. Disagreements travel through documents, not through one side persuading the other.
The deliberate design here is adversarial. Two roles sharing the same prior knowledge are likely to make the same mistake together. But their objectives differ: one is optimizing for safety, the other for completeness. Staging a few rounds of head-to-head review surfaces the places where the spec doesn’t hold up, and it surfaces them early. Using planning mode (produce a plan first, hold off on code) across the first two phases is the cheapest, highest-return part of this whole process.
3. Phase 1: Produce a Spec Skeleton Document
Don’t let the agent start coding right away. The first step is writing the requirement into an engineering spec. It becomes the single source of truth for the project, and every later dispute is settled against it. At minimum, six things have to be nailed down:
- The goal and verifiable acceptance criteria, each one mappable to some test
- Scope boundaries: an explicit list of what this cycle will not do. This prevents drift better than a list of what it will do.
- Interface and data contracts: inputs, outputs, field types, error codes, idempotency requirements
- Module and directory layout, plus the dependency direction between modules. Reverse dependencies are forbidden.
- Error and degradation strategy: how timeouts, retries, empty data, and partial failures are handled
- Observability requirements: log levels and key fields, which metrics to expose
A skeleton you can reuse directly:
# Module Spec: <module name>## Goal## Acceptance Criteria (numbered, referenceable by tests)- AC-1 ... / AC-2 ...## Explicitly Out of Scope## Interface Contracts| Name | Inputs | Outputs | Error Codes | Idempotent |## Data Model and Constraints## Module Structure and Dependency Direction## Errors / Degradation / Retry## Logging and Metrics## Open Questions
Pay attention to that last section. A spec will always contain uncertain points. Log them explicitly instead of letting the agent make its own guesses and bake them into code.
4. Phase 2: Adversarial Review and Convergence
With the spec in hand, enter a loop: A produces the spec, B picks it apart item by item with supporting reasons, A responds item by item by revising where it accepts the point and pushing back where it doesn’t, and the cycle repeats.
“Until both sides are satisfied” is not a convergence criterion. It is too subjective. Use three rules that can actually be decided:
- No new blocking items: a full consecutive round with no P0/P1 issues (blocking defects, data errors, security problems) counts as converged.
- Dissent ledger cleared: every point still in disagreement is written into the spec’s decision record, with the conclusion, the reasoning, and the impact surface. Nothing stays hanging.
- Acceptance criteria testable: every acceptance criterion points at a specific test case. If it can’t, the description is still vague.
Two practical tricks. First, mandate “at least one objection per round, or an explicit no-objection declaration with a reason for each item,” which blocks polite agreement. Second, require responses formatted as numbered lists tagged [Agree] or [Disagree] with the rationale, so round-over-round diffs are quick to compare. When disagreements pile up, don’t loop forever. Anything that can’t be converged should be handed to a human to decide.
5. Phase 3: Codify the Rules in a Project-Level Constraint File
Write the rules that keep getting violated into a convention file at the repository root, such as ai-guide.md, and have the implementation role read it at the start of every task. The principle is verifiable rules, not vague asks like “write it well.”
# Project-Level Constraints## Tech Stack and Versions (do not upgrade dependencies on your own)## Directory Rules: where new files go, which directories are forbidden## Code Style: naming; route all error handling through utils/errors; no bare throw of strings## Change Size: <= 400 lines per change; split anything larger## Definition of Done: lint passes + type check passes + relevant unit tests pass + spec updated## Forbidden: no new external interfaces that aren't declared in the spec; never write a secret into the repo
Version this file alongside the code, and treat it as a living document. Every problem a new audit round exposes should be distilled into one more rule. That is the positive feedback loop of the whole workflow: a defect only has to be made once.
6. Phase 4: Modular, Incremental Development
Work module by module, and prefer vertical slices, where one module carries its own UI, logic, and storage and runs end to end, over scaffolding empty shells for every layer first. The reason is simple: a vertical slice is verifiable the moment it is done, so problems surface early. With horizontal layering, interface mismatches often only show up at integration time, and the rework is expensive.
Keep the step size small inside each module. Hold a single change to a few hundred lines, run static checks, type checks, and the related tests immediately, and move to the next module only once they pass. Also put the acceptance criteria numbers from the spec into the commit message, so you build a traceability chain from requirement to code to test.
7. Phase 5: Cross Code Audit
When a module is done, hand it to Role A for an independent audit that produces graded findings (blocking, major, suggestion). A fixed five-category checklist keeps this from being a gut-feel exercise:
- Correctness: boundary and null handling, pagination and concurrency, missing state machine branches
- Security: input validation, authentication and authorization gaps, no hard-coded secrets, injection and path traversal, log redaction
- Consistency: deviations from the spec, unified naming and error codes, duplicated implementations
- Performance: I/O inside loops, N+1 queries, unbounded caches and memory growth
- Maintainability: cyclomatic complexity, overly long functions, implicit conventions missing key comments
The flow: B submits the code and the change notes, A issues graded findings, B responds item by item by either fixing or explaining why not, and once the two sides agree, the newly surfaced issues go back into the constraint file.
8. Phase 6: Jointly Draft the Test Plan, with Human Backfill
Automated tests get the same co-drafting treatment rather than being accepted on trust. Let Role A draft the test case design based on the code and the acceptance criteria (equivalence classes, boundary values, exception paths, failure scenarios), let Role B implement them, and let the two go a few rounds on coverage before finalizing. The bar is hard: every acceptance criterion gets at least one assertion, and when that assertion fails, the error message pinpoints the specific rule that was violated.
Then comes the part that has to be done by a human. Automation covers logic, not feel. The UI defects that non-specialists hit most often live almost entirely in this gap:
- Whether empty, loading, and error states each give a clear message instead of a blank screen or a silent failure
- Long-text wrapping and truncation, misalignment at different resolutions and zoom levels, control alignment
- Cross-checking permission combinations, meaning how the same page looks to different roles
- Copy readability, whether an action is reversible, whether a mistaken action requires confirmation and offers undo
- Perceived responsiveness under realistic data volumes and extreme data
When fixing bugs, insist on writing the failing test before changing the code, so you don’t fix one thing and introduce two. Tests that pass on regression stay in the suite to keep the bug from coming back.
9. Engineering Scaffolding and Objective Quality Gates
Consensus between agents cannot substitute for objective verification. At minimum you need these machine-decidable gates: version control with branch protection; a CI pipeline chaining together build, static checks, unit tests, and dependency vulnerability scanning; artifact archiving; log service, monitoring, and alerting; and secret management. These all fall into general-purpose cloud product categories, and Baidu AI Cloud has corresponding capabilities available to plug in directly. Turning “consensus” into a repeatably executable pipeline is the real precondition for the process staying stable.
10. Applicability Boundaries and Common Pitfalls
- Consensus is not correctness. Two models can share the same wrong prior. Specs and tests have to be checked back against the real requirement, and a human owns the acceptance criteria.
- “It looks like it runs.” Compilation rates for generated code are high. Correctness rates are not. Type checks and tests are the only reliable gates.
- Context cost. Summarize long sessions and restart them. Don’t count on the agent remembering an agreement made thirty turns ago; documents are the memory carrier.
- Security red lines. Don’t put secrets, production data, or user personal information into a conversation. Any new dependency needs a human to confirm its source and maintenance status.
- Role positioning. A non-specialist developer’s value lies in defining intent, acceptance criteria, and trade-off decisions, not in line-by-line coding.
11. One-Page Rollout Checklist
- Turn the requirement into a document first, including acceptance criteria and an explicit “out of scope” section.
- Split planning and implementation into two roles with independent contexts.
- Run the adversarial review loop until a full consecutive round produces no new blocking items.
- Record all dissent in a decision log, and let a human make the final calls.
- Commit and version the constraint file, with every rule verifiable.
- Develop module by module in vertical slices, keeping each change to a few hundred lines.
- Run a cross audit per module, issue graded findings, and close them one by one.
- Draft the test plan jointly, mapping acceptance criteria one-to-one onto test cases.
- Have a human cover UI and feel, and write the failing test before fixing each bug.
- Move every gate into the pipeline, and write defect lessons back into the constraint file.
