# Loop Lessons

- `ToolExecutionUpdate` must include the originating tool call `id` and `name`, and partial tool updates must flow through an awaited relay instead of `try_send`; otherwise concurrent tool progress becomes unattributed and can be silently dropped under channel backpressure.
- Post-turn assistant replacements must preserve the original `ToolCall` blocks. A text-only replacement on a tool turn breaks the core invariant that assistant tool calls stay paired with the committed `ToolResult` messages that follow.
- `TurnPolicyContext.context_messages` must always be a committed turn snapshot. Text-only turns need the assistant message committed before post-turn policies run, and transfer turns must also execute post-turn policies before honoring transfer termination.
- Async tool approval futures need the same `AssertUnwindSafe(...).catch_unwind()` isolation as the sync approval-context hook. A panic after the approval future is polled must turn into `ToolApprovalResolved { approved: false }` plus an error tool result, not an unwound dispatch task.
- Steering interrupts cannot rely on a second `poll_steering()` call to recover messages already drained by a worker task. If a worker polls steering to trip the interrupt flag, it must hand those drained messages into shared batch state so `ToolExecOutcome::SteeringInterrupt` preserves them for the next turn.
- Parent cancellation must be observed while the batch collector waits on join handles. Child cancellation tokens do not unblock `JoinHandle::await` for cancellation-unaware tools, so the collector has to `select!` on `batch_token.cancelled()`, abort the remaining tasks, and return an aborted batch outcome immediately.
- An aborted tool batch must terminate the current turn inline. Routing that outcome through `handle_cancellation()` emits a second synthetic `TurnStart` and drops the tool-call payload/results that belong to the interrupted turn.
- `ToolExecutionStrategy::partition()` indices target the post-preprocessing `tool_calls` slice passed into the strategy, not the original LLM-emitted tool-call list. The dispatch layer must reject out-of-bounds, duplicate, or missing prepared indices with synthetic tool errors before any tool starts.
- `PreDispatchVerdict::Stop` must synthesize one terminal error result for every unresolved tool call in the batch, including calls that already passed preprocessing before a later stop fires. Stop aborts dispatch, but it does not relax tool-result parity.
