Context
We maintain the AG-UI ↔ ADK middleware (ag-ui-adk, part of ag-ui-protocol/ag-ui). AG-UI is a protocol between agents and frontends; one of its event categories is STEP_STARTED / STEP_FINISHED, which bracket the phases/nodes of a run (the spec recommends stepName = node name).
To emit these from ADK workflows (SequentialAgent / ParallelAgent / LoopAgent / the 2.0 Workflow graph), the middleware infers node boundaries from the runner.run_async event stream using each event's author (node name) and branch (ParallelAgent assigns a distinct par.<sub_agent> per branch via _create_branch_ctx_for_sub_agent). This works cleanly for serial and parallel topologies.
Problem
There is one gap: a ParallelAgent nested inside a LoopAgent. The concurrent branches keep the same author, branch, and invocation_id across iterations, and the flat event stream carries no iteration-boundary signal — so a consumer cannot tell iteration 1 from iteration 2. The events are identical except for their unique id. (Verified on google-adk 2.3.0.)
LoopAgent(max_iterations=2, sub_agents=[ParallelAgent(sub_agents=[p, q])]) emits:
author='p' branch='par.p' invocation_id='e-…' ← iteration 1
author='q' branch='par.q' invocation_id='e-…'
author='p' branch='par.p' invocation_id='e-…' ← iteration 2 (indistinguishable)
author='q' branch='par.q' invocation_id='e-…'
As a result, per-iteration steps cannot be reconstructed for this topology.
ADK already tracks the iteration
LoopAgent._run_async_impl already maintains times_looped, and on the resumable path it even yields a state event carrying it (self._create_agent_state_event(ctx)). It's simply not surfaced on the events produced within an iteration.
Ask (minimal, additive)
Surface the current loop iteration in the event stream, either:
- a read-only field on events produced within a
LoopAgent iteration (e.g. Event.loop_iteration, or in event metadata), or
- a lightweight iteration-boundary marker event emitted at each iteration start (generalizing the existing resumable-path state event to always fire).
This is purely additive and does not change execution behavior.
Why not put the iteration into branch?
That would be convenient for consumers, but branch governs context/event isolation — changing it per iteration could affect how loop iterations share state (the whole point of a loop). So we're explicitly asking to expose the iteration separately from branch.
Motivation
We need this to faithfully emit all AG-UI event types from ADK. STEP_STARTED/STEP_FINISHED is currently the one category the ADK integration can only approximate (for loop-of-parallel). See the AG-UI PR: ag-ui-protocol/ag-ui#2076.
Related
A more general alternative — emitting explicit agent/node lifecycle events in the stream — is filed as the companion issue #6267, which would subsume this. This issue (#6266) is the minimal, lowest-risk subset scoped to loops.
Context
We maintain the AG-UI ↔ ADK middleware (
ag-ui-adk, part of ag-ui-protocol/ag-ui). AG-UI is a protocol between agents and frontends; one of its event categories isSTEP_STARTED/STEP_FINISHED, which bracket the phases/nodes of a run (the spec recommendsstepName = node name).To emit these from ADK workflows (
SequentialAgent/ParallelAgent/LoopAgent/ the 2.0Workflowgraph), the middleware infers node boundaries from therunner.run_asyncevent stream using each event'sauthor(node name) andbranch(ParallelAgentassigns a distinctpar.<sub_agent>per branch via_create_branch_ctx_for_sub_agent). This works cleanly for serial and parallel topologies.Problem
There is one gap: a
ParallelAgentnested inside aLoopAgent. The concurrent branches keep the sameauthor,branch, andinvocation_idacross iterations, and the flat event stream carries no iteration-boundary signal — so a consumer cannot tell iteration 1 from iteration 2. The events are identical except for their uniqueid. (Verified ongoogle-adk 2.3.0.)LoopAgent(max_iterations=2, sub_agents=[ParallelAgent(sub_agents=[p, q])])emits:As a result, per-iteration steps cannot be reconstructed for this topology.
ADK already tracks the iteration
LoopAgent._run_async_implalready maintainstimes_looped, and on the resumable path it even yields a state event carrying it (self._create_agent_state_event(ctx)). It's simply not surfaced on the events produced within an iteration.Ask (minimal, additive)
Surface the current loop iteration in the event stream, either:
LoopAgentiteration (e.g.Event.loop_iteration, or in event metadata), orThis is purely additive and does not change execution behavior.
Why not put the iteration into
branch?That would be convenient for consumers, but
branchgoverns context/event isolation — changing it per iteration could affect how loop iterations share state (the whole point of a loop). So we're explicitly asking to expose the iteration separately frombranch.Motivation
We need this to faithfully emit all AG-UI event types from ADK.
STEP_STARTED/STEP_FINISHEDis currently the one category the ADK integration can only approximate (for loop-of-parallel). See the AG-UI PR: ag-ui-protocol/ag-ui#2076.Related
A more general alternative — emitting explicit agent/node lifecycle events in the stream — is filed as the companion issue #6267, which would subsume this. This issue (#6266) is the minimal, lowest-risk subset scoped to loops.