Elements

67 / 122 · Agents

Handoff

Control passing between agents, with the reason and what came along.

TriageMaintainer

Triage reproduced the report and narrowed it to the converter, so the fix goes to the agent that can write and verify a patch.

carried overThe failing test and its outputThe two files the reader already narrowed to

Installation

npx shadcn@latest add "@assistant-ui/elements-agent-handoff"

Usage

import { AgentHandoff } from "@/components/elements/agent-handoff";

<AgentHandoff from="Triage" to="Maintainer" reason={reason} carried={carried} settled />

Props

from*stringAgent giving up control.
to*stringAgent taking it.
reason*stringWhy the baton moved. Without it a handoff reads as an unexplained jump.
carried*string[]Context that travelled across. Pass an empty array to hide the section.
settled*booleanCools the arrow and the receiving badge once the new agent is underway.
classNamestringExtra classes merged onto the root.

Source

agent-handoff.tsx
"use client";

import { ArrowRightIcon, BotIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { field, mono } from "./surfaces";

export function AgentHandoff({
  from,
  to,
  reason,
  carried,
  settled,
  className,
}: {
  from: string;
  to: string;
  reason: string;
  carried: readonly string[];
  settled: boolean;
  className?: string;
}) {
  return (
    <div className={cn("flex w-full max-w-sm flex-col gap-2", className)}>
      <div className="flex items-center gap-2">
        <span
          className={cn(
            field,
            "text-foreground/45 flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs transition-opacity duration-500",
            settled && "opacity-45",
          )}
        >
          <BotIcon className="size-3" />
          {from}
        </span>

        <ArrowRightIcon
          className={cn(
            "size-3.5 shrink-0 transition-colors duration-500",
            settled ? "text-foreground/25" : "text-blue-500 dark:text-blue-400",
          )}
        />

        <span
          className={cn(
            "flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs transition-colors duration-500",
            settled
              ? cn(field, "text-foreground/80")
              : "bg-blue-500/12 text-blue-700 dark:bg-blue-400/15 dark:text-blue-300",
          )}
        >
          <BotIcon className="size-3" />
          {to}
        </span>
      </div>

      <p className="text-foreground/55 text-xs leading-relaxed">{reason}</p>

      {carried.length > 0 && (
        <div className="flex flex-col gap-1">
          <span className={cn(mono, "text-foreground/30")}>carried over</span>
          {carried.map((item) => (
            <span
              key={item}
              className="text-foreground/60 border-foreground/12 border-s ps-2.5 text-xs leading-relaxed"
            >
              {item}
            </span>
          ))}
        </div>
      )}
    </div>
  );
}