Fully automated agents are fast, but some decisions carry enough risk that a human checkpoint is worth the wait. This recipe shows how to interrupt an agent loop at a designated checkpoint, surface the pending decision to a human reviewer, and resume execution after approval — all without losing the conversation context that led to that point.
The loop runs normally until the model signals it wants to take a high-stakes action. You intercept that signal, serialize the current state, and block until an approval arrives. The model never sees the pause; from its perspective the conversation simply continues with a user message that says "approved" or "rejected."
A minimal checkpoint looks like this:
type CheckpointState = {
conversationId: string;
messages: { role: string; content: string }[];
pendingAction: string;
requestedAt: string;
};
Store the state in a database or cache keyed by conversationId, then notify the reviewer through your own notification channel (email, Slack, a webhook on your service).
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://opendunes.com/api/v1",
apiKey: process.env.OPENDUNES_API_KEY,
});
const CHECKPOINT_SIGNAL = "CHECKPOINT_REQUIRED:";
async function runAgentLoop(initialMessages: { role: string; content: string }[]) {
const messages = [...initialMessages];
while (true) {
const response = await client.chat.completions.create({
model: "anthropic/claude-opus-4.8",
messages,
max_tokens: 1024,
});
const assistantMessage = response.choices[0].message;
messages.push({ role: "assistant", content: assistantMessage.content ?? "" });
const content = assistantMessage.content ?? "";
if (content.startsWith(CHECKPOINT_SIGNAL)) {
const pendingAction = content.slice(CHECKPOINT_SIGNAL.length).trim();
const approval = await waitForApproval(messages, pendingAction);
if (!approval) {
messages.push({ role: "user", content: "Action rejected. Do not proceed. Summarize what you planned to do and stop." });
} else {
messages.push({ role: "user", content: "Approved. Continue." });
}
continue;
}
return content;
}
}
Tell the model exactly when to raise a checkpoint and what format to use:
const systemPrompt = `You are a deployment assistant.
When you are about to take an irreversible action (delete, deploy, send, charge),
stop and output exactly: CHECKPOINT_REQUIRED: <one-sentence description of the action>.
Do nothing else until you receive "Approved." or "Rejected."`;
Keep the trigger criteria specific. Vague instructions like "pause for risky actions" leave too much ambiguity and produce inconsistent checkpoints.
The simplest implementation polls a database row:
async function waitForApproval(
messages: { role: string; content: string }[],
pendingAction: string,
timeoutMs = 30 * 60 * 1000,
): Promise<boolean> {
const conversationId = crypto.randomUUID();
const deadline = Date.now() + timeoutMs;
await saveCheckpoint({ conversationId, messages, pendingAction, requestedAt: new Date().toISOString() });
await notifyReviewer(conversationId, pendingAction);
while (Date.now() < deadline) {
const decision = await pollDecision(conversationId);
if (decision === "approved") return true;
if (decision === "rejected") return false;
await new Promise((r) => setTimeout(r, 3000));
}
await resolveCheckpoint(conversationId, "timeout");
return false;
}
For production use, replace the poll loop with a queue or a long-poll endpoint on your own service so the agent process can sleep instead of spinning.
A loop that runs 10 turns with anthropic/claude-opus-4.8 at roughly 500 tokens per turn costs approximately 12–18 DA in total, depending on output length. Checkpoints themselves add no extra tokens — only the approval message (~5 tokens) is injected.
- Context length: Long loops accumulate messages. Summarize older turns if the conversation approaches the model's context limit.
- Parallel loops: Each conversation needs its own state record; never share
messages arrays across concurrent agents.
- Idempotency: If the agent is resumed after a crash, replay detection (checking
conversationId in your store) prevents double-execution.