Core Concepts
Human Handoff
Let a person take over the browser when needed, then return control to the AI agent without losing the workflow state.
Some workflows should pause for a person.
Common examples:
- login
- approval
- verification
- account-specific actions the AI agent should not guess through
Banata supports this through human handoff.
What handoff means
Handoff does not create a separate workflow.
Instead:
- the AI agent pauses
- the person takes over the same browser
- the same sandbox keeps the same files and context
- control can return to the agent afterward
Requesting handoff
ts
await sandbox.requestHumanHandoff({
reason: "approval",
message: "Please review the page and continue manually.",
resumePrompt:
"After the human finishes, continue from the current page and summarize the result.",
});Response shape:
ts
{
ok: boolean;
handoff: {
requestId: string;
status: "pending" | "accepted" | "completed" | "cancelled" | "expired";
reason: "mfa" | "captcha" | "approval" | "login_failed" | "ambiguous_ui" | "file_download" | "custom";
message: string;
requestedBy: "agent" | "worker" | "sdk" | "dashboard";
controller?: string;
resumePrompt?: string;
note?: string;
requestedAt: number;
acceptedAt?: number;
completedAt?: number;
expiresAt?: number;
updatedAt: number;
};
preview: {
status: string;
controlMode?: "ai" | "human" | "shared";
controller?: string;
updatedAt?: number;
} | null;
pairedBrowser?: {
previewUrl?: string;
controlMode?: "ai" | "human" | "shared";
controller?: string;
handoffRequestedAt?: number;
updatedAt?: number;
} | null;
}The important fields are:
handoff.reasonhandoff.messagehandoff.resumePrompthandoff.statuspreview.controlMode
In the normal flow:
- before handoff:
controlModeisai - during handoff:
controlModeisshared - after handoff completes:
controlModereturns toai
Accepting handoff
ts
await sandbox.acceptHumanHandoff({
controller: "dashboard-user",
});This returns the same core handoff object, but now:
handoff.statusshould beacceptedpreview.controlModeshould still beshared
Giving control back
ts
await sandbox.completeHumanHandoff({
controller: "dashboard-user",
returnControlTo: "ai",
runResumePrompt: true,
});Response shape:
ts
{
ok: boolean;
handoff: {
requestId: string;
status: "completed";
reason: string;
message: string;
requestedBy: string;
requestedAt: number;
completedAt?: number;
updatedAt: number;
};
preview: {
status: string;
controlMode?: "ai" | "human" | "shared";
} | null;
pairedBrowser?: {
previewUrl?: string;
controlMode?: "ai" | "human" | "shared";
} | null;
resume?: {
ok: boolean;
sessionId?: string;
response?: unknown;
};
}If runResumePrompt is enabled and there is a stored resume prompt, the resume field tells you that the return-to-agent step was triggered.
After completion, preview.controlMode and pairedBrowser.controlMode should be ai.
When to use handoff
Use it when:
- a step requires human judgment
- login or verification is needed
- a customer or operator must approve something
- the AI agent should resume only after a manual checkpoint
Designing good handoff prompts
A good handoff-driven workflow usually has four parts:
- what the AI agent should do before stopping
- why it must stop
- what the person should do
- what should happen after control returns