Skip to content

feat(workflow): park parent on WaitForOutput child with no output#986

Draft
wolo-lab wants to merge 2 commits into
v2from
wolo/wait-for-output
Draft

feat(workflow): park parent on WaitForOutput child with no output#986
wolo-lab wants to merge 2 commits into
v2from
wolo/wait-for-output

Conversation

@wolo-lab

@wolo-lab wolo-lab commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Problem

NodeConfig.WaitForOutput existed but was never honored — no production code read it (the M3 finding in the v2 audit). A child dispatched via RunNode that opts into WaitForOutput but finishes without producing output (e.g. a task-mode LlmAgent that delegated to a sub-agent and has not yet yielded its final answer) returned the zero value, falsely completing the parent instead of waiting for it to finish.

Solution

RunNode (the dynamic sub-scheduler path) now honors WaitForOutput. When a child opts in and emits no output (and is not interrupting via HITL), the parent is parked with ErrNodeInterrupted instead of completing, so it re-runs and re-invokes RunNode once the child can produce output.

  • Output presence is tracked via a hasOutput flag (nil is a valid output, distinct from "no output").
  • waitsForOutput reads the tri-state NodeConfig.WaitForOutput (nil ⇒ engine default false).
  • All parking paths share one pause helper.

Relationship to #1037 (WithRaiseOnWait)

This PR is rebased onto #1037 and the two parking mechanisms are unified into a single, consistent path in runNode:

Both gates run independently and both return through the shared pause helper:

if opts.raiseOnWait && len(pendingLongRunningIDs) > 0 {
    interrupted = true
}
if interrupted { // HITL + raise-on-wait
    return nil, s.pause(name, childPath, runID)
}
if !hasOutput && waitsForOutput(child) { // WaitForOutput
    return nil, s.pause(name, childPath, runID)
}

adk-python parity

adk-python has both, and so do we now:

  • NodeConfig.WaitForOutput mirrors adk-python's wait_for_output node field (_base_node.py) — parks unconditionally on missing output.
  • WithRaiseOnWait (feat: add WithRaiseOnWait option #1037) mirrors adk-python's per-call run_node(raise_on_wait=True) (agents/context.py) — parks on an unresolved long-running tool.

@wolo-lab wolo-lab self-assigned this Jun 8, 2026
@wolo-lab wolo-lab changed the title feat(workflow): park parent when a WaitForOutput child yields no output feat(workflow): park parent on WaitForOutput child with no output Jun 8, 2026
@wolo-lab wolo-lab force-pushed the wolo/wait-for-output branch from 2a4fd24 to 70e6d52 Compare June 8, 2026 20:38
Base automatically changed from wolo/output-for to v2 June 9, 2026 09:34
@wolo-lab wolo-lab force-pushed the wolo/wait-for-output branch 3 times, most recently from ad7ec1f to a26d2de Compare June 15, 2026 14:58
This will mark the node as ErrNodeInterrupted if there's unresolved
function call and no matching function responses on the node.
@wolo-lab wolo-lab requested a review from dpasiukevich June 15, 2026 18:26
RunNode now honors NodeConfig.WaitForOutput: a child that opts in and
finishes without producing output parks the parent with ErrNodeInterrupted
instead of returning the zero value, so it re-runs and re-invokes RunNode
once the child can produce output. Mirrors adk-python's
ctx.run_node(raise_on_wait=True). Tracked via a sawOutput flag (nil is a
valid output) and a waitsForOutput helper reading the tri-state config.
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