Skip to content

LoopAgent: surface the current iteration index in the event stream #6266

Description

@ferponse

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions