SIGN IN SIGN UP

fix(adk): export a turn's spans before its terminal event leaves the process (#2786)

A streamed A2A turn on Agent Substrate leaves no trace spans, while the
same turn sent as a unary `SendMessage` does. The UI's chat page streams
its turns, so from the UI every conversation is missing from the trace
backend, while `buf curl` unary turns and the Go e2e trace cases pass.

**Why**

`KAGENT_PRE_RESPONSE_TRACE_FLUSH` (#2176) flushes spans in the HTTP
wrapper in `go/adk/pkg/a2a/server/server.go`, after the gRPC handler
returns. For a unary request that is before the response is written, so
it is early enough. For `SendStreamingMessage` the terminal
`TaskStatusUpdateEvent` is already on the wire by then, the gateway
closes its stream to the runtime as soon as that event arrives
(`go/core/internal/a2agateway/task_run.go`), and Substrate checkpoints
the actor right after. On a kind cluster the atelet log shows the task
completing at 06:02:45.837 and the checkpoint at 06:02:46.013, so the
flush is frozen mid-export and its deadline has passed by the next thaw.
The spans of every streamed turn stay in the snapshot.

**What changes**

- `KAgentExecutor.Execute` ends the invocation span and force-flushes
right before yielding the event that ends the turn (a terminal,
input-required or auth-required state, or an error). That is the only
window a streamed turn has.
- The flush stays behind `KAGENT_PRE_RESPONSE_TRACE_FLUSH`, now read
through `telemetry.PreResponseFlushEnabled()` in both the executor and
the server wrapper. Runtimes that are not frozen after a response keep
relying on the batch exporter's timer.
- The server wrapper's flush stays, since it is the only point the
otelhttp server span can be exported from. Its comment now explains how
the two flushes relate.

**Verification**

- With the runtime image built from this change on a kind cluster, a
`SendStreamingMessage` turn on a fresh conversation landed its three
`gcp.vertex.agent` spans within five seconds. Before the change the same
call produced none, while a unary `SendMessage` on the same instance
produced three.
- Two turns typed into the UI's chat page were traced, and "View traces"
on the answer opened the trace.
- `TestKAgentExecutor_ExportsSpansBeforeYieldingTheTerminalEvent`
asserts the export has happened by the time the terminal event reaches
the consumer, with the invocation span in it. It fails on the current
code. `TestKAgentExecutor_LeavesSpansToTheBatcherWithoutTheOptIn` pins
that nothing is flushed per turn without the variable.
- `go build`, `go vet`, `gofmt` and `go test` over `./adk/...`, and
`make -C go lint`, are clean.

Signed-off-by: David Haifley <david.haifley@solo.io>
D
David Haifley committed
6a6b54ffd1fa0617a6bfc2822bc19c4ed8c5f9da
Parent: f378edf
Committed by GitHub <noreply@github.com> on 9/10/2026, 10:36:00 PM