Patterns / Durable approvals
Durable approvals for long-running agents
Agents now run for minutes or hours, and a person may answer an approval long after the request that asked for it is gone. Candor's adapters keep the approval and the run as data, so neither depends on one browser tab or one server process.
With the Claude Agent SDK, permission requests wait in an ApprovalStore. The default is this process's memory; pass your own (a table, a queue) so any instance can take the answer. claudeCanUseTool works anywhere your agent runs, including a workflow engine: it records the request, waits for the answer, and turns it into what the SDK expects with claudePermissionResult. Answer it from your own route, or from the step that signals your workflow.
A ClaudeRunStore keeps a run's events. With detach, the run carries on when the browser goes away; after a reload, useClaudeAgent re-attaches through attachClaudeAgentRun from the run's id. Because the event fold is pure, a finished conversation replays exactly from its stored events (initialEvents).
With the Vercel AI SDK, a WorkflowAgent run streams the same UI messages: a tool that needs approval shows as waiting for as long as the workflow waits, and an approval made automatically is marked as one. The AI SDK's own WorkflowChatTransport re-attaches the stream after a reload.
The components
- Claude Agent SDK serverFreeApprovalStore, ClaudeRunStore, claudeCanUseTool, attachClaudeAgentRun, stop and the permission mode.
- Claude Agent SDKFreeThe pure event fold (replay) and claudePermissionResult for an answer that lands anywhere.
- useClaudeAgentFreeinitialEvents, attach after a reload, onRespond for your own route, runId to save.
- AI SDKFreeWorkflowAgent's approvals, waiting or automatic, in the same messages.
- Permission promptFreeThe waiting request, shown again after a reload.
- Approval requestProA second person's approval, compact for a phone or a notification (Pro).
See it working
More in the examples: Claude Agent SDK
Install
Each component brings in what it depends on. These are free (MIT).
Good to know
- A store makes the answer and the record durable, not the agent: the agent still runs in the process that started it. When the process itself mustn't matter, run the agent in your workflow engine with claudeCanUseTool.
- A late answer to a run that already ended is refused, not lost: answering and closing a request each happen once, whichever comes first.
detachneeds a process that outlives the response, not a serverless function that's frozen when the response ends. Give it anapprovalTimeoutMs, so a request nobody answers expires (denied) instead of holding the run for ever.- Saving the conversation and re-attaching both? Pass the hook's
promptIdto the server'sprompt: the re-attached run then replaces its own events ininitialEventsinstead of adding them twice. - Policy lives on the server:
editablesays which tools a person may edit, andmodes(orallowed) which permission modes a run may switch to. The browser can't widen either.