šŸ”„ 0
⭐ 0
Lesson 4 of 10 30 min +250 XP

Three-Agent Architecture Pattern

Evolution from Two to Three Agents

The two-agent pattern works, but has limitations:

  • Self-evaluation bias: Coders tend to rate their own work highly
  • No quality gate: Features pass without rigorous testing
  • Missing planning: Complex features need upfront design

Anthropic's three-agent harness addresses these:

Three-Agent Harness Architecture

The Planner Agent

The Planner converts vague requirements into detailed specifications:

Responsibilities

interface PlannerAgent {
  // Convert requirements to detailed spec
  createProductSpec(requirements: string): Promise<ProductSpec>;

  // Break spec into implementable sprints
  createSprintPlan(spec: ProductSpec): Promise<Sprint[]>;

  // Define acceptance criteria
  createSprintContract(sprint: Sprint): Promise<SprintContract>;
}

Product Specification

interface ProductSpec {
  overview: {
    name: string;
    description: string;
    targetUsers: string[];
    coreValue: string;
  };

  technicalStack: {
    frontend: string;      // "React + Vite + TypeScript"
    backend: string;       // "FastAPI + SQLite"
    styling: string;       // "TailwindCSS"
    testing: string;       // "Playwright"
  };

  features: Feature[];

  constraints: {
    timeline: string;
    complexity: 'low' | 'medium' | 'high';
    qualityBar: string;
  };
}

Sprint Contracts

Before implementation, Planner and Generator negotiate explicit agreements:

interface SprintContract {
  sprintId: string;
  feature: string;

  // What "done" looks like
  acceptanceCriteria: {
    criterion: string;
    testable: boolean;
    verificationMethod: string;
  }[];

  // Testable behaviors
  behaviors: {
    given: string;
    when: string;
    then: string;
  }[];

  // What the evaluator will check
  evaluationCriteria: {
    dimension: string;
    weight: number;
    passingScore: number;
  }[];
}
Example Sprint Contract:
{
  "sprintId": "sprint-001",
  "feature": "User Registration",

  "acceptanceCriteria": [
    {
      "criterion": "User can submit registration form",
      "testable": true,
      "verificationMethod": "Playwright form submission test"
    },
    {
      "criterion": "Validation errors display for invalid input",
      "testable": true,
      "verificationMethod": "Test with invalid email format"
    },
    {
      "criterion": "Success message shows after registration",
      "testable": true,
      "verificationMethod": "Check for success toast/message"
    }
  ],

  "behaviors": [
    {
      "given": "User is on registration page",
      "when": "User submits valid email and password",
      "then": "Account is created and success message displays"
    },
    {
      "given": "User is on registration page",
      "when": "User submits invalid email format",
      "then": "Validation error displays under email field"
    }
  ],

  "evaluationCriteria": [
    { "dimension": "functionality", "weight": 0.4, "passingScore": 0.8 },
    { "dimension": "design", "weight": 0.3, "passingScore": 0.7 },
    { "dimension": "code_quality", "weight": 0.3, "passingScore": 0.7 }
  ]
}

The Generator Agent

The Generator implements features based on sprint contracts:

Responsibilities

interface GeneratorAgent {
  // Implement feature according to contract
  implementFeature(contract: SprintContract): Promise<Implementation>;

  // Self-evaluate before handoff
  selfEvaluate(implementation: Implementation): Promise<SelfEvaluation>;

  // Use git for version control
  commitChanges(message: string): Promise<void>;
}

Implementation Pattern

async function generatorSession(contract: SprintContract) {
  console.log(`šŸ”Ø Generator: Starting ${contract.feature}`);

  // 1. Read the sprint contract
  const criteria = contract.acceptanceCriteria;
  const behaviors = contract.behaviors;

  // 2. Implement each acceptance criterion
  for (const criterion of criteria) {
    console.log(`  šŸ“ Implementing: ${criterion.criterion}`);

    // Generate code
    await implementCriterion(criterion);

    // Test locally
    const localTest = await runLocalTest(criterion);
    if (!localTest.passed) {
      console.log(`  āš ļø Local test failed, fixing...`);
      await fixImplementation(criterion, localTest.error);
    }

    // Commit incrementally
    await bash(`git add .`);
    await bash(`git commit -m "feat: ${criterion.criterion}"`);
  }

  // 3. Self-evaluate before handoff
  const selfEval = await selfEvaluate(contract);
  console.log(`  šŸ“Š Self-evaluation: ${selfEval.score}/100`);

  // 4. Create handoff document for evaluator
  await writeFile('handoff-to-evaluator.json', JSON.stringify({
    sprintId: contract.sprintId,
    feature: contract.feature,
    selfEvaluation: selfEval,
    commitHash: await bash('git rev-parse HEAD'),
    readyForEvaluation: true
  }));

  console.log(`āœ… Generator: Handoff ready for Evaluator`);
}

The Evaluator Agent

The Evaluator provides rigorous, unbiased assessment:

GAN-Inspired Design

The evaluator is inspired by the discriminator in Generative Adversarial Networks:

GAN-Inspired Feedback Loop

Evaluation Criteria

Anthropic's evaluator uses four dimensions:

interface EvaluationCriteria {
  designQuality: {
    description: "Coherence, mood, identity";
    rubric: {
      excellent: "Cohesive design language, clear visual hierarchy";
      good: "Consistent styling, minor inconsistencies";
      poor: "Inconsistent, no clear design system";
    };
    weight: 0.25;
  };

  originality: {
    description: "Custom decisions vs template defaults";
    rubric: {
      excellent: "Unique design choices, memorable";
      good: "Some customization beyond defaults";
      poor: "Generic, looks like unmodified template";
    };
    weight: 0.25;
  };

  craft: {
    description: "Typography, spacing, color harmony";
    rubric: {
      excellent: "Polished details, pixel-perfect";
      good: "Well-executed, minor issues";
      poor: "Sloppy, inconsistent spacing/alignment";
    };
    weight: 0.25;
  };

  functionality: {
    description: "Usability and task completion";
    rubric: {
      excellent: "All features work flawlessly";
      good: "Core features work, minor bugs";
      poor: "Critical features broken";
    };
    weight: 0.25;
  };
}

Evaluator Implementation

async function evaluatorSession(handoff: HandoffDocument) {
  console.log(`šŸ” Evaluator: Reviewing ${handoff.feature}`);

  // 1. Load sprint contract
  const contract = await loadSprintContract(handoff.sprintId);

  // 2. Start the application
  await bash('npm run dev &');
  await sleep(3000); // Wait for server

  // 3. Run Playwright tests for each behavior
  const behaviorResults = [];
  for (const behavior of contract.behaviors) {
    const result = await runPlaywrightTest(behavior);
    behaviorResults.push({
      behavior: behavior,
      passed: result.passed,
      screenshot: result.screenshot,
      error: result.error
    });
  }

  // 4. Grade each dimension
  const grades = {
    designQuality: await gradeDesign(behaviorResults),
    originality: await gradeOriginality(behaviorResults),
    craft: await gradeCraft(behaviorResults),
    functionality: await gradeFunctionality(behaviorResults)
  };

  // 5. Calculate weighted score
  const finalScore = calculateWeightedScore(grades, contract.evaluationCriteria);

  // 6. Generate feedback
  const feedback = await generateDetailedFeedback(grades, behaviorResults);

  // 7. Make pass/fail decision
  const passed = finalScore >= contract.evaluationCriteria
    .reduce((sum, c) => sum + c.passingScore * c.weight, 0);

  // 8. Write evaluation report
  await writeFile('evaluation-report.json', JSON.stringify({
    sprintId: handoff.sprintId,
    feature: handoff.feature,
    grades,
    finalScore,
    passed,
    feedback,
    behaviorResults
  }));

  console.log(`šŸ“Š Evaluator: Score ${finalScore}/100 - ${passed ? 'PASSED' : 'FAILED'}`);

  if (!passed) {
    console.log(`šŸ“ Feedback for Generator:`);
    feedback.improvements.forEach(i => console.log(`  - ${i}`));
  }

  return { passed, feedback };
}

The Feedback Loop

When the evaluator rejects work, the generator iterates:

async function runThreeAgentHarness(requirements: string) {
  // Phase 1: Planning
  const planner = new PlannerAgent();
  const spec = await planner.createProductSpec(requirements);
  const sprints = await planner.createSprintPlan(spec);

  // Phase 2 & 3: Generation + Evaluation loop
  for (const sprint of sprints) {
    const contract = await planner.createSprintContract(sprint);

    let passed = false;
    let attempts = 0;
    const maxAttempts = 3;

    while (!passed && attempts < maxAttempts) {
      attempts++;
      console.log(`\nšŸ”„ Sprint ${sprint.id} - Attempt ${attempts}/${maxAttempts}`);

      // Generator implements
      const generator = new GeneratorAgent();
      await generator.implementFeature(contract);

      // Evaluator grades
      const evaluator = new EvaluatorAgent();
      const result = await evaluator.evaluate(contract);

      passed = result.passed;

      if (!passed && attempts < maxAttempts) {
        // Feed feedback back to generator
        await generator.incorporateFeedback(result.feedback);
      }
    }

    if (!passed) {
      console.log(`āŒ Sprint ${sprint.id} failed after ${maxAttempts} attempts`);
      // Human intervention needed
    }
  }
}

When to Use Three Agents vs Two

ScenarioRecommended Pattern
Simple CRUD appTwo-agent
Complex UI with design requirementsThree-agent
Backend API onlyTwo-agent
Full-stack with quality barThree-agent
Prototype/MVPTwo-agent
Production-quality productThree-agent

E-Commerce Three-Agent Example

ecommerce-three-agent/
ā”œā”€ā”€ agents/
│   ā”œā”€ā”€ planner.ts        # Converts requirements to specs
│   ā”œā”€ā”€ generator.ts      # Implements features
│   └── evaluator.ts      # Tests and grades
ā”œā”€ā”€ contracts/
│   ā”œā”€ā”€ auth-sprint.json
│   ā”œā”€ā”€ products-sprint.json
│   ā”œā”€ā”€ cart-sprint.json
│   └── checkout-sprint.json
ā”œā”€ā”€ evaluations/
│   └── [generated reports]
└── harness.ts

Key Takeaways

  • Three agents separate concerns - Planning, Implementation, Evaluation
  • Sprint contracts define "done" - Explicit acceptance criteria
  • GAN-inspired feedback - Evaluator drives quality improvement
  • Self-evaluation is biased - Separate evaluator catches issues
  • Iteration improves output - Multiple attempts with feedback

Practice Exercise

Design a three-agent harness for a dashboard application:

  • Create a product spec with Planner output format
  • Design sprint contracts for 3 features
  • Define evaluation criteria specific to dashboards
  • Write the feedback loop logic

Next Lesson

Now that we understand agent architectures, let's dive into Session Management & State Persistence - the techniques that enable work across multiple context windows.

Two-Agent Architecture Pattern