Steer tool use
By default the model decides whether to call tools. send(_:toolChoice:)
overrides that decision per turn — force at least one call, or forbid calls
entirely.
// The model decides (default)
try await agent.send("Trim the intro clip.")
// Force at least one tool call
try await agent.send("Trim the intro clip.", toolChoice: .required)
// Forbid tool calls this turn
try await agent.send("Summarize what you changed.", toolChoice: .none)
ToolChoice has three values: .auto, .required, and .none. .auto is
the default everywhere and adds nothing to the request.
How .required behaves
.required forces the turn's first provider round trip only. Once the
model has attempted a call, the follow-up round trips revert to .auto so it
can read the tool results and produce a final answer — otherwise it would be
forced into endless calls. An attempted call satisfies the requirement even
if a guard later denies it.
On a provider that runs tools inside its own session (the on-device model), there is no per-round-trip seam: the forced mode applies to the provider's whole internal turn.
Violations fail loud
A model that ignores the instruction — .required with zero attempted calls,
or .none with at least one — fails the turn with a typed error:
do {
try await agent.send("Just answer in prose.", toolChoice: .none)
} catch AgentSessionError.toolChoiceNotHonored(let requested, let observedToolCalls) {
print("asked for \(requested), model attempted \(observedToolCalls) calls")
}
A violating turn persists no assistant message and rolls back its undo
transaction — your message stays as the conversation's last entry, ready to
resend. .none is enforced before execution: a forbidden call is caught
before any executor runs.
Validated before anything leaves
A non-.auto choice against a provider without native tool-choice support
throws AgentSessionError.toolChoiceUnsupported before any request is built
and before your message is appended — a misconfigured send leaves the session
untouched. Silently dropping the instruction is never an option: a dropped
.none would let tools run after you demanded none.
Two misconfigurations also fail up front with
AgentSessionError.invalidConfiguration: .required with a zero tool-call
budget (see bound the turn), and .required when no
tools are available to the provider.
Provider support
| Provider | Tool choice |
|---|---|
| Anthropic | native |
| OpenAI | native |
| Gemini | native |
| AgentKit Cloud | native |
| Your backend | off by default — opt in via custom capabilities when your backend honors the emitted field |
| Apple on-device | OS 27+ with an app built against the OS 27 SDK; below that the capability reports false and a non-auto choice throws before any request |
Next
- Bound the turn — tool budgets and how they interact
with
.required. - Error reference — the full typed error surface.