Skip to main content

Multi-Agent Review

This example runs two independent reviewer agents at the same time using Parallel, then feeds both reviews into an aggregator task that produces a final verdict.

Workflow Definition

// multi-agent-review.tsx
import { createSmithers, Task, Sequence, Parallel } from "smithers-orchestrator";
import { ToolLoopAgent as Agent } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const { Workflow, smithers } = createSmithers({
  review: z.object({
    approved: z.boolean(),
    feedback: z.string(),
  }),
  verdict: z.object({
    approved: z.boolean(),
    summary: z.string(),
  }),
});

const securityReviewer = new Agent({
  model: anthropic("claude-sonnet-4-5-20250929"),
  instructions:
    "You are a security-focused code reviewer. Look for vulnerabilities, injection risks, and auth issues. Return your verdict and detailed feedback.",
});

const qualityReviewer = new Agent({
  model: anthropic("claude-sonnet-4-5-20250929"),
  instructions:
    "You are a code quality reviewer. Evaluate readability, test coverage, error handling, and adherence to best practices. Return your verdict and detailed feedback.",
});

const aggregator = new Agent({
  model: anthropic("claude-sonnet-4-5-20250929"),
  instructions:
    "You receive two code reviews. Synthesize them into a single verdict. Approve only if both reviewers approve.",
});

export default smithers((ctx) => {
  const secReview = ctx.outputMaybe("review", { nodeId: "security-review" });
  const qualReview = ctx.outputMaybe("review", { nodeId: "quality-review" });

  return (
    <Workflow name="multi-agent-review">
      <Sequence>
        {/* Both reviews run concurrently */}
        <Parallel maxConcurrency={2}>
          <Task id="security-review" output="review" agent={securityReviewer}>
            Review this PR diff for security issues:{"\n\n"}
            ```diff{"\n"}- const token = req.query.token;{"\n"}+ const token =
            sanitize(req.headers.authorization);{"\n"}```
          </Task>

          <Task id="quality-review" output="review" agent={qualityReviewer}>
            Review this PR diff for code quality:{"\n\n"}
            ```diff{"\n"}- const token = req.query.token;{"\n"}+ const token =
            sanitize(req.headers.authorization);{"\n"}```
          </Task>
        </Parallel>

        {/* Aggregate once both reviews are complete */}
        <Task id="aggregate" output="verdict" agent={aggregator}>
          Combine these two reviews into a final verdict:{"\n\n"}
          Security review: {secReview?.approved ? "APPROVED" : "REJECTED"} -{" "}
          {secReview?.feedback}
          {"\n\n"}
          Quality review: {qualReview?.approved ? "APPROVED" : "REJECTED"} -{" "}
          {qualReview?.feedback}
        </Task>
      </Sequence>
    </Workflow>
  );
});

Running the Workflow

smithers run multi-agent-review.tsx --input '{}'
Example output:
[multi-agent-review] Starting run jkl012
[security-review] Running...
[quality-review] Running...
[security-review] Done -> { approved: true, feedback: "Good: moved token from query to header, added sanitization." }
[quality-review] Done -> { approved: false, feedback: "Missing error handling if authorization header is absent." }
[aggregate] Done -> { approved: false, summary: "Security looks good, but quality reviewer flagged missing null check on header." }
[multi-agent-review] Completed

How Parallel Works

  • All children of Parallel start executing at the same time.
  • The maxConcurrency prop limits how many tasks run simultaneously. If omitted, all children run at once.
  • The Sequence after the Parallel block waits for all parallel tasks to finish before continuing.
  • Each task writes to the same review table but with different nodeId values, so they do not conflict.

Accessing Parallel Results

Use ctx.outputMaybe(table, { nodeId }) with the specific task ID to retrieve each reviewer’s output:
const secReview = ctx.outputMaybe("review", { nodeId: "security-review" });
const qualReview = ctx.outputMaybe("review", { nodeId: "quality-review" });
Both return undefined until their respective tasks complete. After the Parallel block finishes, both values are guaranteed to be available for the aggregator task.