10 KiB
Self-Describing Interrupt Contracts
Date: 2026-07-01
Status: Implemented. This document is the live interrupt contract.
Related:
- Current roadmap
- Persisted run/resume contract
- Workflow console, agent demo, and defense presentation
- Thesis system design
Purpose
Make human-in-the-loop workflow pauses self-describing. A client inspecting an interrupted run should know:
- what kind of interrupt occurred;
- what payload was sent to the user;
- which resume outcomes are valid;
- what JSON shape a resume payload must have;
- how to validate the response before sending
resume_run.
This is required for the Workflow Console and useful for CLI, JSON-RPC, MCP, and agent clients. It prevents every client from needing workflow-specific code just to render and answer an approval step.
Implemented Contract
The core interrupt model now exposes a complete public pause/resume contract.
InterruptNodestoreskind, request bindings, resume bindings, and resume outcomes, plusrequest_schemaandresume_schema.- Runtime builds an
InterruptRequestwith id, frame id, node id, kind, payload, route, resumability, outcomes, request schema, resume schema, and atypedmarker. workflow.runs.inspectserializes that persisted interrupt request.- Runtime validates request payloads before pausing and validates resume payloads against the persisted pause-time schema before mutating state.
This is close to the LangGraph-style interrupt(value) and Command(resume=...)
pattern: flexible and simple, but the response contract is mostly app
convention. wf should keep the flexibility while making the contract explicit.
Design Summary
JSON Schema contracts are carried from interrupt nodes through persisted run inspection and resume validation.
sequenceDiagram
participant Runtime
participant Store
participant Client
Runtime->>Runtime: Build request payload from interrupt.request bindings
Runtime->>Runtime: Validate payload against request_schema
Runtime->>Store: Persist interrupted run checkpoint
Client->>Store: inspect_run(run_id)
Store-->>Client: payload, outcomes, request_schema, resume_schema
Client->>Client: Render form from resume_schema
Client->>Store: resume_run(run_id, payload, outcome)
Store->>Runtime: Validate resume payload against resume_schema
Runtime->>Runtime: Apply interrupt.resume bindings
Core Model
InterruptNode carries two schema fields:
class InterruptNode(BaseModel):
id: str
type: Literal["interrupt"]
kind: str
request: list[InputBinding] = Field(default_factory=list)
resume: list[OutputBinding] = Field(default_factory=list)
outcomes: list[str] = Field(default_factory=lambda: ["submitted"])
request_schema: JsonSchemaObject = Field(default_factory=_object_schema)
resume_schema: JsonSchemaObject = Field(default_factory=_object_schema)
The default schema is a permissive object schema:
{
"type": "object",
"additionalProperties": true
}
This preserves existing persisted workflow documents and current examples. New authoring helpers should emit explicit schemas whenever they can.
Runtime Request Contract
When an interrupt node executes:
- Build the request payload using existing
requestbindings. - Validate the built payload against
request_schema. - If validation fails, fail the run with a structured execution error. This is a workflow contract bug, not a human pause.
- Persist the interrupted run with an
InterruptRequestthat includes:- interrupt id;
- frame id;
- node id;
- kind;
- payload;
- resumable flag;
- child interrupt route when present;
- outcomes;
- request schema;
- resume schema;
typed=truewhen the schema was explicitly declared.
The runtime should not infer request schemas from payload values. Inference would make the public contract depend on one execution instance rather than the workflow definition.
Resume Contract
Before mutating run state, resume_run validates:
- the run is currently interrupted;
- the selected resume outcome is declared by the interrupt node;
- the resume payload satisfies the interrupt node's
resume_schema.
Only after those checks pass should the runtime apply resume bindings and
advance the graph. Invalid resume payloads must not consume the interruption or
write partial state changes.
Resume validation should use the same JSON Schema validation helper used by workflow validation where possible. Do not hand-roll schema validation.
Public Inspect Shape
workflow.runs.inspect should expose the interrupt contract directly:
{
"status": "interrupted",
"interrupt": {
"id": "interrupt:review",
"frame_id": "frame_1",
"node_id": "review",
"kind": "issue_review",
"payload": {
"report_markdown": "# lda.chat Thesis And Project Readiness Report",
"proposed_issues": []
},
"outcomes": ["submitted", "cancelled"],
"request_schema": {
"type": "object",
"properties": {
"report_markdown": {"type": "string"},
"proposed_issues": {"type": "array"}
},
"required": ["report_markdown", "proposed_issues"]
},
"resume_schema": {
"type": "object",
"properties": {
"approved": {"type": "boolean"},
"selected_issue_ids": {
"type": "array",
"items": {"type": "string"}
},
"comment": {"type": "string"}
},
"required": ["approved", "selected_issue_ids"],
"additionalProperties": false
},
"typed": true,
"resumable": true
}
}
The schema snapshot is part of the interrupted run's public contract. A client should not need to reload the mutable artifact or inspect Python source code to answer the interrupt.
Authoring Contract
Raw workflow plans may specify request_schema and resume_schema directly on
interrupt nodes.
Authoring helpers may provide convenience wrappers:
- direct JSON Schema dictionaries;
- Pydantic models converted through
model_json_schema(); - a small built-in helper for common approval forms.
The stored workflow should still contain ordinary JSON Schema dictionaries. Python type objects must not leak into persisted artifacts.
The first planned demo uses:
kind="issue_review";- a request schema containing rendered report markdown and proposed issues;
- a resume schema containing approval, selected issue ids, and optional comment;
- outcomes
submittedandcancelled.
Validation Contract
Workflow validation should catch static interrupt contract errors:
request_schemaandresume_schemamust be valid JSON Schema documents;- schemas must describe JSON objects for V1;
- request binding targets must be valid local payload paths;
- resume binding sources must be valid local payload paths;
- resume binding destinations must write to declared state fields;
- every declared resume outcome that is routed must be known by the node.
V1 does not need full static proof that every request binding output satisfies
request_schema. Runtime validation still protects execution. Static validation
can become stricter later if it remains useful and maintainable.
CLI And RPC Behavior
CLI and RPC should surface schema failures as ordinary structured product errors, not Python tracebacks.
Recommended behavior:
wf run inspect <run_id>includes the interrupt schemas in JSON output.- Compact/text output summarizes
kind, valid outcomes, and required resume fields. wf run resume <run_id> --input ...validates before mutation and reports missing/invalid resume fields.wf explaingains cards for interrupt request-schema and resume-schema validation failures if new diagnostic codes are added.
Compatibility
Existing workflows without explicit schemas remain valid.
- Missing
request_schemaorresume_schemais treated as a permissive object schema. - Public inspect can include
typed=falsefor legacy interrupts. - New authoring helpers and examples should emit explicit schemas.
- No migration is required for stored artifacts.
Compatibility here is justified because raw workflow plans and saved artifacts are a documented external/persisted contract.
Workflow Console Usage
The Workflow Console uses this contract to render generic human approval forms:
- inspect the interrupted run;
- read
interrupt.kindandinterrupt.resume_schema; - choose a kind-specific renderer when available;
- fall back to a generic JSON Schema form;
- validate locally for user feedback;
- send the resume payload through JSON-RPC;
- display server-side validation errors if the schema check still fails.
The console may provide a custom renderer for issue_review, but the custom
renderer must still emit a payload accepted by resume_schema.
Non-Goals
- New interrupt execution semantics.
- Multi-user approval workflow.
- Authentication, authorization, or audit identity.
- Scheduling or event-triggered resumes.
- A full JSON Schema UI standard.
- Semantic compatibility analysis between schema revisions.
- LangGraph API compatibility.
Implementation Slices
- Extend core models and persistence-safe serialization.
- Validate request and resume schemas using the existing schema validation library path.
- Carry interrupt schemas and outcomes into
InterruptRequestandworkflow.runs.inspect. - Validate resume payloads before state mutation.
- Add authoring/builder helpers and schema discovery output.
- Update CLI docs, skills, and examples.
- Add the
issue_reviewinterrupt to the preparedlda.chatreport workflow.
Test Plan
- Core model accepts explicit schemas and defaults legacy nodes.
- Invalid interrupt schemas are rejected by workflow validation.
- Runtime validates a request payload before persisting interruption.
- Resume rejects invalid payloads without mutating run state.
- Resume rejects unknown outcomes before mutating run state.
workflow.runs.inspectincludes schemas, outcomes, typed flag, and payload.- JSON-RPC and CLI resume return structured errors for invalid payloads.
- Draft/raw-plan compilation preserves interrupt schemas.
- Builder/Pydantic convenience emits plain JSON Schema in saved workflows.
Success Criteria
The contract is complete when an external client can inspect an interrupted run, render a correct form, submit a valid resume payload, and explain invalid responses without reading workflow source code or hard-coding that workflow's interrupt shape.