Skip to content

Human-in-the-loop

When a run pauses for human input, kaboo-react surfaces the prompt in the UI and resumes the run once the user answers. This works for a single gate and for many gates open at once.

Interrupt model

An interrupt's reason is one of two shapes (InterruptReason):

  • ApprovalReason — approve or reject a gated tool call (type: "approval").
  • FormReason — answer one or more questions (type: "form").

Each open interrupt has a unique id and is resolved independently.

The built-in handler

KabooProvider mounts KabooInterruptHandler for you. CopilotKit can surface several open interrupts at once (e.g. two gated tools called in parallel); the handler renders one card per interrupt and resolves each by its own id, so N parallel gates never wedge.

Inline vs chat-slot placement

  • If an interrupt is anchored to a tool call that is still visible on screen, the prompt renders inline at that tool's position, in chronological order.
  • Otherwise it renders in a fallback slot in the chat.

You don't configure this — it's automatic — but it's why prompts appear "in the right place" rather than floating on top of the turn.

The InterruptBridge

The bridge lets an owning agent card (and the drill-down view) render a prompt inline. Use useInterruptFor(toolCallId) to find the open interrupt anchored to a specific tool call:

import { useInterruptFor } from "@pgege/kaboo-react";

function GateBadge({ toolCallId }: { toolCallId: string }) {
  const interrupt = useInterruptFor(toolCallId);
  return <span>{interrupt ? "Awaiting input" : "OK"}</span>;
}

InterruptBridgeProvider and InterruptBridgePublisher are the lower-level pieces; both are wired for you by KabooProvider + KabooInterruptHandler.

Custom renderers

Override the default approval/form UI per interrupt type via interruptRenderers. A renderer receives InterruptRendererProps (the reason plus onResolve/onCancel):

import { KabooProvider, InterruptRenderer } from "@pgege/kaboo-react";
import type { InterruptRendererProps } from "@pgege/kaboo-react";

function MyApproval(props: InterruptRendererProps) {
  // Wrap or fully replace the default UI.
  return <InterruptRenderer {...props} />;
}

export function App({ agent, threadId }: { agent: string; threadId: string }) {
  return (
    <KabooProvider
      runtimeUrl="/api/copilotkit"
      agent={agent}
      threadId={threadId}
      interruptRenderers={{ approval: MyApproval }}
    >
      <div />
    </KabooProvider>
  );
}