surprisingly complex Interrupt shit
we found a way tho lets go
This commit is contained in:
@@ -145,6 +145,20 @@ Conditions are structured JSON, not freeform code strings.
|
||||
.type: "join"
|
||||
```
|
||||
|
||||
#### **InterruptNode**
|
||||
|
||||
```text
|
||||
.id
|
||||
.type: "interrupt"
|
||||
.kind // approval | text_input | choice | tool_auth | ...
|
||||
.request_map // graph state/input path -> interrupt payload field
|
||||
.out_map // resume payload field -> graph state path
|
||||
.outcomes[] // ex: submitted, cancelled
|
||||
```
|
||||
|
||||
Interrupt nodes are explicit graph nodes, not arbitrary line-level pauses inside Python code.
|
||||
This keeps pause points visible, typed, traceable, and easier to validate.
|
||||
|
||||
### Edges
|
||||
|
||||
Routing is outcome-based.
|
||||
@@ -313,6 +327,86 @@ Two useful execution entry points fall out of this:
|
||||
|
||||
That keeps the main execution loop small and makes future interrupt/re-invoke semantics easier to model.
|
||||
|
||||
### Interrupts
|
||||
|
||||
Interrupts should be graph-native and typed.
|
||||
|
||||
Preferred model:
|
||||
|
||||
1. A normal business node returns an outcome such as `needs_input`
|
||||
2. The graph routes to an `InterruptNode`
|
||||
3. The interrupt node produces a typed interrupt request
|
||||
4. The runtime marks the run interrupted and surfaces the request externally
|
||||
5. External code supplies a typed resume payload
|
||||
6. The interrupt node maps resume payload back into state
|
||||
7. The graph continues via a declared interrupt outcome such as `submitted` or `cancelled`
|
||||
|
||||
This is deliberately stricter than line-level dynamic interrupts inside arbitrary node code.
|
||||
|
||||
Why:
|
||||
|
||||
- pause points stay visible in the graph
|
||||
- interrupt payloads can be validated by `kind`
|
||||
- traces stay clean
|
||||
- child graph interruption is easier to surface to parent graphs
|
||||
- `foreach` and nested execution are less likely to become cursed
|
||||
|
||||
#### Interrupt request shape
|
||||
|
||||
At runtime, an interrupt should become a structured request attached to run state.
|
||||
|
||||
```text
|
||||
InterruptRequest
|
||||
.id
|
||||
.node_id
|
||||
.kind
|
||||
.payload
|
||||
.resumable
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `kind` chooses the interrupt contract
|
||||
- `payload` is still JSON-like, but should be typed by `kind`
|
||||
- v1 should allow only one active interrupt per run at a time
|
||||
- the whole run pauses in v1, even if the interrupt originated inside a child graph or future foreach frame
|
||||
|
||||
#### Resume semantics
|
||||
|
||||
Resume should not jump back into the middle of arbitrary Python code.
|
||||
|
||||
Instead:
|
||||
|
||||
- resume data is delivered to the interrupt node
|
||||
- the interrupt node maps resume fields back into state through explicit `out_map`
|
||||
- graph routing continues normally from that node
|
||||
|
||||
This means the primary interrupt design is node-based, not line-based.
|
||||
|
||||
#### Child graph behavior
|
||||
|
||||
If a child graph interrupts:
|
||||
|
||||
- the parent run is considered interrupted in v1
|
||||
- the parent only needs to know that the child graph node interrupted
|
||||
- the child interrupt payload should still be surfaced to the external caller/UI
|
||||
|
||||
#### Foreach compatibility
|
||||
|
||||
Interrupt design must be compatible with future foreach support.
|
||||
|
||||
Bad designs to avoid:
|
||||
|
||||
- one anonymous global interrupt payload with no origin
|
||||
- treating interrupt like a normal business outcome only
|
||||
- resuming from arbitrary instruction pointers
|
||||
|
||||
Better design:
|
||||
|
||||
- interrupt belongs to a specific execution frame / node
|
||||
- v1 may still pause the whole run
|
||||
- future foreach support should record which iteration/frame produced the interrupt
|
||||
|
||||
### Retry
|
||||
|
||||
Default retry behavior should be strict.
|
||||
|
||||
@@ -276,6 +276,70 @@ Default retry behavior should be strict and deterministic.
|
||||
- failfast is the default engine behavior
|
||||
- more permissive collection behavior should be opt-in at graph level, especially in `foreach`
|
||||
|
||||
### Interrupt semantics
|
||||
|
||||
Interrupt should primarily be a graph node, not a random Python line.
|
||||
|
||||
Preferred shape:
|
||||
|
||||
- business node returns something like `needs_input`
|
||||
- graph routes to an `InterruptNode`
|
||||
- interrupt node raises/surfaces a typed interrupt request
|
||||
- runtime marks run interrupted
|
||||
- caller provides resume payload later
|
||||
- interrupt node maps resumed payload back into state
|
||||
- graph continues from declared next outcome
|
||||
|
||||
This is cleaner than loose line-level interrupt for this engine.
|
||||
|
||||
Why:
|
||||
|
||||
- graph can see interrupt points
|
||||
- planner can reason about them
|
||||
- trace is cleaner
|
||||
- validation is easier
|
||||
- child graphs and future foreach are less cursed
|
||||
|
||||
Interrupt node shape idea:
|
||||
|
||||
```text
|
||||
InterruptNode
|
||||
.id
|
||||
.type: "interrupt"
|
||||
.kind
|
||||
.request_map // input/state -> interrupt payload
|
||||
.out_map // resume payload -> state
|
||||
.outcomes[] // submitted, cancelled, ...
|
||||
```
|
||||
|
||||
Interrupt request runtime shape:
|
||||
|
||||
```text
|
||||
InterruptRequest
|
||||
.id
|
||||
.node_id
|
||||
.kind
|
||||
.payload
|
||||
.resumable
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- one active interrupt per run in v1
|
||||
- whole run pauses in v1
|
||||
- if child graph interrupts, parent sees child graph node interrupted
|
||||
- external caller/UI should still receive the child interrupt payload
|
||||
- resume should continue from interrupt node semantics, not arbitrary instruction pointer
|
||||
|
||||
Kinds are explicit and expandable:
|
||||
|
||||
- `approval`
|
||||
- `text_input`
|
||||
- `choice`
|
||||
- `tool_auth`
|
||||
|
||||
Generic now, specialized later is easy because `kind` already exists.
|
||||
|
||||
### Reuse and composition
|
||||
|
||||
- workflow input/output separation makes a workflow reusable as a node
|
||||
|
||||
Reference in New Issue
Block a user