Aaditya

Agent Lanes

AI chats

About

Run several coding agents side by side on one task, each with live status, streamed output and a pick the winner action.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Agent Lanes to your project and fits it to your theme.

Code

How Agent Lanes is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client"

import * as React from "react"
import { AgentLane, AgentLanes, Button, RotateCcwIcon } from "@/components/ui/agent-lanes"

// The demo's times count back from the moment it renders. Read through `performance`, which
// Next.js allows while it prerenders a page; `Date.now()` during render fails the build there.
const demoNow = () => performance.timeOrigin + performance.now()

/* Three agents try three fixes for the same flaky test. One fails. */

interface Script {
  id: string
  name: string
  monogram: string
  description: string
  steps: string[]
  output: string
  /** Characters revealed per 60 ms tick. */
  speed: number
  fails?: string
  metrics: AgentLane["metrics"]
}

const SCRIPTS: Script[] = [
  {
    id: "a",
    name: "Attempt A",
    monogram: "A",
    description: "Wait on the confirmation heading",
    speed: 9,
    steps: ["Reading checkout.spec.ts", "Editing the assertion", "Running the test 20 times"],
    output:
      'The test waits a fixed 2 s for the order to save, and the preview database sometimes takes longer.\n\nI replaced the sleep with an assertion on the heading, which retries until it appears:\n\n```ts\nawait expect(\n  page.getByRole("heading", { name: "Order confirmed" }),\n).toBeVisible({ timeout: 10_000 })\n```\n\nPassed 20 of 20 runs.',
    metrics: [
      { label: "Duration", value: "1 m 52 s", score: 112 },
      { label: "Diff", value: "+3 −1", score: 4 },
      { label: "Test runs", value: "20 of 20", score: 20, better: "higher" },
      { label: "Cost", value: "$0.21", score: 0.21 },
    ],
  },
  {
    id: "b",
    name: "Attempt B",
    monogram: "B",
    description: "Mock the payment API",
    speed: 6,
    steps: ["Reading the payment client", "Adding a route mock", "Running the test 20 times"],
    output:
      'The slow part is the call to the payment sandbox. I mocked it with page.route so the test stops depending on the network:\n\n```ts\nawait page.route("**/api/payments", (route) =>\n  route.fulfill({ json: { status: "succeeded" } }),\n)\n```\n\nPassed 20 of 20 runs, but it no longer covers the real payment call.',
    metrics: [
      { label: "Duration", value: "2 m 40 s", score: 160 },
      { label: "Diff", value: "+14 −0", score: 14 },
      { label: "Test runs", value: "20 of 20", score: 20, better: "higher" },
      { label: "Cost", value: "$0.34", score: 0.34 },
    ],
  },
  {
    id: "c",
    name: "Attempt C",
    monogram: "C",
    description: "Retry the whole test",
    speed: 7,
    steps: ["Reading playwright.config.ts", "Setting retries to 3", "Running the test 20 times"],
    output:
      'Setting retries: 3 in playwright.config.ts for the checkout project hides the flake in most runs.\n\n```ts\n{ name: "checkout", retries: 3 }\n```\n',
    fails:
      "Failed 2 of 20 runs even with retries. The race is still there, so retrying is not a fix.",
    metrics: [
      { label: "Duration", value: "3 m 5 s", score: 185 },
      { label: "Diff", value: "+1 −0", score: 1 },
      { label: "Test runs", value: "18 of 20", score: 18, better: "higher" },
      { label: "Cost", value: "$0.18", score: 0.18 },
    ],
  },
]

