Typed interrupt/resume contract: should Interrupt/ResumeEntry carry durable-workflow-state, and what resume seam do adapters implement against?
#2225
Replies: 1 comment
|
Thanks for framing this as a protocol-level contract question — we've hit both seams while integrating LangGraph with AG-UI, so sharing what worked (and where we currently diverge from the typed interrupt/resume surface). Resume seam (Q2) — LangGraph adapter experienceOn LangGraph, the durable halt is the checkpoint + Practical lessons that we'd want any shared contract to accommodate:
Durable / governance fields (Q1)Optional additive fields for at-most-once / audit correlation would help. From production use, the minimum that felt load-bearing was:
We're less sure the full judgment lifecycle needs to be first-class on day one; adapters can carry richer policy metadata in optional Explicit divergence from the current typed contractToday our wire path still largely follows the older LangGraph AG-UI convention:
rather than exclusively through the newer first-class That is a conscious interim divergence, not a recommendation. Once adapters have a shared resume seam and field set, we'd prefer to migrate onto the typed types and treat Happy to refine this against a concrete proposed schema if useful. |
Uh oh!
There was an error while loading. Please reload this page.
Background
The protocol already defines the interrupt/resume types (
RunFinishedInterruptOutcome,Interrupt,ResumeEntry,RunAgentInput.resume) as ofag-ui-protocol0.1.19. TodayInterruptcarriesid,reason,message,tool_call_id,response_schema,expires_at,metadata;ResumeEntrycarriesinterrupt_id,status(resolved|cancelled),payload.Two things prompted this:
deferprimitive into this contract, which surfaced that adapters currently have no shared guidance on how to express a resume verdict, so each would invent its own convention.Because
Interrupt/ResumeEntryare shared across every SDK (Python, TypeScript, .NET + protobuf, Go, Kotlin, Ruby) and every adapter, this is a protocol-contract decision, not something an adapter can settle unilaterally. Hence a Discussion rather than a PR.Question 1 — Should the contract carry durable-state / governance fields?
@hegu-1 proposed a lifecycle (
proposed → pending_judgment → approved/rejected/expired → executing → executed/failed) with eachInterruptbinding: originaltool_call_id(exists), canonical argument digest, run/checkpoint id, policy version, expiry (expires_atexists), required approver scope; and eachResumeEntrycarrying a decision id and operation id — plus runtime invariants:These would be additive/optional fields, so they're wire-backward-compatible whenever they land. The real question is governance: do we want these to be first-class, spec'd fields with defined enforcement semantics — so adapters implement uniformly from the start — or do we keep the minimal shape and let it evolve per-need? What's the smallest set worth committing to now?
Question 2 — What is the standard resume seam adapters implement against?
Independent of the fields: when a client sends a
ResumeEntry, how should an adapter apply the verdict to the paused run? Frameworks differ (LangGraph resumes from persisted checkpoint state; the Claude SDK'sdeferreplays a frozen tool call through a hook on--resume; the .NET runtime has yet another model). We should define the contract the client sees (how an interrupt id maps to a resume, whatpayloadmay/may not do — e.g. can it edit args, or does an edit require a new proposal) so behavior is consistent across adapters and clients don't have to special-case each backend.Constraints / non-goals
interrupt_<tool_call_id>, or requiring a client's tool hook to query adapter-private state — these emerged in the Claude bridge exploration and shouldn't become de-facto standard without intent.PreToolUsecallback doesn't re-fire), which independently blocks the Claudedefer-based approach regardless of what we decide here.Ask
Maintainer direction on Q1 and Q2, and input from adapter authors on how each framework's native halt/resume would map to the chosen contract. Once there's a shape, the Claude bridge (#2181) and any other adapter can implement against it.
All reactions