Skip to content

feat(agents): opt-in agent lifecycle events (start/finish) in the stream#6269

Open
ferponse wants to merge 1 commit into
google:mainfrom
ferponse:feat/agent-lifecycle-events
Open

feat(agents): opt-in agent lifecycle events (start/finish) in the stream#6269
ferponse wants to merge 1 commit into
google:mainfrom
ferponse:feat/agent-lifecycle-events

Conversation

@ferponse

@ferponse ferponse commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

The runner.run_async stream carries text / tool-call / state / reasoning events, but no explicit "agent X started / finished" boundaries. Consumers that want to bracket work per node/agent (workflows, multi-agent, loops) must infer boundaries from author / branch transitions, which is lossy — e.g. exact parallel-branch end can't be observed, and a ParallelAgent inside a LoopAgent can't be split per iteration (its events are identical across iterations, see #6266).

This adds an opt-in RunConfig.emit_agent_lifecycle_events (default False). When enabled, every agent invocation yields a lightweight lifecycle marker event before its logic and after it — authored by the agent, carrying Event.custom_metadata['agent_lifecycle'] == 'start' | 'finish' (plus the agent's branch).

Fixes #6267.

Why

We're building the AG-UI ↔ ADK middleware (ag-ui-adk), which maps ADK workflow nodes to AG-UI STEP_STARTED / STEP_FINISHED events. Explicit lifecycle events let any consumer bracket nodes exactly, matching what other agent frameworks already emit (CrewAI MethodExecutionStarted/Finished, AWS Strands multiagent_node_start/stop).

How

  • New RunConfig.emit_agent_lifecycle_events: bool = False.
  • BaseAgent.run_async emits a start marker once the agent is actually going to run (i.e. not short-circuited by before_agent_callback) and a paired finish marker on every exit path.
  • Marker events reuse the existing Event.custom_metadata bucket — no Event schema change — and propagate through the tree (run_config is copied into each sub-agent context), so they fire per agent, including per LoopAgent iteration.

Default-off: existing event streams are byte-for-byte unchanged.

Example

LoopAgent(max_iterations=2, sub_agents=[ParallelAgent(sub_agents=[p, q])]) with emit_agent_lifecycle_events=True:

loop [start]
  par [start]
    p [start]  q [start]   …content…   p [finish]  q [finish]
  par [finish]
  par [start]                                             ← iteration 2
    p [start]  q [start]   …content…   p [finish]  q [finish]
  par [finish]
loop [finish]

Every node gets a balanced start/finish per invocation, so iterations and parallel branches are exactly bracketed.

Tests

tests/unittests/agents/test_base_agent.py: added test_run_async_emits_agent_lifecycle_events (start → content → finish) and test_no_agent_lifecycle_events_by_default (backward-compat: stream unchanged). The agents test suite (test_base_agent, test_loop_agent, test_sequential_agent, test_parallel_agent, test_run_config) passes.

Notes

Add RunConfig.emit_agent_lifecycle_events (default False). When enabled, each
agent invocation yields a lightweight lifecycle marker event before its logic
and after it, authored by the agent and carrying
Event.custom_metadata['agent_lifecycle'] == 'start' | 'finish' (plus the
agent's branch). This lets consumers bracket agent/node execution exactly —
including per LoopAgent iteration and parallel-branch boundaries — without
inferring boundaries from author/branch transitions.

Additive and default-off: existing event streams are unchanged. No Event schema
change (it reuses the existing custom_metadata bucket).

Fixes google#6267.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Emit agent/node lifecycle events (start/finish) in runner.run_async

1 participant