LoopAgent: surface the current iteration index in the event stream
- Dominant language
- Python
- Stars
- 21.5k
- Forks
- 4k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 37
Description
### Context
We maintain the **AG-UI ↔ ADK middleware** (`ag-ui-adk`, part of [ag-ui-protocol/ag-ui](https://github.com/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.` 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.
Contributor guide
Assessment
This issue has not been assessed yet.