Skip to main content

8. AG-UI event contract

PhaseEvents the Loco app emitsCopilotKit consumer
StartRUN_STARTED{threadId, runId, protocolVersion}useAgent().agent.isRunning
ProgressSTATE_SNAPSHOT after each graph step, limited to the workflow's state_whitelist. STEP_STARTED/FINISHED and STATE_DELTA (JSON Patch) are optional refinementsagent.state in useAgent
NarrationTEXT_MESSAGE_START/CONTENT/END<CopilotChat>
ToolsTOOL_CALL_START/ARGS/END, then TOOL_CALL_RESULT for server-executed toolsTool-call render hooks
Approval (1.0 client)STATE_SNAPSHOT → MESSAGES_SNAPSHOT → RUN_FINISHED{outcome: {type: "interrupt", interrupts: [{id, reason: "confirmation", message, metadata.ui}]}}useInterrupt → next run carries resume[]
Approval (legacy client)CUSTOM{name: settings.agui.legacy_interrupt_event} + plain RUN_FINISHEDSame hook in legacy mode (verify)
DoneRUN_FINISHED{outcome: {type: "success"}}
FailureRUN_ERROR, also used for an expired, invalid or partial resumeError toast

Two rules come from the protocol and from CopilotKit issue #8080. Use reason: "confirmation" and never a tool-bound interrupt, so the client does not send a synthetic tool message. Always validate the decision on the server, including role and four-eyes checks from the workflow YAML.