Pause a generic interrupt after the model has a draft and before a tool runs.
Each agent iteration has four boundaries. You pick one with a ctx.phase guard in onInterruptBoundary.
This page assumes that you have a definition from Generic Interrupts.
| Phase | When it runs | Use it to |
|---|---|---|
| beforeModel | After onConfig for this iteration, before the adapter call | Get a choice from the user before you pay for tokens |
| afterModel | After the model stream ends, before tools run | Review the draft that the model wrote |
| beforeTools | After the assistant tool-call message is in messages, before the tools run | Allow or block side effects |
| afterTools | After the tools finish and their results are in messages | Audit tool results before the model reads them |
Return { interrupts } to pause. Return nothing to let the run continue. This example pauses at beforeModel on the first iteration only:
import type { ChatMiddleware } from '@tanstack/ai'
import { reviewPlan } from './interrupts'
export const requestReview: ChatMiddleware<unknown, typeof reviewPlan> = {
name: 'request-review',
onInterruptBoundary(ctx) {
if (ctx.phase !== 'beforeModel') return
if (ctx.parentRunId) return
if (ctx.iteration !== 0) return
return {
interrupts: [
reviewPlan.interrupt({
key: 'initial-plan',
reason: 'review-required',
message: 'Review the proposed plan.',
payload: {
title: 'Release plan',
changes: ['Add search', 'Add tests'],
},
}),
],
}
},
}Keep the ctx.parentRunId guard from step 2. The continuation is a new chat() call, and every boundary hook runs again. If the hook returns the same request, the run pauses again. ctx.parentRunId is set only on a continuation, so the guard limits the pause to the original request.
Ask for a plan, an audience, or a locale before the model writes:
The model wrote text, and a human must accept it before tools run:
The model asked for tools, and nothing has run yet:
This is close to tool approval. Use a generic interrupt in these cases:
The tools ran, and a human must see the output before the model uses it:
The React chat example has a playground for all four phases.
Every hook gets the same ChatMiddlewareContext. Its contents change from phase to phase.
The fields that you use most:
Other fields:
A change to ctx.messages does not change the engine config.
onConfig for this iteration has already run. Prompts, tools, messages, and modelOptions hold the values after that merge.
The model stream is complete.
If you need tool names or arguments, use beforeTools. You can also watch onChunk during modelStream.
The engine has added the assistant message with toolCalls to messages. The tools have not run. Read the proposed calls here, before any side effect.
The tools have run. Result messages with role: 'tool' are in messages, and onToolPhaseComplete has already run. Read the results here, before the next model turn.