Configure your agent
Build the Agent SDK options object, choose a model and fallback, set environment and working directory, cap turns and spend, and change settings mid-session.
Every Agent SDK session takes its configuration from three places: settings files on disk, environment variables, and the options object you pass to query(). This page is about composing that options object well, plus the handful of general settings that do not belong to any single feature. For the full type of every field, see the TypeScript reference (Options) and the Python reference (ClaudeAgentOptions).
The options object
All fields are optional. A query() with no options runs on SDK defaults. Field names are camelCase in TypeScript and snake_case in Python.
Here is a read-only agent I use to produce a weekly summary of failing checks in a monorepo:
import { query } from "@anthropic-ai/claude-agent-sdk";
const run = query({
prompt: "Read the last CI report in reports/ and list the failing packages with one-line causes",
options: {
model: "sonnet",
cwd: "/srv/repos/platform",
allowedTools: ["Read", "Glob", "Grep"],
maxTurns: 10,
},
});
for await (const msg of run) {
if (msg.type === "result" && msg.subtype === "success") console.log(msg.result);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
opts = ClaudeAgentOptions(
model="sonnet",
cwd="/srv/repos/platform",
allowed_tools=["Read", "Glob", "Grep"],
max_turns=10,
)
async for msg in query(prompt="Read the last CI report in reports/ and list the failing packages", options=opts):
if isinstance(msg, ResultMessage) and not msg.is_error:
print(msg.result)
asyncio.run(main())
A common misunderstanding: allowedTools / allowed_tools is a pre-approval list, not a whitelist. Tools in the list run without asking. Tools outside it are still available, and the permission mode decides what happens when Claude calls one. To remove a tool entirely, use deny rules or the tools option; SDK permissions explains the difference.
Settings files
Two options decide which settings files contribute:
| Option | What it does |
|---|---|
settingSources / setting_sources | Chooses which filesystem sources load: "user", "project" and "local". Settings files and CLAUDE.md arrive through these. Omit it to load all three, pass [] to load none. |
settings | Supplies settings directly: a file path or an inline JSON string in both languages, and also a plain object in TypeScript. Whatever you pass here beats user, project and local settings. Only managed policy settings outrank it. |
Claude Code features in the SDK goes through what each source brings in.
Model and fallback
If nothing selects a model (no model option, no setting, no environment variable), the session uses Claude Code's default model. The model configuration page explains the precedence and lists the aliases.
model accepts an alias such as opus, sonnet or haiku, or a full model ID. Pin a full ID when you need reproducible behaviour; use a smaller model when speed and cost matter more than depth.
fallbackModel / fallback_model names what to use when the primary is overloaded or unavailable. The session switches over automatically and tries the primary again at the start of every user turn, so it drifts back once the outage clears. You can give one model or a comma-separated chain:
const options = {
model: "opus",
fallbackModel: "sonnet,haiku",
};
opts = ClaudeAgentOptions(model="opus", fallback_model="sonnet")
In TypeScript, setting the fallback to the same value as model throws at startup. The model configuration page covers how long a chain can be.
Note: There are no
temperature,top_pormax_tokensfields. To trade depth for speed, use the effort level; to bound cost, use a budget cap. If you truly need raw sampling parameters, call the Messages API directly.
Environment variables
env sets environment variables for the Claude Code process behind the session. The two SDKs treat it differently, and this catches people out:
| Language | Behaviour of env |
|---|---|
| TypeScript | Replaces the child process's environment entirely |
| Python | Merges over the inherited environment; your values win |
So in TypeScript, spread process.env in, or you lose PATH, HOME and ANTHROPIC_API_KEY:
const options = {
env: { ...process.env, ANTHROPIC_BASE_URL: "https://llm-proxy.internal.example" },
};
opts = ClaudeAgentOptions(env={"ANTHROPIC_BASE_URL": "https://llm-proxy.internal.example"})
Leave env unset and the child inherits your environment in both languages. Anything Claude Code reads from the environment can be set this way; see the environment variables reference. The SDK references also describe the variables for tuning API timeouts and stall detection.
Working directory
cwd is where the session runs. Unset, it is your process's current directory. There is no way to change it on a live session; start a new session instead.
The working directory decides:
- which project's settings and hooks load (Claude Code features in the SDK)
- where project skills are discovered (SDK skills)
- which project a stored session is filed under (session storage)
To let tools read and write outside it, add paths with additionalDirectories (TypeScript) or add_dirs (Python). These grant file access only; they do not load settings or CLAUDE.md from those folders. The permissions page has the details.
Turn and budget caps
Both caps are off unless you set them.
| Option | Ends with result subtype | Behaviour of 0 |
|---|---|---|
maxTurns / max_turns | error_max_turns | Means no limit, same as unset |
maxBudgetUsd / max_budget_usd | error_max_budget_usd | Rejected as invalid at startup; the session never runs |
What happens when a cap trips depends on the input mode:
- Single-message
query(): you receive the cap result and then the SDK raises. Wrap the loop intryif you want to carry on afterwards. - Streaming input: the session survives. The turn count resets for each queued message, but spend accumulates across the whole conversation, so once the budget is used up every later message ends with the same budget result. A
/clearresets the budget.
Subagent spend counts too. See turns and budget and cost tracking.
Changing configuration mid-session
With streaming input you can change the model and permission mode between turns.
| Method | TypeScript (on the Query object) | Python (on ClaudeSDKClient) |
|---|---|---|
| Switch model | setModel(model?) | set_model(model) |
| Switch permission mode | setPermissionMode(mode) | set_permission_mode(mode) |
Python's plain query() returns a simple iterator with no control methods, which is why you need ClaudeSDKClient there. Calling the model setter with no model switches to Claude Code's default, not back to the model you started with.
TypeScript has two more:
applyFlagSettings(settings)applies settings at runtime, for exampleawait run.applyFlagSettings({ effortLevel: "high" }). It takes settings-file keys, not options fields, and not every key takes effect mid-session.updateSettings(source, settings)writes one allowlisted key to a settings file. With"localSettings"it writes the project's local settings (for example{ outputStyle: "Explanatory" }), which applies from the next request and persists for future sessions that load local settings. With"userSettings"the only accepted key iseffortLevel, saved as the default effort for the current model; it does not change the running session.
A Python example that escalates to a stronger model after a cheap first pass:
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, ResultMessage
async def triage_then_fix():
async with ClaudeSDKClient(options=ClaudeAgentOptions(model="haiku")) as client:
await client.query("Read errors.log and tell me whether this is a config problem or a code bug. One word.")
verdict = ""
async for msg in client.receive_response():
if isinstance(msg, ResultMessage):
verdict = (msg.result or "").strip().lower()
if "code" in verdict:
await client.set_model("opus")
await client.set_permission_mode("acceptEdits")
await client.query("Find and fix the bug behind the errors in errors.log.")
async for msg in client.receive_response():
...
In TypeScript, the pattern is to feed prompts from an async generator and hold back the next message until you have called the setters, so the second turn runs with the new settings.
Note: Prompt caches are per model. After switching, the next request re-reads the whole conversation uncached at the new model's price. The prompt caching page explains why.
Which option does what
| TypeScript | Python | Controls | Read more |
|---|---|---|---|
permissionMode | permission_mode | How much runs without approval | Permissions |
allowedTools | allowed_tools | Pre-approved tools | Permissions |
canUseTool | can_use_tool | Your approval callback | User input |
systemPrompt | system_prompt | Instructions to the agent | System prompts |
settingSources | setting_sources | Filesystem settings to load | Claude Code features |
mcpServers | mcp_servers | External tool servers | MCP |
agents | agents | Subagent definitions | Subagents |
hooks | hooks | Lifecycle callbacks | Hooks |
skills | skills | Which skills load | Skills |
plugins | plugins | Plugins to load | Plugins |
outputFormat | output_format | JSON schema for the result | Structured outputs |
resume | resume | Continue a stored session | Sessions |
forkSession | fork_session | Branch a session | Sessions |
sessionStore | session_store | External session persistence | Session storage |
enableFileCheckpointing | enable_file_checkpointing | Rewindable file edits | File checkpointing |
effort | effort | How hard Claude thinks | Agent loop |
sandbox | sandbox | Sandboxing of tool execution | Secure deployment |
If you know what you want but not which feature delivers it, Claude Code features in the SDK has a decision table. For multi-tenant services, hosting shows how settingSources, env and cwd combine to keep tenants apart.