Session options
function buildSessionOptions<TConfig extends BaseSessionConfig>(
config: TConfig,
runDir: string,
spec: AgentSpec<TConfig>,
options?: { autoCompactEnabled?: boolean; run?: RunFolder },
): Promise<{
options: Options; // for the SDK's query()
transcriptLogger: TranscriptLogger;
transcriptPath: string; // <runDir>/transcript.jsonl
modeControl: ModeControl;
language: Language; // the resolved language
}>;
config: your session config.runDir: this run's folder. The transcript, the response file and the kit's MCP servers use it. With a runs folder, pass the folder the chat gives your opener (run.dir).spec: yourAgentSpec.options.autoCompactEnabled: the SDK's automatic context compaction;trueby default.options.run: keeps the SDK's transcript in the run folder and resumes its session if it has one. See Runs and resuming.
What goes into Options
System prompt
spec.buildSystemPrompt(config), then the extensions' list and each active one's own section (the awareness's "Who you are", the sources', the knowledge base's), then the reply language line unless spec.replyInLanguage === false.
Tools
tools (what exists) and allowedTools (what's pre-approved) are the same list, built from the configuration:
| Condition | Tools added |
|---|---|
| always | WebFetch, WebSearch, TodoWrite |
config.extraWritableDirs | Read, Write, Edit, Glob, Grep |
config.extraReadableDirs, or an extension that asks for them (sources) | Read, Glob, Grep |
| file tools or at least one plugin | Skill |
spec.buildSubagents() returns something | Agent; Bash too when a subagent lists it in its tools or has no tools |
TodoWrite is the SDK's task list for long jobs (the session's env is process.env plus CLAUDE_CODE_ENABLE_TASKS=0, without which the CLI replaces it with its own Task tools): the chats show the list instead of the calls (see Ink chat). Nothing else of the CLI's built-in tools is available. Bash is there only because the SDK won't spawn a subagent whose own tools aren't in the session's; a hook denies it to the main agent.
MCP servers and permissions
mcpServers holds spec.buildMcpServers(config, runDir) plus the kit's own, when they apply:
| Server | Tool | When |
|---|---|---|
approvals | request_human_approval, ask_human, present_plan | mode is not autonomous |
manualLogin | request_manual_login | mode is not autonomous and spec.manualInterventionTexts is set |
sourceFiles | list_sources, extract_text, save_to_sources, download_to_sources, and request_file, retire_source outside autonomous mode | the sources extension (with its folder) |
time | current_time, date_math | always (in config.timeZone, or the system's) |
knowledge | the knowledge_* tools (knowledge_retire outside autonomous mode) | the knowledge extension (with its folder) |
canUseTool is allowAnyMcpTool: every mcp__* call is approved, anything else is denied (the built-in tools above are already pre-approved, so they never reach it). disallowedTools is spec.disallowedTools ?? [] and wins over everything.
Hooks
PreToolUse, in this order:
- the transcript logger;
- the file scope gate, with file tools;
- the step gate, outside autonomous mode, active only while the mode is
interactive; - the plan gate, likewise outside autonomous mode, active only while the mode is
plan(see modes); - with subagents, the three subagent gates: type, Bash, foreground.
PostToolUse: the transcript logger.
The result also has apis, what each active extension hands the host, by its name: the sources' addSource at apis.sources.addSource, the knowledge base's store at apis.knowledge.knowledgeStore (see Knowledge store).
Skills, plugins and the working directory
When the session has file tools or at least one plugin:
cwdisconfig.projectDir;additionalDirectoriesare the searchable folders: the extensions' read-only ones (the sources folder), thenextraWritableDirs, thenextraReadableDirs;pluginsarespec.pluginRoots(config), plus the knowledge plugin when the knowledge base is on, each loaded as a local plugin with its MCP discovery skipped;skillsisspec.skills ?? "all";"plugins"becomes the skills of the plugins loaded (plugin:folder), and a list gets the enabled extensions' skills added.
Otherwise the SDK's defaults apply (the process's working directory, no plugins).
Settings
| Option | Value | Why |
|---|---|---|
settingSources | spec.settingSources ?? ["project"] | the runner's own Claude Code settings never reach the agent unless asked for |
settings.autoCompactEnabled | options.autoCompactEnabled ?? true | long chats keep working |
settings.autoMemoryEnabled | false | the runner's Claude Code memory is not the agent's |
settings.includeGitInstructions | false | no kit agent commits code, and the git context skewed the reply language |
settings.disableClaudeAiConnectors | true | the claude.ai connectors of the account the session runs with (Drive, Gmail…) are the runner's integrations, not the agent's |
includePartialMessages | true | replies stream token by token |
maxTurns | 400 | a generous cap per query |
See Permissions and isolation for the reasons.
Subagents
agents is what spec.buildSubagents(config) returned, with the reply language line appended to each subagent's prompt.
Runs
With options.run: sessionStore is a run store for run.dir, and resume is run.sessionId when it has one.
Adding your own options
The result is a plain object: spread it to add or override anything the kit doesn't set for you.
const { options } = await buildSessionOptions(config, runDir, spec);
const session: Options = {
...options,
model: "claude-sonnet-5",
maxTurns: 50,
hooks: {
...options.hooks,
PostToolUse: [...(options.hooks?.PostToolUse ?? []), { hooks: [myAuditHook] }],
},
};
Keep the kit's hooks when you add yours: replacing hooks wholesale would drop every guardrail. See Your own hooks.
Calling it more than once
Every call builds a fresh set of servers and hooks bound to its runDir. Build the options once per session, and again for another run folder (that's what a session opener does on /resume). The resolved language, the mode control and the transcript logger belong to that call.