Summary
agents-cli eval generate fails on every case with Malformed agent event: missing content. as soon as the evaluated agent (root or nested) has a before_agent_callback set — even on an otherwise trivial, working agent. agents-cli run (interactive) handles the same SSE event fine; the problem is specific to the eval generate code path.
Root cause
google/agents/cli/eval/cmd_generate.py::_parse_sse_event requires a content field on every event received over /run_sse:
def _parse_sse_event(event: dict) -> evals_types.AgentEvent:
...
missing = [field for field in ("author", "content") if not event.get(field)]
if missing:
raise ValueError(f"Malformed agent event: missing {' and '.join(missing)}.")
return evals_types.AgentEvent(
author=event.get("author"), content=event.get("content")
)
ADK legitimately emits an event with no content when a before_agent_callback mutates session state: a pure bookkeeping event carrying actions.stateDelta (whatever the callback wrote), with author present but no top-level content. This is the first event of the SSE stream, before any model response. Example, captured directly from /run_sse:
data: {"invocationId":"e-...","author":"root_agent","actions":{"stateDelta":{"foo":"bar"},"artifactDelta":{},"requestedAuthConfigs":{},"requestedToolConfirmations":{}},"nodeInfo":{"path":"root_agent@1"},"id":"...","timestamp":...}
_parse_sse_event raises on this event, which aborts inference for the entire case.
Minimal reproduction (independent of any specific project)
Created via agents-cli scaffold create <name> --adk -y (default scaffold, no project-specific changes). Only change to the generated app/agent.py: add a before_agent_callback that sets a single state variable.
async def _init_state(callback_context) -> None:
if "foo" not in callback_context.state:
callback_context.state["foo"] = "bar"
root_agent = Agent(
name="root_agent",
model=Gemini(model=MODEL, retry_options=types.HttpRetryOptions(attempts=3)),
instruction="You are a helpful AI assistant.",
tools=[get_weather],
before_agent_callback=_init_state, # <-- only change
)
tests/eval/datasets/repro.json:
{
"eval_cases": [
{
"eval_case_id": "hello",
"prompt": {"role": "user", "parts": [{"text": "Hello, what can you help me with?"}]}
}
]
}
agents-cli eval generate --dataset tests/eval/datasets/repro.json -o /tmp/repro_traces
With before_agent_callback:
[generate] case[0] FAILED: Malformed agent event: missing content.
Inference summary: 0/1 succeeded, 1 failed.
Error: Inference failed: 0 of 1 cases succeeded.
Control run (identical code, only before_agent_callback=_init_state, removed):
[generate] case[0] done
Traces saved to /tmp/repro_traces
Isolated via bisection
The following were each tested individually against the same scaffold baseline (real Gemini API, gemini-3.6-flash) and all passed eval generate successfully:
sub_agents=[<plain Agent>], unused
sub_agents=[<plain Agent>] + actual delegation via transfer_to_agent
tools=[AgentTool(<plain Agent>)] + actual tool call
sub_agents=[<SequentialAgent>], unused, combined with AgentTool + tools
sub_agents=[<Agent with output_schema>], unused
Only adding before_agent_callback (alone, without any of the above) reliably triggers the failure — this is the minimal sufficient condition.
Expected vs. actual
- Expected:
agents-cli eval generate works for any agent with before_agent_callback (a documented, common ADK pattern for state initialization) just as it does without one.
- Actual: The very first event of the stream aborts the entire case before any model response is even generated. Affects every case in the dataset equally (0 of N succeed).
Environment
| Component |
Version |
agents-cli |
1.4.0 (also reproducible on 1.3.1 — same missing check present unchanged) |
google-adk |
2.7.0 |
| Python |
3.13.14 |
| Model |
gemini-3.6-flash (scaffold default), reproduced independent of model |
| OS |
macOS 26.5.2 (Darwin) |
| Provider |
Gemini API / AI Studio (GEMINI_API_KEY), no Vertex AI |
Suggested fix
_parse_sse_event should recognize events with no content but a valid author and (optionally) actions.stateDelta/actions.endOfAgent as pure bookkeeping/callback events, and either (a) skip them (they carry no AgentEvent-relevant model/tool content) or (b) construct the AgentEvent with an empty/synthetic content instead of raising. Alternatively, make content optional in AgentEvent construction if the downstream data structure allows it.
Impact
This blocks agents-cli eval generate — and by extension eval run, which calls generate internally — for any agent using the documented before_agent_callback state-initialization pattern, which is not an uncommon setup.
Summary
agents-cli eval generatefails on every case withMalformed agent event: missing content.as soon as the evaluated agent (root or nested) has abefore_agent_callbackset — even on an otherwise trivial, working agent.agents-cli run(interactive) handles the same SSE event fine; the problem is specific to theeval generatecode path.Root cause
google/agents/cli/eval/cmd_generate.py::_parse_sse_eventrequires acontentfield on every event received over/run_sse:ADK legitimately emits an event with no
contentwhen abefore_agent_callbackmutates session state: a pure bookkeeping event carryingactions.stateDelta(whatever the callback wrote), withauthorpresent but no top-levelcontent. This is the first event of the SSE stream, before any model response. Example, captured directly from/run_sse:_parse_sse_eventraises on this event, which aborts inference for the entire case.Minimal reproduction (independent of any specific project)
Created via
agents-cli scaffold create <name> --adk -y(default scaffold, no project-specific changes). Only change to the generatedapp/agent.py: add abefore_agent_callbackthat sets a single state variable.tests/eval/datasets/repro.json:{ "eval_cases": [ { "eval_case_id": "hello", "prompt": {"role": "user", "parts": [{"text": "Hello, what can you help me with?"}]} } ] }agents-cli eval generate --dataset tests/eval/datasets/repro.json -o /tmp/repro_tracesWith
before_agent_callback:Control run (identical code, only
before_agent_callback=_init_state,removed):Isolated via bisection
The following were each tested individually against the same scaffold baseline (real Gemini API,
gemini-3.6-flash) and all passedeval generatesuccessfully:sub_agents=[<plain Agent>], unusedsub_agents=[<plain Agent>]+ actual delegation viatransfer_to_agenttools=[AgentTool(<plain Agent>)]+ actual tool callsub_agents=[<SequentialAgent>], unused, combined withAgentTool+ toolssub_agents=[<Agent with output_schema>], unusedOnly adding
before_agent_callback(alone, without any of the above) reliably triggers the failure — this is the minimal sufficient condition.Expected vs. actual
agents-cli eval generateworks for any agent withbefore_agent_callback(a documented, common ADK pattern for state initialization) just as it does without one.Environment
agents-climissingcheck present unchanged)google-adkgemini-3.6-flash(scaffold default), reproduced independent of modelGEMINI_API_KEY), no Vertex AISuggested fix
_parse_sse_eventshould recognize events with nocontentbut a validauthorand (optionally)actions.stateDelta/actions.endOfAgentas pure bookkeeping/callback events, and either (a) skip them (they carry noAgentEvent-relevant model/tool content) or (b) construct theAgentEventwith an empty/syntheticcontentinstead of raising. Alternatively, makecontentoptional inAgentEventconstruction if the downstream data structure allows it.Impact
This blocks
agents-cli eval generate— and by extensioneval run, which callsgenerateinternally — for any agent using the documentedbefore_agent_callbackstate-initialization pattern, which is not an uncommon setup.