export default function AgentLanesDemo() {
  const [run, setRun] = React.useState(0)
  const [ticks, setTicks] = React.useState(0)
  const [winner, setWinner] = React.useState<string>()
  const [startedAt, setStartedAt] = React.useState(() => demoNow())
  // Lanes retried in place: the tick and time they restarted from. A retried lane passes.
  const [retried, setRetried] = React.useState<Record<string, { tick: number; at: number }>>({})

  const longest =
    Math.max(
      ...SCRIPTS.map((s) => (retried[s.id]?.tick ?? 0) + Math.ceil(s.output.length / s.speed)),
    ) + 12
  React.useEffect(() => {
    if (ticks > longest) return
    const id = window.setTimeout(() => setTicks((t) => t + 1), 60)
    return () => window.clearTimeout(id)
  }, [ticks, longest, run])

  const lanes: AgentLane[] = SCRIPTS.map((s) => {
    const spawn = 10
    const retry = retried[s.id]
    const laneStart = retry?.at ?? startedAt
    const fails = retry ? undefined : s.fails
    const chars = Math.max(0, (ticks - (retry?.tick ?? 0) - spawn) * s.speed)
    const finished = chars >= s.output.length
    const progress = Math.min(1, chars / s.output.length)
    const step = s.steps[Math.min(s.steps.length - 1, Math.floor(progress * s.steps.length))]
    // Metrics describe a finished attempt, so they only appear once the lane is done.
    if (ticks - (retry?.tick ?? 0) < spawn)
      return {
        ...s,
        metrics: undefined,
        status: "spawning",
        step: "Starting a worktree",
        startedAt: laneStart,
      }
    if (!finished)
      return {
        ...s,
        metrics: undefined,
        status: "running",
        step,
        output: s.output.slice(0, chars),
        startedAt: laneStart,
      }
    return {
      ...s,
      status: fails ? "failed" : "done",
      step: fails ? "18 of 20 runs passed" : "20 of 20 runs passed",
      output: s.output,
      error: fails,
      // The retry passes every run, so its row in the comparison says so.
      metrics: retry
        ? s.metrics?.map((m) =>
            m.label === "Test runs" ? { ...m, value: "20 of 20", score: 20 } : m,
          )
        : s.metrics,
      durationMs: (s.metrics?.[0]?.score ?? 0) * 1000,
    }
  })
  const allDone = lanes.every((l) => l.status === "done" || l.status === "failed")

  return (
    <div className="flex w-full max-w-5xl flex-col gap-3">
      <AgentLanes
        lanes={lanes}
        title="Fix the flaky checkout test"
        winnerId={winner}
        onPick={setWinner}
        onUndoPick={() => setWinner(undefined)}
        onRetry={(id) => setRetried((prev) => ({ ...prev, [id]: { tick: ticks, at: Date.now() } }))}
        defaultCompare={false}
      />
      <div className="flex min-h-8 items-center justify-between gap-3 px-1">
        <p className="text-[0.8125rem]/[1.25rem] text-(color:--chatcn-fg-subtle)" aria-live="polite">
          {winner
            ? `Applied attempt ${winner.toUpperCase()} to fix/checkout-flake. The other worktrees were removed.`
            : allDone
              ? "Compare the attempts, then use one."
              : "Three agents work in separate worktrees."}
        </p>
        {allDone ? (
          <Button
            size="sm"
            onClick={() => {
              setWinner(undefined)
              setRetried({})
              setTicks(0)
              setStartedAt(Date.now())
              setRun((r) => r + 1)
            }}
          >
            <RotateCcwIcon />
            Run again
          </Button>
        ) : null}
      </div>
    </div>
  )
}

More by Aaditya

View all
  • Computer View

    Computer View

  • Reasoning

    Reasoning

  • Audio Visualizer

    Audio Visualizer

  • Dither Orb

    Dither Orb

  • Voice Orb

    Voice Orb

More in AI chats

View all
  • AI Actions

    AI Actions

    332
  • Background Inbox

    Background Inbox

    32
  • AI Task List

    AI Task List

    176
  • Color Picker

    Color Picker

    142
  • Streaming Text

    Streaming Text

    20
  • Permission Grant

    Permission Grant

    20
  • AI Suggestions

    AI Suggestions

    18
  • Agent Chat

    Agent Chat

    18

Explore more

Part of chatcn — browse the full library on 21st.dev.