Bound the turn

A model in a loop will occasionally try to stay in the loop. AgentRunLimits puts hard ceilings on every turn, so a runaway plan costs a bounded amount of compute, money, and time.

The five limits

Limit Default Bounds
maxToolCallsPerTurn 12 tool executions in one turn, across all round trips
maxProviderRoundTrips 8 provider requests in one turn — each batch of tool results triggers another
maxContinuationRebuilds 1 extra provider passes after new tool domains are activated mid-turn
confirmationTimeoutSeconds 45 how long a pending confirmation waits before it is denied
maxWallClockSeconds 60 total elapsed time for the turn, streaming and tool execution included

Set the limits

Pass limits at makeAgent; anything you don't name keeps its default.

let agent = try runtime.makeAgent(
    provider: .anthropic(apiKey: key),
    role: role,
    limits: AgentRunLimits(
        maxToolCallsPerTurn: 6,
        maxWallClockSeconds: 30
    )
)

What a trip throws

Three of the limits fail the turn with a typed AgentSessionError thrown from send():

Limit Error
maxToolCallsPerTurn AgentSessionError.maxToolCallsExceeded
maxProviderRoundTrips AgentSessionError.maxRoundTripsExceeded
maxWallClockSeconds AgentSessionError.wallClockTimeoutExceeded

The wall clock is checked between stream events and before every tool call, and a tool still running when the budget expires fails the turn mid-flight.

The other two never throw. A confirmation that outlives confirmationTimeoutSeconds is denied — the tool is skipped, the model is told, and the turn continues (details). And when maxContinuationRebuilds is spent, the turn stops taking extra passes and finishes with the tools it has.

Per turn, not per session

Counters reset on every send() — the caps bound a single turn, not the session's lifetime. A tripped limit doesn't poison anything: the failed turn rolls back its undo transaction and the session is immediately ready for the next send(), with fresh counters.

Handle the thrown errors per turn — see when it fails.