68 lines
5.1 KiB
Markdown
68 lines
5.1 KiB
Markdown
# Runner Lifecycle
|
|
|
|
Use this reference for changes to `Runner`, turn accounting, guardrails, hooks, handoffs, interruptions, cancellation, or streaming and non-streaming behavior.
|
|
|
|
## Turn Boundary
|
|
|
|
A turn is one logical model invocation plus processing of that response. Tool execution, handoff resolution, session persistence, interruption resume, and retries inside that logical invocation do not independently consume turns.
|
|
|
|
- Increment the turn counter exactly once when the run loop starts a logical model turn. Transport or provider retries inside `get_new_response()` remain part of that turn.
|
|
- A handoff changes the current agent, but the next turn begins only when the new agent invokes a model.
|
|
- Resuming `NextStepInterruption` continues the paused turn. Resolve stored approvals and tool work before deciding whether another model call is needed.
|
|
- Preserve `max_turns` and the current turn in `RunState`; resume must not reset the budget or charge a turn twice.
|
|
|
|
## Guardrail Ordering
|
|
|
|
- Input guardrails belong to the starting agent and run only for the initial user input. Do not rerun them after handoffs or when resuming an interruption.
|
|
- Sequential input guardrails must finish before model-side effects begin. Parallel input guardrails may overlap the model call, so a tripwire or exception must cancel and await the in-flight model task and sibling guardrail tasks.
|
|
- Tool input guardrails run before the approved tool side effect. Tool output guardrails run after local execution and before the output is accepted into the next step.
|
|
- Output guardrails run only after a candidate final output exists. Streaming must await them and preserve the same tripwire and exception behavior as non-streaming execution before declaring completion.
|
|
- Guardrail results are observable run state. Preserve them across handoffs, error handlers, streamed completion, and `RunState` round-trips.
|
|
|
|
## Step State Machine
|
|
|
|
`SingleStepResult.next_step` is the control boundary after one model response and its local side effects:
|
|
|
|
| Step | Meaning |
|
|
|---|---|
|
|
| `NextStepRunAgain` | Continue with the current agent and make another model call |
|
|
| `NextStepHandoff` | Switch the current agent, emit the transition, then continue |
|
|
| `NextStepFinalOutput` | A final candidate exists; finish terminal hooks, output guardrails, persistence, and result construction |
|
|
| `NextStepInterruption` | Persist enough processed state to resume pending approvals without rerunning completed work |
|
|
|
|
Do not bypass this state machine with path-local completion logic. New terminal or pausable behavior must define non-streaming, streaming, session, tracing, and serialized-resume semantics.
|
|
|
|
## Streaming Parity and Cancellation
|
|
|
|
- Streaming and non-streaming paths must produce equivalent final output, generated items, current agent, usage, guardrail results, session history, and interruption state for the same model behavior.
|
|
- Raw transport events may differ, but semantic `RunItemStreamEvent` and `AgentUpdatedStreamEvent` emission must follow the same processed items and agent transitions used by the non-streaming result.
|
|
- `stream_events()` is the stream driver's cleanup boundary. Keep consuming it until exhaustion after normal completion or `cancel()`, or explicitly close the async iterator; merely breaking after the last visible token does not prove session writes, guardrails, compaction, sandbox cleanup, usage, or terminal errors have settled.
|
|
- Immediate cancellation marks the result complete and requests task cancellation. `after_turn` cancellation leaves the current model/tool turn running so it can persist state and usage before the next turn. Preserve this distinction instead of treating both modes as queue shutdown.
|
|
- Terminal run-loop, guardrail, and max-turn errors must be surfaced from `stream_events()` after the required queued events are handled. Preserve `run_loop_exception` as a diagnostic view of the background task, not as a replacement completion primitive.
|
|
- `task.cancel()` is a request, not cleanup completion. Await cancelled tasks when their `finally` blocks, exceptions, or owned resources affect run correctness.
|
|
- Keep lifecycle hooks aligned across both paths, especially model start/end, handoff, tool start/end, and final-output hooks.
|
|
|
|
## Review Checklist
|
|
|
|
1. Identify which turn and which agent own the behavior.
|
|
2. Trace every `NextStep` outcome, including interruption resume.
|
|
3. Compare streaming and non-streaming side effects and terminal ordering.
|
|
4. Test guardrail tripwires and exceptions in sequential and parallel modes when relevant.
|
|
5. Verify normal exhaustion, explicit iterator close, immediate cancellation, and after-turn cancellation leave the documented result and owned resources in a coherent state.
|
|
|
|
## Sources
|
|
|
|
- `src/agents/run.py`
|
|
- `src/agents/run_internal/run_loop.py`
|
|
- `src/agents/run_internal/run_steps.py`
|
|
- `src/agents/run_internal/turn_preparation.py`
|
|
- `src/agents/run_internal/turn_resolution.py`
|
|
- `src/agents/run_internal/guardrails.py`
|
|
- `tests/test_agent_runner.py`
|
|
- `tests/test_agent_runner_streamed.py`
|
|
- `tests/test_cancel_streaming.py`
|
|
- `tests/test_guardrails.py`
|
|
- `tests/test_run_state.py`
|
|
- `docs/streaming.md`
|
|
- `docs/results.md`
|