Errors during dispatch
How the four error sources during a dispatch reach their terminal state, and why the surfacing differs depending on whether dispatch() was called by a TurnRunner or standalone.
LLM Dispatch covers the dispatch contract; the Exception Reference lists every dispatch-specific E_* code under Dispatch.
Error sources
- Executor throws (non-abort). Wrapped as
E_LLM_EXECUTION_EXECUTOR_ERROR, emitted onerror, the pending delta queue is cleared, the dispatch nacks. - Input or output middleware throws (non-abort). Both pipelines surface their failures through the same code,
E_DISPATCH_PIPELINE_ERROR. Emitted onerror, the dispatch nacks. (The summary names "input pipeline" and "output pipeline" are not encoded in the exception itself — observers identify the side viaiterationStart/iterationEndframing or via a label inside the middleware.) - Abort. The
AbortSignalfires (turn-level abort, gate-aborted, or whatever else is wired into the controller). The delta queue is discarded, the loop breaks,dispatchEnd.statusis'aborted'. Noerrorevent is emitted — abort is not an error. ctx.nack(error)called.dispatchEnd.statusis'nack',dispatchEnd.erroris the supplied error.
Where errors surface depends on the entry point
When TurnRunner.run() is the caller, dispatch errors are caught by the runner, emitted on the error bus, and run() still resolves. When dispatch() is called standalone, the same errors reject the dispatch() promise — no TurnRunner is there to swallow them. Wire your own try/catch around standalone dispatches, or rely on the observability hooks you passed in.
See the Exception Reference for the exception codes and what each one means about which seam misbehaved.