feat(workflow): park parent on WaitForOutput child with no output#986
Draft
wolo-lab wants to merge 6 commits into
Draft
feat(workflow): park parent on WaitForOutput child with no output#986wolo-lab wants to merge 6 commits into
wolo-lab wants to merge 6 commits into
Conversation
…sOutput Add NodeInfo.MessageAsOutput: when set and Event.Output is nil, readers derive the node's output from the event's model text. The static and dynamic schedulers both honor it (Output wins, message text is the fallback), mirroring adk-python's _track_event_in_context. Empty text is a valid output, matching python; AgentNode's own empty-text-skips behavior is unchanged. This lets a delegated child whose message IS its output (e.g. an LlmAgent node) promote its text to the parent via WithUseAsOutput, and feeds it to a successor on a normal handoff.
Complete the MessageAsOutput feature to match adk-python's single mechanism, where a node sets Event.Output and NodeInfo.MessageAsOutput together: - Producer: synthesizeAgentOutput now stamps NodeInfo.MessageAsOutput when it derives output from model text (mirrors process_llm_agent_output), so the flag is present in history. - Resume reader: collectNodeOutputs derives a node's output from the model message when an event is flagged MessageAsOutput and carries no explicit Output (mirrors _reconstruct_node_states' use_message_as_output), so a message-as-output node recovers its output on resume. Previously the flag was only read (live, via childEventOutput) but never produced and never consumed on resume.
Adopt adk-python's delegation model, replacing the v2 re-emit approach (revises #920). When a WithUseAsOutput child delegates the parent's output, the child's own event now carries the output up and the parent emits no terminal event (full suppression, mirroring _output_delegated). Previously the child's event was dropped and the parent re-emitted the delegated value, which could not support output_for attribution and lost the value when the orchestrator body returned nil. Also stamp NodeInfo.Path on a child event that set NodeInfo without a Path (e.g. MessageAsOutput), since such events are now emitted up. Tests updated to assert the child event carries the delegated output.
Add OutputFor to session.NodeInfo and stamp it on a delegated child's output event with the whole delegation chain ([childPath, parentPath, ...ancestors]), mirroring adk-python's node_info.output_for / _enrich_event. outputForAncestors is threaded through RunNode (WithUseAsOutput) so a multi-level chain (grandchild -> parent -> top) records every ancestor on the single output event. On resume collectNodeOutputs attributes the output to the static owner of each OutputFor path, so a delegating ancestor recovers its output without re-emitting (output_for parity). Also fixes nested dynamic-node path composition: a child context already carries the full "<parent>/<name>@<runID>" path, so composePath uses it as-is instead of appending the name again (surfaced by multi-level delegation).
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.
2a4fd24 to
70e6d52
Compare
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.
Problem
NodeConfig.WaitForOutputexisted but was never honored — no productioncode read it. A child run via
RunNodethat opts intoWaitForOutputbutfinishes without producing output (e.g. an
LlmAgenttask/chat node thatdelegated to a sub-agent and has not yet yielded its final answer) would
return the zero value, falsely completing the parent instead of waiting.
adk-python handles this with
ctx.run_node(raise_on_wait=True); adk-gohad no equivalent.
Solution
RunNodenow honorsWaitForOutput. When a child opts in and emits nooutput (and is not interrupting), the parent is parked with
ErrNodeInterruptedinstead of completing, so it re-runs and re-invokesRunNodeonce the child can produce output. Mirrors adk-python'sraise_on_wait. Output presence is tracked via ahasOutputflag (nil isa valid output, distinct from "no output"), and a
waitsForOutputhelperreads the tri-state
NodeConfig.WaitForOutput.This is the RunNode slice of
WaitForOutput; the static-scheduler /resume parity is deferred (Go fan-in already works via JoinNode's
predecessor-completion barrier, so no current consumer needs it).