Skip to content

Fix df.loop continue_as_new restarting from graph root when loop is not root#228

Open
Copilot wants to merge 2 commits into
mainfrom
copilot/fix-loop-restart-issue
Open

Fix df.loop continue_as_new restarting from graph root when loop is not root#228
Copilot wants to merge 2 commits into
mainfrom
copilot/fix-loop-restart-issue

Conversation

Copilot AI commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

df.loop called continue_as_new inline in the main orchestration, so every new generation restarted from graph.root_node_id — re-executing prefix nodes on every loop iteration. For prefix ~> df.loop(body), the prefix ran once per iteration instead of once per instance.

A sub-orchestration approach was evaluated but is blocked by a duroxide 0.1.29 limitation: ContinueAsNew drops the parent link, so the parent orchestration never receives the child's completion.

Approach: loop-continue sentinel

Instead of calling continue_as_new inside execute_loop_node, the loop returns an upward-propagating sentinel that the root execute() handles:

// execute_loop_node — signals another iteration is needed
Ok(make_loop_continue_signal(node_id, results))

// Root execute() — calls continue_as_new from the correct scope
Ok(result) if is_loop_continue_signal(result) => {
    let (restart_node_id, cont_results) = extract_loop_continue(result);
    ctx.continue_as_new(FunctionInput {
        loop_continuation: Some(LoopContinuation { restart_node_id, results: cont_results }),
        ..
    }).await
}

On the next generation, execute() resumes from restart_node_id with pre-populated results, skipping any prefix nodes.

Changes

  • src/orchestrations/execute_function_graph.rs

    • execute_loop_node: returns __loop_continue__ sentinel instead of calling continue_as_new
    • execute_then_node: propagates sentinel from RIGHT child unchanged; when sentinel comes from LEFT child, promotes restart_node_id to the THEN node itself so the right-side suffix is included in the restart path
    • Root execute(): detects sentinel, calls continue_as_new with LoopContinuation pointing at the loop node (or nearest enclosing THEN for loop ~> suffix)
    • Results serialized via BTreeMap for deterministic JSON key order — required for duroxide replay correctness
    • Removes the dead execute_loop / LOOP_NAME sub-orchestration
  • src/types.rs: adds LoopContinuation struct and loop_continuation: Option<LoopContinuation> to FunctionInput (backward-compatible; absent on initial invocation)

  • tests/e2e/sql/24_nonroot_loop.sql: regression tests for three non-root loop shapes:

    1. prefix ~> loop — prefix runs once, body runs N times
    2. prefix ~> loop ~> suffix — prefix and suffix each run once
    3. Named result from prefix node accessible inside loop body across generations

…ot root

Co-authored-by: pinodeca <32303022+pinodeca@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix df.loop continue_as_new restart behavior Fix df.loop continue_as_new restarting from graph root when loop is not root Jun 11, 2026
Copilot AI requested a review from pinodeca June 11, 2026 23:29
@pinodeca pinodeca marked this pull request as ready for review June 11, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants