Failure
ADK validates eagerly, names every exception with a stable error code, and does not swallow errors. The runner's behavior on failure is mechanical: validation throws at the seam where the bad input arrived; pipeline failures surface as error events on the observability bus; dispatch failures end the dispatch with dispatchEnd.status: 'nack'; abort short-circuits silently.
The per-code reference — every E_* code organized by seam, with fatality, behavior, and the nuances that matter in production — lives in the Exception Reference.
Exception anchors
Every exception is constructed by createException. The code is the stable identifier (E_*). The fatal flag controls whether the runner throws out of run() or emits on the error bus.
Fatal vs non-fatal is a structural distinction
- Fatal exceptions indicate programming errors. They throw synchronously at the seam where the bad input arrived. There is no path back into the runner.
- Non-fatal exceptions indicate runtime failures. They are emitted on the observability
errorevent and the pipeline stage that produced them is skipped; the turn / dispatch continues to its terminal event.
Abort is not an error
Abort is a settlement outcome, not a failure
When the turn-level abort signal fires (or middleware throws an AbortError), the pipeline short-circuits silently:
- No
errorevent is emitted. turnEndstill fires.dispatchEnd.statusis'aborted'(if the abort occurred during dispatch).- Pending deltas inside dispatch are discarded — partial writes never reach storage.
The consumer's abort handler is responsible for any user-visible signal. The runner does not interpret abort as failure; abort is a settlement outcome on its own.
What you do not catch
try/catch around run() is not enough
run() resolves with void and rejects only with the fatal exceptions listed above (validation errors at construction and entry). Everything else surfaces through events:
- Non-fatal pipeline errors →
runner.observe('error', ...). - Dispatch settlement →
runner.observe('dispatchEnd', ev => ev.status === 'nack' ? ev.error : ...). - Tool errors → either
toolExecutionEnd(withisError: true) on the observability bus, ortoolCall(withisError: true) on the functional bus. - Gate failures → the
TurnContext.waitForpromise rejects; observability seesturnGateClosedwith the result.
If you wrap run() in a try { } catch { }, you are catching programmer errors (invalid config, invalid context). For runtime failures, listen to events.