feat(workflow): RunNode WithUseAsOutput for child -> parent output delegation#920
Merged
Conversation
5ddf6e4 to
982aa73
Compare
0089191 to
b39a686
Compare
f864f49 to
94fd0ec
Compare
bddb1a1 to
1156e83
Compare
hanorik
previously approved these changes
Jun 1, 2026
1156e83 to
f6ed1c3
Compare
…tion Workflow-engine support for human-in-the-loop, unified on a single mechanism — history rehydration — matching adk-python (no persisted run-state event, no PendingRequest field). - scheduler: per-event back-pressure handshake (a non-partial function-response is persisted before the node's flow rebuilds the next model request, fixing a non-deterministic re-issue race); pause a node on accumulated Event.LongRunningToolIDs (RequestInput rides on them); stamp NodeInfo.Path = node name on static node events so rehydration can attribute interrupts (dynamic children fold into their static ancestor). - persistence: ReconstructRunState ports adk-python's _reconstruct_node_states + _infer_node_state — per-node scan (interrupts, resolved user responses, schemas, output), status inference (WAITING / PENDING+ResumedInputs re-entry / COMPLETED+Output handoff), backward-edge predecessor input, and schema validation on the surviving (last-wins) response. - resume: single path over the rehydrated state, gated on the current turn's responses for idempotency; already-run handoff successors are skipped (RunState.completed). - state: NodeState.Interrupts + unexported interruptSchemas; RunState.completed; HasWaiting. No PendingRequest, no persisted run-state blob. - workflowagent: detectResume uses ReconstructRunState and surfaces reconstruction (schema-validation) errors. A node may raise multiple interrupts per activation. workflow and workflowagent suites pass with -race.
da03c57 to
0d7d9b1
Compare
467581b to
3f8bec4
Compare
9 tasks
…, Routes) AppendEvent (in-memory) and the database storage layer dropped Event's workflow fields when persisting: the in-memory copy omitted NodeInfo, RequestedInput and Routes, and the database layer never serialized NodeInfo or RequestedInput. History-based resume attributes interrupts by NodeInfo.Path, so losing it broke HITL resume — a RequestInput workflow (e.g. examples/workflow/hitl_simple) would re-prompt instead of continuing after the reply. Persist all three fields in both backends and add round-trip regression tests for each.
wolo-lab
added a commit
that referenced
this pull request
Jun 5, 2026
…ution Add NodeInfo.OutputFor: the node paths an event's Output counts for — the emitter plus any WithUseAsOutput delegating ancestors. A delegating child's single event is stamped OutputFor=[child, parent, ...] and flows up, and the parent no longer re-emits a duplicate terminal output event (full suppression, matching adk-python's _output_delegated + output_for). Resume attributes a descendant's output to its delegating ancestors via OutputFor. Every output event records OutputFor (own path minimum), mirroring adk-python _enrich_event. Built on the temp integration branch (#960 + #920 + #966); rebase onto v2 once those merge.
3f8bec4 to
843dfca
Compare
wolo-lab
added a commit
that referenced
this pull request
Jun 5, 2026
…ution Add NodeInfo.OutputFor: the node paths an event's Output counts for — the emitter plus any WithUseAsOutput delegating ancestors. A delegating child's single event is stamped OutputFor=[child, parent, ...] and flows up, and the parent no longer re-emits a duplicate terminal output event (full suppression, matching adk-python's _output_delegated + output_for). Resume attributes a descendant's output to its delegating ancestors via OutputFor. Every output event records OutputFor (own path minimum), mirroring adk-python _enrich_event. Built on the temp integration branch (#960 + #920 + #966); rebase onto v2 once those merge.
843dfca to
94e6366
Compare
wolo-lab
added a commit
that referenced
this pull request
Jun 8, 2026
…ution Add NodeInfo.OutputFor: the node paths an event's Output counts for — the emitter plus any WithUseAsOutput delegating ancestors. A delegating child's single event is stamped OutputFor=[child, parent, ...] and flows up, and the parent no longer re-emits a duplicate terminal output event (full suppression, matching adk-python's _output_delegated + output_for). Resume attributes a descendant's output to its delegating ancestors via OutputFor. Every output event records OutputFor (own path minimum), mirroring adk-python _enrich_event. Built on the temp integration branch (#960 + #920 + #966); rebase onto v2 once those merge.
Two resume-correctness fixes for dynamic orchestrators and HITL. 1. Cross-resume dedup. A dynamic node body re-runs from the top on resume, so every RunNode before the pause point would re-execute its child. rehydrateCache rebuilds the sub-scheduler's resultByPath from session events (child terminal events carry NodeInfo.Path + Output), so completed children with a stable WithRunID are served from cache. Mirrors adk-python's _rehydrate_from_events / DynamicNodeScheduler. 2. Terminal handoff asker now resumes. Resume only bumped its scheduled counter per scheduled successor, so a single-asker workflow (no successors) wrongly returned ErrNothingToResume. A matched handoff asker now counts as an effective resume itself, gated on answeredThisTurn (from a per-interrupt resolvedCount during rehydration) so a duplicate resume stays an idempotent no-op.
17aa0ce to
6861671
Compare
RunNode gains a per-call WithUseAsOutput() option that promotes a dynamic child's output to the parent dynamic node's terminal Event.Output, suppressing the orchestrator body's own return value. At most one delegating child per parent activation is allowed; a second attempt returns ErrOutputAlreadyDelegated without invoking the child. Builds on the idempotent cache from the previous CL: a WithRunID replay re-honours the delegation but does not re-run the child. BUG=515645490
94e6366 to
558781b
Compare
wolo-lab
added a commit
that referenced
this pull request
Jun 8, 2026
…ution Add NodeInfo.OutputFor: the node paths an event's Output counts for — the emitter plus any WithUseAsOutput delegating ancestors. A delegating child's single event is stamped OutputFor=[child, parent, ...] and flows up, and the parent no longer re-emits a duplicate terminal output event (full suppression, matching adk-python's _output_delegated + output_for). Resume attributes a descendant's output to its delegating ancestors via OutputFor. Every output event records OutputFor (own path minimum), mirroring adk-python _enrich_event. Built on the temp integration branch (#960 + #920 + #966); rebase onto v2 once those merge.
wolo-lab
added a commit
that referenced
this pull request
Jun 8, 2026
…ution Add NodeInfo.OutputFor: the node paths an event's Output counts for — the emitter plus any WithUseAsOutput delegating ancestors. A delegating child's single event is stamped OutputFor=[child, parent, ...] and flows up, and the parent no longer re-emits a duplicate terminal output event (full suppression, matching adk-python's _output_delegated + output_for). Resume attributes a descendant's output to its delegating ancestors via OutputFor. Every output event records OutputFor (own path minimum), mirroring adk-python _enrich_event. Built on the temp integration branch (#960 + #920 + #966); rebase onto v2 once those merge.
hanorik
approved these changes
Jun 8, 2026
hanorik
approved these changes
Jun 8, 2026
wolo-lab
added a commit
that referenced
this pull request
Jun 8, 2026
…ution Add NodeInfo.OutputFor: the node paths an event's Output counts for — the emitter plus any WithUseAsOutput delegating ancestors. A delegating child's single event is stamped OutputFor=[child, parent, ...] and flows up, and the parent no longer re-emits a duplicate terminal output event (full suppression, matching adk-python's _output_delegated + output_for). Resume attributes a descendant's output to its delegating ancestors via OutputFor. Every output event records OutputFor (own path minimum), mirroring adk-python _enrich_event. Built on the temp integration branch (#960 + #920 + #966); rebase onto v2 once those merge.
wolo-lab
added a commit
that referenced
this pull request
Jun 8, 2026
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.
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.
What
RunNodegainsWithUseAsOutput(), which promotes a dynamicchild's output to the parent dynamic node's terminal output. The
value returned by the orchestrator body is discarded in favour of
the delegated child's. At most one delegating child per parent
activation is permitted; a second attempt fails with
ErrOutputAlreadyDelegated.When to use
Use it for thin-dispatcher orchestrators: the parent picks one
child (e.g. by routing logic) and that child's output — including
its streamed tokens, when the child is an
LlmAgent— is theorchestrator's output. Without delegation the parent would have to
wait for the child to finish and re-emit a single string,
collapsing streaming.