Repository navigation
feat(engine)!: run containers as resumable engine frames - #197
Merged
Merged
Conversation
laipz8200
force-pushed
the
laipz8200/frame-aware-execution
branch
from
July 15, 2026 01:53
fac929a to
597a639
Compare
laipz8200
marked this pull request as ready for review
July 15, 2026 05:07
Member
Author
|
The next step in the plan is to directly remove control flow nodes, such as loops and iterations. We will then transform the DSL files into their corresponding container handler implementations within the Node Factory. |
Move loop and iteration execution into the main graph engine frame model. Add frame-aware task routing and event handling so container bodies run without creating nested engines. Extend DSL loading for container node types and add workflow event scenarios for complete graphs.
Rename ReadyQueueProtocol to ReadyQueue and update direct imports.
Add field comments for loop and iteration run contexts.
Keep nested iteration outputs when flatten_output is disabled.
Route LoopNode and IterationNode through the normal BaseNode.run() generator path. Add explicit start/resume ready tasks, WorkerPool-owned suspended invocation storage, and semantic container await/result models so child-frame execution can complete without occupying a worker thread.
Avoid firing GraphEngineLayer.on_node_run_end when a cooperative container node yields an await request. The hook now runs only when the invocation actually completes after resume.
Protect ContainerExecution bookkeeping with a reentrant lock because worker threads start container awaits while the dispatcher completes child frames. Add a regression test that blocks complete_frame and verifies concurrent start_container_await waits.
Keep container execution state in GraphRuntimeState and drive loop and iteration frames through protocol-based handlers and serializable start/resume tasks. Add strict full-graph event checks and HITL resume coverage, including reuse of the same GraphEngine instance after a pause.
laipz8200
force-pushed
the
laipz8200/frame-aware-execution
branch
from
July 20, 2026 03:27
6d85e98 to
aceb267
Compare
41tair
reviewed
Jul 22, 2026
Dispatch container await requests through the event loop so workers remain available to consume child tasks. Start workers before replaying bounded ready queues and consolidate the associated lifecycle and full-graph tests.
Keep child-frame events and execution metrics for diagnostics, but merge response outputs into the parent runtime only when the Loop or Iteration frame succeeds.
Emit frame-aware runtime, graph execution, and ready queue snapshots as version 2. Restore version 1 snapshots by converting legacy node IDs into root-frame start tasks.
laipz8200
enabled auto-merge (squash)
July 28, 2026 04:57
Remove redundant built-in handler locks under the single-dispatcher execution model and share Iteration success and failure result construction.
41tair
approved these changes
Jul 28, 2026
2 of 19 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
!Related Issue
Closes #198
Summary
BaseNode.run()lifecycle while GraphEngine interprets their child graphs as execution frames.mainwith semantic container requests, typed results, and explicit start/resume tasks.GraphRuntimeState, including graph execution, ready and deferred work, container invocations, and child frame snapshots.ContainerHandlerprotocol while keeping Loop and Iteration scheduling and aggregation independent.Architecture
Node lifecycle
Loop and Iteration emit their normal start events and then yield
LoopFrameRequestorIterationFrameRequest. Nodes describe container semantics only; they do not receive frame ids or manage child frames.When a handler produces another frame request or a terminal
ContainerExecutionResult, a worker resumes the original node through its explicit container resume path. The node emits its own progress, success, or failure event and then completes through the standardBaseNodeevent conversion.Scheduling
The ready queue stores a discriminated
ReadyTaskunion:StartTask(frame_id, node_id)starts a node invocation inside a frame.ResumeTask(invocation_id, result)resumes a suspended container invocation.Workers execute both task variants without branching on concrete node types. A container invocation does not occupy a worker while its child frames are running.
Container handlers
GraphEngine registers Loop and Iteration handlers through
ContainerHandler. EventHandler uses the frame kind to select the registered handler through one common path. Each handler independently owns child frame creation, scheduling, output and usage aggregation, break or error state, and terminal result construction.Container handlers produce typed results but do not emit the parent container node terminal events and do not control GraphEngine lifecycle.
Runtime state and pause boundary
GraphRuntimeStateowns all state required to resume execution:The graph definition is intentionally excluded. Restoring execution requires constructing an engine with the same graph definition and a runtime state loaded through
GraphRuntimeState.from_snapshot().When a pause is requested, the dispatcher stops new scheduling, moves queued tasks to deferred storage, and waits for in-flight nodes to finish before child frames are snapshotted. Execution may resume with a newly constructed engine or by calling
run()again on the same paused engine instance. Each engine run resets its transient event buffer and completion signal so events from the paused run are not replayed.Event flow
Child node events use the normal dispatcher and event handler pipeline. They carry
in_loop_idorin_iteration_idand frame index metadata. Container start pseudo-nodes remain internal, while the parent Loop or Iteration node emits the public container lifecycle events.Output storage, parent edge traversal, and parent frame completion happen only after the resumed container node emits its normal node completion event.
Scope
This change covers the existing Loop and Iteration nodes and establishes the protocol needed by future container types. It does not add a generic Subgraph node or WorkflowToolContainer, serialize graph definitions, provide a persistence service, or retain a compatibility path for the old child-engine execution model.
Breaking Changes
LoopNodeandIterationNodeno longer create childGraphEngineinstances or execute child graphs inside_run().ReadyQueueProtocolis renamed toReadyQueue.ReadyTaskunion ofStartTaskandResumeTask; implementations must supportdrain(),dumps(), andloads().GraphRuntimeStatesnapshot data now includes graph execution, deferred scheduling, container runs, and container child frames. Earlier snapshots cannot be loaded into this model.with_runtime_state(graph_runtime_state).NodeExecution.execution_idis required when the domain object is created.NodeExecution.mark_started()no longer accepts an execution id, andNode.ensure_execution_id()is removed.ExecutionCoordinator, the ready queue factory,LLMUsageTrackingMixin, and legacy Iteration exception types from the old execution path are removed.Downstream Adaptation
GraphEngine.run()orgraphon.dsl.loads(...).run()instead of invoking Loop or Iteration execution methods directly.ReadyQueue, persist bothStartTaskandResumeTask, and preserve deferred work across serialization.GraphRuntimeStatedata. Restore them withGraphRuntimeState.from_snapshot()and supply the same graph definition when rebuilding the engine.with_runtime_state()to custom node factories that can create nodes inside container frames.execution_idwhen constructingNodeExecution, replacemark_started(execution_id)withmark_started(), and bind the frame-scoped id to a node before invoking it.ReadyQueue, runtime snapshot, graph event, and container handler interfaces as appropriate.Checklist
!