Documentation Index
Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
Use this file to discover all available pages before exploring further.
Functions
query()
The SDK’s main entry function. Creates an async iterator that streams messages in arrival order.
async def query(
*,
prompt: str | AsyncIterable[dict[str, Any]],
options: QoderAgentOptions | None = None,
) -> AsyncIterator[Message]:
...
Parameters
| Parameter | Type | Description |
|---|
prompt | str | AsyncIterable[dict[str, Any]] | Pass a string for single-turn; pass an async iterable for multi-turn |
options | QoderAgentOptions | Optional session configuration |
Return Value
Returns AsyncIterator[Message], consumed via async for.
QoderSDKClient
Class-based multi-turn conversation API. Suitable for scenarios that need to keep session state across turns or dynamically switch the model or permission mode.
client = QoderSDKClient(options=options)
await client.connect("Initial prompt.")
| Method | Description |
|---|
client.query(prompt) | Send a new round of user input |
client.receive_response() | Consume messages until ResultMessage |
client.receive_messages() | Background async iterator that receives the entire session message stream |
client.connect(prompt) / client.disconnect() | Manually manage the connection |
client.interrupt() | Interrupt the current generation or tool execution |
client.set_model(model) | Switch the model at runtime |
client.set_permission_mode(mode) | Switch the permission mode at runtime |
client.stop_task(task_id) | Stop a background task |
client.apply_flag_settings(settings) | Inject flag-level settings |
client.supported_agents() | List the agents currently available |
client.get_mcp_status() | Get the status of all MCP servers |
client.set_mcp_servers(servers) | Replace the MCP server configuration |
client.reconnect_mcp_server(name) | Reconnect a specific MCP server |
client.toggle_mcp_server(name, enabled) | Enable or disable an MCP server |
client.get_available_models() | Get the list of available models |
Types
QoderAgentOptions
Configuration object for query() and QoderSDKClient. The Python SDK uses snake_case field names.
| Field | Type | Default | Description |
|---|
auth | InternalAuthOptions | dict | None | None | Authentication configuration |
on_auth_expired | Callable | None | None | Callback when authentication expires; triggered at most once per session |
tools | list[str] | ToolsPreset | None | None | Tool set. Pass a string array to restrict available tools; built-in tool names are listed in Built-in Tool List |
allowed_tools | list[str] | [] | Tool allowlist; listed tools are pre-authorized |
disallowed_tools | list[str] | [] | Tool blocklist; takes priority over allowed_tools and permission_mode |
can_use_tool | CanUseTool | None | Custom tool permission callback |
permission_mode | PermissionMode | None | Session permission mode |
allow_dangerously_skip_permissions | bool | False | Allow skipping permission checks; used together with permission_mode="bypassPermissions" |
permission_prompt_tool_name | str | None | None | MCP tool name for permission prompts; mutually exclusive with can_use_tool |
model | str | None | None | Model to use |
fallback_model | str | None | None | Fallback model when the main model fails |
resolve_model | ModelPolicyProvider | None | Dynamic model-selection callback. Passing it switches into dynamic-callback mode; see Model Policy |
resolve_model_timeout_ms | int | 500 | Callback timeout (milliseconds); only effective when resolve_model is passed |
system_prompt | str | SystemPromptPreset | SystemPromptFile | None | None | System prompt |
cwd | str | Path | None | None | Working directory |
env | dict[str, str | None] | {} | Environment variables passed to the CLI process |
cli_path | str | Path | None | None | Path to the qodercli executable |
session_id | str | None | None | Specify the session UUID |
resume | str | None | None | Session ID to resume |
continue_conversation | bool | False | Continue the most recent session |
fork_session | bool | False | When used with resume, fork into a new session ID |
max_turns | int | None | None | Maximum conversation turns |
include_partial_messages | bool | False | Include StreamEvent streaming fragments; see Streaming Output |
mcp_servers | dict[str, McpServerConfig] | str | Path | {} | MCP server configuration; see MCP |
allowed_mcp_server_names | list[str] | [] | Restrict which MCP servers are available |
strict_mcp_config | bool | False | Strict MCP validation |
hooks | dict[HookEvent, list[HookMatcher]] | None | None | Lifecycle hooks; see Hooks |
agents | dict[str, AgentDefinition] | None | None | Programmatically defined subagents; see Agents Reference |
agent | str | None | None | Agent name used by the main thread; see Agents Reference |
settings | str | Path | Settings | None | None | Inline settings object or settings file path |
setting_sources | list[SettingSource] | None | None | Which filesystem settings to load |
add_dirs | list[str | Path] | [] | Additional directories accessible to the AI |
extra_args | dict[str, str | None] | {} | Additional arguments passed to the CLI |
plugins | list[SdkPluginConfig] | [] | Load local plugins; see Plugins |
skills | list[str] | Literal["all"] | None | None | Enabled skills; pass "all" to enable all |
enable_file_checkpointing | bool | None | None | Enable file checkpointing |
thinking | ThinkingConfig | None | None | Thinking configuration |
Authentication
from qoder_agent_sdk import access_token, access_token_from_env, qodercli_auth
| Factory function | Description |
|---|
access_token(token) | Pass the PAT directly |
access_token_from_env() | Read from the default environment variable QODER_PERSONAL_ACCESS_TOKEN |
access_token_from_env("MY_PAT") | Read from a specified environment variable |
qodercli_auth() | Reuse the local qodercli login session |
For convenience usage, see SDK Authentication.
Agents Reference
This page summarizes the stable configuration items related to SDK Agents. For getting started and usage scenarios, see Subagent Usage Guide.
Sources of Available Agents
The agents available in the current session may come from multiple sources:
| Source | Description |
|---|
| SDK registration | Registered for the current session via QoderAgentOptions.agents |
| CLI built-in | Built-in Agents registered when qodercli starts |
| User / project config | Agent definitions in the user or project configuration directory |
| Plugin | Agents provided by loaded plugins |
In the interactive CLI, you can run /agents to view the currently discovered Agents; on the command line, run qodercli agents list. In the Python SDK, after QoderSDKClient connects, call client.supported_agents() to get a summary of agents available in the current session.
QoderAgentOptions.agents
Type: dict[str, AgentDefinition] | None
Registers custom Agents available in the current session. The dict key is the Agent name and the value is that Agent’s definition.
The Agent tool is required: Custom subagents require the main session to delegate through the built-in Agent tool, so allowed_tools must include the Agent tool.
from qoder_agent_sdk import AgentDefinition, QoderAgentOptions
options = QoderAgentOptions(
allowed_tools=["Agent"],
agents={
"reviewer": AgentDefinition(
description="Reviews code quality and reports actionable findings.",
prompt="Review the requested code and report concrete issues.",
tools=["Read", "Grep", "Glob"],
),
},
)
After registration, the model can invoke these subagents through the built-in Agent tool. The main session must include Agent in its tool set to delegate work; allowed_tools=["Agent"] is the required pre-authorization form. If you use tools to narrow the main session’s available tools, include Agent there as well.
QoderAgentOptions.agent
Type: str | None
Specifies which Agent identity the main session runs as. The value can be a name registered in agents, or a built-in / plugin Agent name discovered by the current CLI.
options = QoderAgentOptions(
agents={
"planner": AgentDefinition(
description="Plans work before implementation.",
prompt="Break work into steps, risks, and validation checks.",
tools=["Read", "Grep", "Glob"],
),
},
agent="planner",
)
When set, the main session uses that Agent’s prompt, model, and tool restrictions. When omitted, the default main-session behavior is used.
AgentDefinition
Definition of a custom Agent. The Python SDK’s AgentDefinition is a dataclass whose field names use the protocol-style camelCase. The fields below are the stable capabilities currently covered and verified by the SDK.
from dataclasses import dataclass
from typing import Any, Literal
@dataclass
class AgentDefinition:
description: str
prompt: str
tools: list[str] | None = None
disallowedTools: list[str] | None = None
model: str | None = None
skills: list[str] | None = None
mcpServers: list[str | dict[str, Any]] | None = None
initialPrompt: str | None = None
maxTurns: int | None = None
effort: Literal["low", "medium", "high", "max"] | None = None
permissionMode: PermissionMode | None = None
| Field | Type | Required | Description |
|---|
description | str | Yes | Agent purpose description; the model uses it to decide when to invoke the Agent |
prompt | str | Yes | Agent system prompt |
tools | list[str] | None | No | Tool allowlist for this Agent |
disallowedTools | list[str] | None | No | Tools excluded from this Agent’s tool set |
model | str | None | No | Model override; "inherit" means inherit the main session model |
mcpServers | list[str | dict[str, Any]] | None | No | MCP server specs available to this Agent |
skills | list[str] | None | No | Skill names preloaded into the Agent context |
initialPrompt | str | None | No | First user input automatically submitted when this Agent is used as the main session Agent |
maxTurns | int | None | No | Maximum API turns for the Agent |
effort | "low" | "medium" | "high" | "max" | None | No | Reasoning effort level |
permissionMode | PermissionMode | None | No | Permission mode for tool execution inside this Agent |
The Python SDK serializes AgentDefinition into the initialize request via dataclasses.asdict(); qodercli then parses it according to the current Agent schema.
description
Describes what tasks the Agent is suitable for. It affects whether the model chooses this Agent.
description="Runs project tests, analyzes failing output, and suggests fixes."
Prefer a clear triggering scenario. Avoid broad descriptions such as Helpful assistant.
prompt
The Agent’s system prompt. Use it to define the role, constraints, and output format.
prompt="""You are a security reviewer.
Check for authentication bypass, authorization bugs, injection risks, and secret leaks.
Return findings sorted by severity."""
Tool allowlist for the Agent. When set, the Agent can only use the listed tools.
tools=["Read", "Grep", "Glob"]
When tools is omitted, the subagent default tool set is used. A subagent’s tool set does not inherit trimming from the main session’s allowed_tools.
Excludes specific tools from the Agent’s tool set.
disallowedTools=["Bash", "Write"]
When disallowedTools is omitted, the subagent does not inherit trimming from the main session’s disallowed_tools. Usually avoid setting both tools and disallowedTools unless you explicitly know the final tool set.
model
Specifies the model for the Agent; when omitted, the session default model is used. At the Python type level it is str | None; the SDK does not restrict specific strings locally. Common model tiers include:
| Value | Tier | Description | Suitable for | Credit cost |
|---|
"auto" | Smart routing | Intelligently selects the best model, balancing capability and cost | Most daily development work; recommended default | ~1.0x |
"ultimate" | Ultimate | Expert-level deep reasoning and thinking capability | Complex system design and difficult analysis | ~1.6x |
"performance" | Performance | Advanced reasoning and high-quality output | Core implementation, architecture design, refactoring | ~1.1x |
"efficient" | Efficient | Standard reasoning with good cost efficiency | Basic code generation, unit tests, daily Q&A | ~0.3x |
"lite" | Lite | Basic reasoning, free to use | Quick validation, simple logic, quick questions | 0x |
Agents also support two special forms:
| Value | Description |
|---|
"inherit" | Inherit the main session model; usually not echoed in supported_agents() |
| Full model ID | Directly specify a model ID supported by the current CLI / backend |
mcpServers
mcpServers: list[str | dict[str, Any]] | None
Limits or adds MCP servers available to this Agent. Each entry can be a session-level server name, or an inline server configuration mapping.
Reference a session-level MCP server:
options = QoderAgentOptions(
mcp_servers={
"orders": {
"command": "python",
"args": ["servers/orders.py"],
},
},
allowed_tools=["Agent"],
agents={
"support": AgentDefinition(
description="Answers support questions using order tools.",
prompt="Use order tools when needed and return a concise answer.",
mcpServers=["orders"],
tools=["mcp__orders__lookup_order"],
),
},
)
Configure a dedicated MCP server for a specific Agent:
AgentDefinition(
description="Searches the internal knowledge base.",
prompt="Search the knowledge base and cite relevant entries.",
mcpServers=[
{
"kb": {
"command": "python",
"args": ["servers/kb.py"],
},
},
],
tools=["mcp__kb__search"],
)
When you only want to expose a specific MCP tool, also configure tools=["mcp__server__tool"] to avoid exposing every tool of that server to the Agent.
skills
A list of skill names preloaded into the Agent context. Plain skill names and plugin-qualified names are both supported.
skills=["review", "sdk-test-plugin:sdk-echo"]
For session-level skill behavior, see Skills.
initialPrompt
Automatically submitted as the first user input when this Agent becomes the main session Agent through QoderAgentOptions.agent.
initialPrompt="Start by scanning authentication and session management code."
This field only takes effect for the main session Agent. It is ignored when the Agent is invoked as a subagent through the Agent tool.
maxTurns
Limits the Agent’s maximum API turns. Use it to control cost, execution time, and loop risk.
effort
EffortLevel = Literal["low", "medium", "high", "max"]
Controls the Agent’s reasoning effort level. Higher effort is usually suitable for complex reviews, architecture analysis, and high-risk changes, but increases latency and token usage.
permissionMode
Controls the permission mode for tool execution inside this Agent. It uses the same semantics as the session-level permission_mode, but its scope is limited to this Agent. For the session-level permission chain, the priority of allowed_tools / disallowed_tools / can_use_tool, and examples, see Permission Control.
PermissionMode = Literal[
"default",
"acceptEdits",
"plan",
"bypassPermissions",
"yolo",
"dontAsk",
"auto",
]
| Value | Meaning | Suitable for |
|---|
"default" | Standard permission behavior. Tool calls still pass through tool sets, allow / deny rules, runtime approval, or CLI default policy | Most interactive subagents |
"acceptEdits" | Automatically accepts file-edit operations; other sensitive operations still follow the permission flow | A subagent that is approved to modify workspace files |
"bypassPermissions" | Skips permission checks. High-risk mode, usually only for trusted automation or test environments | Controlled CI, temporary validation, one-off automation |
"yolo" | Compatibility alias for "bypassPermissions"; also skips permission checks | Compatibility with older configs; not recommended for new code |
"plan" | Plan mode. Suitable for producing a plan first; by default it does not perform real changes | Planning, design, review, or cases where the subagent should not modify files |
"dontAsk" | Does not ask interactively; operations that are not pre-authorized or allowed by rules are denied | Non-interactive environments, or workflows that should fail instead of prompting |
"auto" | Runtime capability decides allow or deny automatically | Reduce confirmation interruptions while retaining runtime judgment |
For permission semantics, see Permission Control.
AgentInfo
Agent summary returned by QoderSDKClient.supported_agents().
class AgentInfo(TypedDict):
name: str
description: str
model: NotRequired[str | None]
The Python SDK does not currently export a TypedDict named AgentInfo; the return type of supported_agents() is list[dict[str, Any]]. The structure above is the stable field convention of the actually returned dicts.
| Field | Type | Description |
|---|
name | str | Agent name |
description | str | Agent purpose description |
model | str | None | Agent model override; usually empty when unset or when model="inherit" |
from qoder_agent_sdk import AgentDefinition, QoderAgentOptions, QoderSDKClient
options = QoderAgentOptions(
agents={
"reviewer": AgentDefinition(
description="Reviews code quality.",
prompt="Review code and report findings.",
),
},
)
client = QoderSDKClient(options=options)
await client.connect("List agents.")
agents = client.supported_agents()
await client.disconnect()
The returned list may include Agents registered through agents, and may also include built-in, project, user, or plugin Agents discovered by the current CLI. The actual available entries depend on the qodercli version and current configuration.
Context and Invocation Boundaries
- Subagents use independent context and do not receive the parent session’s full history.
- The main information passed from the parent session to a subagent is the task prompt supplied to the
Agent tool.
- A subagent’s intermediate tool results do not directly enter the parent session; the parent session receives the subagent’s final response.
- Subagents cannot spawn their own subagents, so do not put
Agent in a subagent’s tools.
initialPrompt only takes effect for the main session Agent specified by agent.
Model Policy
Dynamic model-selection capability of query(). Two modes: fixed-model (no resolve_model, uses options.model or backend default) and dynamic-callback (pass resolve_model, the callback decides the model before every LLM call). For full concepts, triggers and error handling see Model Policy.
options.resolve_model
Type: ModelPolicyProvider
Entry point for dynamic-callback mode. Once passed, dynamic-callback mode is enabled and the SDK calls this callback before every LLM request to fetch the model. The model returned by the callback is the final model for that request; there is no automatic fallback.
options.resolve_model_timeout_ms
Type: int, default 500
Callback timeout (milliseconds). On timeout ModelPolicyTimeoutError is thrown and the query fails (no fallback). Only effective when resolve_model is passed.
ModelPolicyProvider
Callback function signature. May be synchronous or asynchronous.
from typing import Awaitable, Callable
ModelPolicyProvider = Callable[
["ModelPolicyContext"],
"ModelPolicyResult | Awaitable[ModelPolicyResult]",
]
Triggering scenarios are distinguished by QoderModelPurpose:
| Scenario | purpose | Notes |
|---|
| Main conversation | 'main' | Re-invoked between turns / tools — a session may trigger many times |
| Subagent | 'subagent' | Subagents share the same provider |
| WebFetch tool | 'web_fetch' | After WebFetch retrieves content, a second LLM call summarizes it |
| ImageGen tool | 'image_gen' | Used to pick the image-generation model |
| Context compaction | 'compact' | Before compaction starts, the callback is queried for the compaction model |
| BYOK | any | Set model to a CustomModel object to route via a third-party LLM |
Behavioral notes:
- The callback may be triggered many times within a single session (re-invoked before every turn / tool / sub-task).
- The
model returned by the callback is the final model for that request; the SDK does not re-validate it.
- Throwing an exception or returning an empty
model fails the query directly. See Model Policy — Error handling.
ModelPolicyContext
The context passed to the callback on every invocation.
class ModelPolicyContext(TypedDict, total=False):
purpose: str # QoderModelPurpose
sessionId: str
agentId: str
turnIndex: int
availableModels: list[ModelInfo]
| Field | Type | Required | Description |
|---|
purpose | QoderModelPurpose | Yes | Purpose of this LLM call |
sessionId | str | Yes | Current session ID; the same value is passed across callback invocations within a session, so it can be used as a cache / telemetry key |
agentId | str | Yes | Identifier of the Agent that initiated the call |
turnIndex | int | Yes | Current turn index |
availableModels | ModelInfo[] | Yes | The models currently available to the account in real time (the CLI carries the list with every get_model_policy request) |
QoderModelPurpose
from typing import Literal
QoderModelPurpose = Literal[
"main",
"plan",
"task",
"compact",
"title",
"suggestion",
"generate",
"hook_prompt",
"subagent",
"web_fetch",
"image_gen",
"compression",
"utility",
]
| Value | Triggering scenario |
|---|
'main' | Main-conversation LLM call |
'subagent' | Subagent call |
'web_fetch' | Secondary LLM call triggered by the WebFetch tool |
'image_gen' | Image-generation call triggered by the ImageGen tool |
'compact' | Context compaction / summarization |
ModelPolicyResult
The callback’s return value.
class ModelPolicyResult(TypedDict, total=False):
model: str | CustomModel
parameters: dict[str, Any]
| Field | Type | Required | Description |
|---|
model | str | CustomModel | Yes | String: model identifier; object: BYOK credentials + model identifier |
parameters | dict[str, Any] | No | Model-parameter overrides (e.g. temperature, max_tokens) |
model forms:
- String — any model ID supported by the backend (such as
auto / performance / glm51); the exact set of valid values is returned in real time by client.get_available_models(). Must be non-empty, otherwise the query fails.
CustomModel object (BYOK) — the SDK extracts the object’s model field as the model identifier for this call, and forwards the remaining fields as credentials to the CLI for routing to a third-party LLM.
CustomModel
BYOK credentials. In the resolve_model callback, set the model field directly to this object, and that LLM request will be routed to a third-party provider.
class CustomModel(TypedDict, total=False):
provider: str
model: str
api_key: str
url: str
style: str # "openai" | "anthropic"
| Field | Type | Required | Description |
|---|
provider | str | Yes | Provider key — must match a BYOKProviderInfo.key |
model | str | Yes | Model identifier — extracted by the SDK as the model ID for this call |
api_key | str | Yes | The API Key supplied by the user |
url | str | No | Override the default base URL |
style | str | No | Upstream protocol style, e.g. "openai" / "anthropic"; defaults to "openai" |
Notes:
provider must match a key in the catalog, otherwise backend authentication fails.
- A wrong
api_key causes authentication to fail, which fails the query directly (dynamic-callback mode does not fall back).
- BYOK calls report
total_cost_usd as 0 on the platform; token usage is reported as-is and billed by the provider side.
BYOK Catalog Types
The provider/model catalog returned by client.list_byok_providers().
class SDKControlGetByokConfigResponse(TypedDict, total=False):
providers: list[BYOKProviderInfo]
class BYOKProviderInfo(TypedDict, total=False):
key: str
display_name: str
api_key_url: str
url: str
fields: list[BYOKFieldInfo]
types: list[BYOKModelTypeInfo]
class BYOKFieldInfo(TypedDict, total=False):
key: str
display_name: str
type: str # e.g. "free_input"
mandatory: bool
class BYOKModelTypeInfo(TypedDict, total=False):
key: str
display_name: str
models: list[BYOKModelInfo]
class BYOKModelInfo(TypedDict, total=False):
key: str
display_name: str
is_vl: bool
is_reasoning: bool
format: str
max_input_tokens: int
BYOKProviderInfo
| Field | Type | Description |
|---|
key | str | Provider key — fill into CustomModel.provider for BYOK |
display_name | str | Display name |
api_key_url | str | URL pointing the user where to obtain an API Key |
url | str | Base URL for inference requests |
fields | list[BYOKFieldInfo] | List of fields the provider requires the user to fill in |
types | list[BYOKModelTypeInfo] | Model groups under this provider |
BYOKFieldInfo
| Field | Type | Description |
|---|
key | str | Field key (e.g. api_key) |
display_name | str | Display name shown to the user |
type | str | Field type (e.g. "free_input") |
mandatory | bool | Whether the field is required |
BYOKModelTypeInfo
| Field | Type | Description |
|---|
key | str | Group key, common values: cp / tp / pg (optional) |
display_name | str | Group display name |
models | list[BYOKModelInfo] | Models within the group |
BYOKModelInfo
| Field | Type | Description |
|---|
key | str | Model ID — fill into CustomModel.model |
display_name | str | Display name |
is_vl | bool | Whether vision / multi-modal input is supported |
is_reasoning | bool | Whether this is a reasoning model |
format | str | Upstream protocol format (e.g. openai) |
max_input_tokens | int | Maximum input token count |
ModelInfo
Summary of an available model returned by client.get_available_models(). Also used as the element type of ModelPolicyContext.availableModels.
class ModelInfo(TypedDict, total=False):
value: str
displayName: str
isEnabled: bool
| Field | Type | Description |
|---|
value | str | Model identifier; usable as ModelPolicyResult.model or as the argument of client.set_model() |
displayName | str | Display name |
isEnabled | bool | undefined | Whether currently available; undefined is treated as available |
ModelPolicyTimeoutError
class ModelPolicyTimeoutError(Exception): ...
Thrown by the SDK when the resolve_model callback exceeds options.resolve_model_timeout_ms without returning. The query fails directly, with no fallback.
client.set_model()
async def set_model(self, model: str | None = None) -> None: ...
Switches the model in fixed-model mode at runtime. Takes effect on the next LLM call. Effective only in fixed-model mode; in dynamic-callback mode, calling it does not override the callback’s result. Valid model IDs: see ModelInfo.value.
client.get_available_models()
async def get_available_models(self) -> list[ModelInfo]: ...
Fetches the latest model list available to the current account in real time. Always returns the latest result, no caching; returns an empty array (does not throw) when the list cannot be fetched temporarily. In dynamic-callback mode, ModelPolicyContext.availableModels already carries the same up-to-date list, so calling this method explicitly is unnecessary.
client.list_byok_providers()
async def list_byok_providers(self) -> list[BYOKProviderInfo] | None: ...
Returns the BYOK provider/model catalog available to the current account as an array:
- Returns
None: the CLI does not support this API (graceful fallback, no exception).
- Returns an array (may be empty): the list of providers available to the current account (an empty array means the account has not enabled BYOK).
For field semantics, see BYOK Catalog Types.
Host-defined custom tool permission approval callback.
from collections.abc import Awaitable, Callable
from typing import Any
CanUseTool = Callable[
[str, dict[str, Any], ToolPermissionContext],
Awaitable[PermissionResult],
]
ToolPermissionContext
import asyncio
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ToolPermissionContext:
signal: asyncio.Event | None = None
suggestions: list[Any] = field(default_factory=list)
blocked_path: str | None = None
decision_reason: str | None = None
decision_reason_type: str | None = None
classifier_approvable: bool | None = None
title: str | None = None
display_name: str | None = None
description: str | None = None
tool_use_id: str | None = None
agent_id: str | None = None
exit_plan_mode: ExitPlanModeApprovalDetails | None = None
| Field | Type | Description |
|---|
signal | asyncio.Event | None | Set when cancelled |
suggestions | list[Any] | Permission update suggestions from the CLI |
blocked_path | str | None | File path triggering authorization (file-related scenarios only) |
decision_reason | str | None | Human-readable authorization reason from the CLI |
decision_reason_type | str | None | Permission reason classification |
classifier_approvable | bool | None | Whether the current call can be auto-approved by the runtime classifier |
title / display_name / description | str | None | Human-readable authorization text generated at runtime |
tool_use_id | str | None | This tool invocation’s ID |
agent_id | str | None | Subagent ID initiating the call |
exit_plan_mode | ExitPlanModeApprovalDetails | None | Approval details when exiting plan mode |
For full usage and examples, see Permission Control.
PermissionMode
PermissionMode = Literal[
"default",
"acceptEdits",
"bypassPermissions",
"yolo",
"plan",
"dontAsk",
"auto",
]
| Value | Meaning | Suitable for |
|---|
"default" | Standard permission behavior. Tool calls are handled by tools, allow / deny rules, dynamic approval, or runtime policy | Most interactive sessions |
"acceptEdits" | Automatically accepts file-edit operations; other sensitive operations still follow the permission flow | Sessions that are approved to modify the workspace |
"bypassPermissions" | Skips permission checks; must also set allow_dangerously_skip_permissions=True | Trusted automation or test environments |
"yolo" | Compatibility alias for "bypassPermissions"; must also set allow_dangerously_skip_permissions=True | Compatibility with older configs; not recommended for new code |
"plan" | Plan mode. Suitable for producing a plan first; by default it does not perform real changes | Planning, design, review |
"dontAsk" | Does not ask interactively; operations that are not pre-authorized or allowed by rules are denied | Non-interactive environments, or workflows that should fail instead of prompting |
"auto" | Runtime capability decides allow or deny automatically; safe workspace file edits may be auto-allowed | Reduce confirmation interruptions while retaining runtime judgment |
For more details on the permission chain, see Permission Control.
PermissionResult
Return value of CanUseTool.
from dataclasses import dataclass
from typing import Any, Literal
@dataclass
class PermissionResultAllow:
behavior: Literal["allow"] = "allow"
updated_input: dict[str, Any] | None = None
updated_permissions: list[PermissionUpdate | dict[str, Any]] | None = None
decision_classification: PermissionDecisionClassification | None = None
@dataclass
class PermissionResultDeny:
behavior: Literal["deny"] = "deny"
message: str = ""
interrupt: bool = False
decision_classification: PermissionDecisionClassification | None = None
PermissionResult = PermissionResultAllow | PermissionResultDeny
allow.updated_input, when modified, replaces the actual parameters the tool receives. deny.interrupt=True denies and also interrupts the Agent.
The tool_name received by can_use_tool is the full tool name, for example "Bash", "Read", "mcp__orders__lookup_order".
McpServerConfig
MCP server configuration, passed to QoderAgentOptions.mcp_servers.
McpServerConfig = (
McpStdioServerConfig
| McpSSEServerConfig
| McpHttpServerConfig
| McpSdkServerConfig
)
McpStdioServerConfig
class McpStdioServerConfig(TypedDict):
type: NotRequired[Literal["stdio"]]
command: str
args: NotRequired[list[str]]
env: NotRequired[dict[str, str]]
tools: NotRequired[list[McpServerToolPolicy]]
McpSSEServerConfig
class McpSSEServerConfig(TypedDict):
type: Literal["sse"]
url: str
headers: NotRequired[dict[str, str]]
tools: NotRequired[list[McpServerToolPolicy]]
McpHttpServerConfig
class McpHttpServerConfig(TypedDict):
type: Literal["http"]
url: str
headers: NotRequired[dict[str, str]]
tools: NotRequired[list[McpServerToolPolicy]]
McpSdkServerConfig
class McpSdkServerConfig(TypedDict):
type: Literal["sdk"]
name: str
instance: McpServer
tools: NotRequired[list[McpServerToolPolicy]]
Returned by the create_sdk_mcp_server() factory; see MCP - In-Process Server.
class McpServerToolPolicy(TypedDict):
name: str
permission_policy: Literal["always_allow", "always_ask", "always_deny"]
SdkPluginConfig
Load local plugins.
class SdkPluginConfig(TypedDict):
type: Literal["local"]
path: str
| Field | Type | Description |
|---|
type | "local" | Currently only local is supported |
path | str | Absolute or relative path to the plugin directory |
SettingSource
Controls which filesystem settings are loaded.
SettingSource = Literal["user", "project", "local"]
| Value | Meaning | Location |
|---|
"user" | User-level global settings | ~/.qoder/settings.json |
"project" | Project shared settings (version controlled) | .qoder/settings.json |
"local" | Project local settings (gitignored) | .qoder/settings.local.json |
When omitted, all sources are loaded per CLI defaults; pass [] to skip entirely.
This page summarizes the stable tool-related APIs, the built-in tool list, and type definitions. For usage paths and scenarios, see Tools Usage Guide.
Note: The Python SDK currently does not export the built-in tool input / output type collections from the TypeScript SDK, such as BashInput, FileReadInput, or ToolInputSchemas. The implementation status is explicitly noted in the relevant sections.
The TypeScript SDK provides options.toolConfig to configure the behavior of certain built-in tools:
type ToolConfig = {
askUserQuestion?: {
previewFormat?: "markdown" | "html";
};
};
The Python SDK does not currently export an equivalent QoderAgentOptions.tool_config field; AskUserQuestion can still be used as a runtime tool name in tools, allowed_tools, disallowed_tools, can_use_tool, and hook matchers.
In tools, allowed_tools, disallowed_tools, can_use_tool, hook matchers, and Agent tool allowlists, built-in tools use the runtime tool names in the table below.
| Category | Tool name | Description | Python SDK status |
|---|
| Command execution | Bash | Execute shell commands | Usable in permission / tool-scope configuration |
| File operations | Read | Read file contents | Usable in permission / tool-scope configuration |
| File operations | Edit | Edit files by string matching | Usable in permission / tool-scope configuration |
| File operations | Write | Create or overwrite files | Usable in permission / tool-scope configuration |
| Search | Glob | Search by filename pattern | Usable in permission / tool-scope configuration |
| Search | Grep | Search by content regex | Usable in permission / tool-scope configuration |
| Network | WebFetch | Fetch and process URL content | Usable in permission / tool-scope configuration |
| Network | WebSearch | Web search | Usable in permission / tool-scope configuration |
| Agent | Agent | Invoke a subagent | Usable in permission / tool-scope configuration |
| Interaction | AskUserQuestion | Ask the user a question | Usable in permission / tool-scope configuration |
| Notebook | NotebookEdit | Edit notebook cells | Usable in permission / tool-scope configuration |
| Background tasks | TaskOutput | Send output to a background task | Usable in permission / tool-scope configuration |
| Background tasks | TaskStop | Stop a background task | Usable in permission / tool-scope configuration |
| Plan / worktree | ExitPlanMode | Exit plan mode | Usable in permission / tool-scope configuration |
| Plan / worktree | EnterWorktree | Enter a git worktree | Usable in permission / tool-scope configuration |
| Plan / worktree | ExitWorktree | Exit a worktree | Usable in permission / tool-scope configuration |
| Config | Config | Read or write configuration | Usable in permission / tool-scope configuration |
| Todo | TodoWrite | Manage todo items | Usable in permission / tool-scope configuration |
| MCP resources | ListMcpResources | List MCP resources | Usable in permission / tool-scope configuration |
| MCP resources | ReadMcpResource | Read an MCP resource | Usable in permission / tool-scope configuration |
| MCP invocation | Mcp | Generic MCP tool call | Usable in permission / tool-scope configuration |
Custom MCP tool names use this format:
mcp__{serverName}__{toolName}
Creates an SDK MCP tool definition. The Python version is decorator-style; the handler is wrapped via @tool(...).
from collections.abc import Awaitable, Callable
from typing import Any
from mcp.types import ToolAnnotations
def tool(
name: str,
description: str,
input_schema: type | dict[str, Any],
annotations: ToolAnnotations | None = None,
) -> Callable[[Callable[..., Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]:
...
| Parameter | Type | Required | Semantics | Current Qoder behavior |
|---|
name | str | Yes | Unique identifier of the tool within the current MCP server | Forms the model-visible full tool name mcp__{serverName}__{name}; required to be non-empty at registration |
description | str | Yes | Tool description shown to the model — when to use it, what it does, what it returns | Passed through to the tool list and directly affects whether the model invokes it correctly; required to be non-empty at registration |
input_schema | type | dict[str, Any] | Yes | Defines the tool’s input parameters | The SDK generates an MCP input schema; supports a simple dict, a TypedDict, or a complete JSON Schema dict |
annotations | ToolAnnotations | None | No | Additional tool metadata | The SDK registers annotations on the MCP server; does not replace permission configuration |
tool() itself only defines the tool; the decorated async handler is the function executed when the tool is called. Registration constraints such as name, description, and duplicate tool names are validated by create_sdk_mcp_server() when the tool is registered.
The Python SDK does not have the TypeScript SDK’s AnyZodRawShape / InferShape. The Python version of input_schema supports the following forms:
# Simple dict: all fields required
{"query": str, "limit": int}
# Use Annotated to add a field description
{"query": Annotated[str, "Search keywords"]}
# TypedDict: supports NotRequired
class SearchInput(TypedDict):
query: str
limit: NotRequired[int]
# Complete JSON Schema dict
{
"type": "object",
"properties": {
"query": {"type": "string"},
"source": {"type": "string", "enum": ["docs", "wiki"]},
},
"required": ["query"],
}
Common Python type conversions:
| Python form | JSON Schema semantics |
|---|
str | {"type": "string"} |
int | {"type": "integer"} |
float | {"type": "number"} |
bool | {"type": "boolean"} |
list[T] | array, with items |
dict | object |
Annotated[T, "..."] | adds description to T’s schema |
TypedDict | object; generates required based on required / NotRequired |
TypeScript-only schema helper types
| TypeScript reference type | Python SDK status | Python equivalent capability |
|---|
AnyZodRawShape | Not implemented / not exported | Use dict[str, type], TypedDict, or a complete JSON Schema dict |
InferShape | Not implemented / not exported | The handler receives an args dict; if static typing is needed, declare your own TypedDict in business code |
ToolExtras | Not implemented / not exported | In Python, annotations is passed directly as the 4th argument to @tool() |
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, Generic, TypeVar
from mcp.types import ToolAnnotations
T = TypeVar("T")
@dataclass
class SdkMcpTool(Generic[T]):
name: str
description: str
input_schema: type[T] | dict[str, Any]
handler: Callable[..., Awaitable[dict[str, Any]]]
annotations: ToolAnnotations | None = None
The @tool() decorator returns an SdkMcpTool. You typically do not need to construct one manually.
ToolInvocationContext
import asyncio
from dataclasses import dataclass, field
@dataclass
class ToolInvocationContext:
signal: asyncio.Event = field(default_factory=asyncio.Event)
The handler may take one or two parameters:
@tool("watch", "Watch a counter.", {"max": int})
async def watch(args, extra: ToolInvocationContext):
...
When the handler accepts a second positional parameter, the SDK passes a ToolInvocationContext. extra.signal is set when the CLI cancels an in-flight tool call.
The Python version directly uses mcp.types.ToolAnnotations.
from mcp.types import ToolAnnotations
ToolAnnotations(
title="Search docs",
readOnlyHint=True,
destructiveHint=False,
idempotentHint=True,
openWorldHint=False,
maxResultSizeChars=500_000,
)
| Field | Type | Optional | Semantics | Current Qoder behavior |
|---|
title | str | Yes | Human-readable title of the tool | MCP metadata; not currently a verified Qoder behavior capability |
readOnlyHint | bool | Yes | Marks the tool as not modifying state | The observable effect today is that read-only tools are eligible for concurrent execution within the same batch of tool calls; not a permission switch |
destructiveHint | bool | Yes | Marks the tool as potentially performing destructive updates | Risk metadata; does not currently auto-block authorized tool execution |
openWorldHint | bool | Yes | Marks whether the tool interacts with an open external world | External-interaction metadata; does not currently auto-block authorized tool execution |
maxResultSizeChars | int | Yes | Marks the tool’s result-size cap | The Python SDK writes it into _meta["anthropic/maxResultSizeChars"] for the CLI to read; this is the MCP type extension field currently used in Python |
These fields are metadata and scheduling hints, not permission switches. Whether execution is allowed is still determined by tools, allowed_tools, disallowed_tools, permission_mode, can_use_tool, and hooks.
create_sdk_mcp_server()
Creates an MCP server that runs in the same process as the SDK.
from typing import Any
def create_sdk_mcp_server(
name: str,
version: str = "1.0.0",
tools: list[SdkMcpTool[Any]] | None = None,
) -> McpSdkServerConfig:
...
| Parameter | Default | Description |
|---|
name | Required | MCP server name; used in mcp__{serverName}__{toolName} |
version | "1.0.0" | Server version info |
tools | None | List of tools registered to this server |
CreateSdkMcpServerOptions
The TypeScript SDK uses a CreateSdkMcpServerOptions object parameter; the Python SDK does not export this type and does not use an options object. The Python equivalent is the three function parameters of create_sdk_mcp_server(name, version="1.0.0", tools=None).
Return Value
Returns an McpSdkServerConfig that can be used directly as a value in QoderAgentOptions.mcp_servers.
from typing import Literal, TypedDict
from typing_extensions import NotRequired
class McpSdkServerConfig(TypedDict):
type: Literal["sdk"]
name: str
instance: McpServer
tools: NotRequired[list[McpServerToolPolicy]]
class McpServerToolPolicy(TypedDict):
name: str
permission_policy: Literal["always_allow", "always_ask", "always_deny"]
The tools policy field exists in the Python types. It is primarily used for tool permission policy at the MCP server configuration layer; common in-process SDK server integrations are still controlled through allowed_tools, disallowed_tools, permission_mode, can_use_tool, and hooks.
The Python SDK does not export its own CallToolResult type. Handlers return a dict, and the SDK converts it into the MCP CallToolResult.
from typing import Literal, TypedDict
from typing_extensions import NotRequired
class TextToolContent(TypedDict):
type: Literal["text"]
text: str
class ImageToolContent(TypedDict):
type: Literal["image"]
data: str
mimeType: str
class ResourceLinkToolContent(TypedDict):
type: Literal["resource_link"]
uri: str
name: NotRequired[str]
description: NotRequired[str]
mimeType: NotRequired[str]
class EmbeddedResourceValue(TypedDict, total=False):
uri: str
mimeType: str
text: str
blob: str
class EmbeddedResourceToolContent(TypedDict):
type: Literal["resource"]
resource: EmbeddedResourceValue
ToolContent = (
TextToolContent
| ImageToolContent
| ResourceLinkToolContent
| EmbeddedResourceToolContent
)
class ToolHandlerResult(TypedDict):
content: list[ToolContent]
is_error: NotRequired[bool]
McpToolResultContent
The Python SDK currently recognizes the following content blocks:
| Type | Python handler dict | Current Qoder behavior |
|---|
| Text | {"type": "text", "text": "..."} | Converted to TextContent |
| Image | {"type": "image", "data": "...", "mimeType": "image/png"} | Converted to ImageContent |
| Resource link | {"type": "resource_link", "uri": "...", "name": "...", "description": "..."} | Downgraded to TextContent, concatenating name / uri / description |
| Embedded text resource | {"type": "resource", "resource": {"text": "..."}} | Converted to TextContent |
| Embedded binary resource | {"type": "resource", "resource": {"blob": "..."}} | Currently skipped, with a warning logged |
Differences from the TS reference:
- The Python handler uses
is_error, not the MCP / TypeScript isError field name; the SDK maps it to MCP isError internally.
- Top-level
_meta on the Python handler is not currently passed through to CallToolResult.
- The Python
call_tool conversion logic does not currently handle the audio content block; even though MCP types import AudioContent, a handler returning {"type": "audio"} will fall into the unsupported-content warning branch.
The tool permission approval callback is defined in the common types and is repeated here for ease of reference.
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import Any, Literal
@dataclass
class ToolPermissionContext:
signal: asyncio.Event | None = None
suggestions: list[Any] = field(default_factory=list)
blocked_path: str | None = None
decision_reason: str | None = None
decision_reason_type: str | None = None
classifier_approvable: bool | None = None
title: str | None = None
display_name: str | None = None
description: str | None = None
tool_use_id: str | None = None
agent_id: str | None = None
exit_plan_mode: ExitPlanModeApprovalDetails | None = None
CanUseTool = Callable[
[str, dict[str, Any], ToolPermissionContext],
Awaitable[PermissionResult],
]
PermissionResult
@dataclass
class PermissionResultAllow:
behavior: Literal["allow"] = "allow"
updated_input: dict[str, Any] | None = None
updated_permissions: list[PermissionUpdate | dict[str, Any]] | None = None
decision_classification: PermissionDecisionClassification | None = None
@dataclass
class PermissionResultDeny:
behavior: Literal["deny"] = "deny"
message: str = ""
interrupt: bool = False
decision_classification: PermissionDecisionClassification | None = None
PermissionResult = PermissionResultAllow | PermissionResultDeny
The tool_name received by can_use_tool is the full tool name, for example Bash, Read, mcp__orders__lookup_order.
The Python SDK exports McpToolInfo and McpToolAnnotations to describe the per-server tool information returned by QoderSDKClient.get_mcp_status().
from typing import TypedDict
from typing_extensions import NotRequired
class McpToolAnnotations(TypedDict, total=False):
readOnly: bool
destructive: bool
openWorld: bool
class McpToolInfo(TypedDict):
name: str
description: NotRequired[str]
annotations: NotRequired[McpToolAnnotations]
Note: the annotation field names in status are the CLI-projected readOnly, destructive, openWorld, not the readOnlyHint, destructiveHint, openWorldHint from the ToolAnnotations input. idempotentHint is not currently echoed in the status tool list.
The TypeScript SDK provides input / output structures for built-in tools at the type level. The Python SDK currently does not export these TypedDicts, nor does it export ToolInputSchemas / ToolOutputSchemas union types. Note: the type names in the table below are the TypeScript reference type names; Python permission configuration and tool allowlists still use the runtime tool names from Built-in Tool List.
| TypeScript type | Python SDK status |
|---|
AgentInput / AgentOutput | Not exported |
BashInput / BashOutput | Not exported |
FileReadInput / FileReadOutput | Not exported; runtime tool name remains Read |
FileEditInput / FileEditOutput | Not exported; runtime tool name remains Edit |
FileWriteInput / FileWriteOutput | Not exported; runtime tool name remains Write |
GlobInput / GlobOutput | Not exported |
GrepInput / GrepOutput | Not exported |
WebFetchInput / WebFetchOutput | Not exported |
WebSearchInput / WebSearchOutput | Not exported |
AskUserQuestionInput / AskUserQuestionOutput | Not exported |
NotebookEditInput / NotebookEditOutput | Not exported |
TaskOutputInput | Not exported |
TaskStopInput / TaskStopOutput | Not exported |
ExitPlanModeInput / ExitPlanModeOutput | Not exported |
ConfigInput / ConfigOutput | Not exported |
EnterWorktreeInput / EnterWorktreeOutput | Not exported |
ExitWorktreeInput / ExitWorktreeOutput | Not exported |
TodoWriteInput / TodoWriteOutput | Not exported |
ListMcpResourcesInput / ListMcpResourcesOutput | Not exported |
ReadMcpResourceInput | Not exported |
McpInput / McpOutput | Not exported |
ToolInputSchemas | Not exported |
ToolOutputSchemas | Not exported |
What the Python side exposes as stable and configurable is the runtime tool names in Built-in Tool List. If you need strongly-typed built-in tool parameters in a Python application, define your own TypedDict or dataclass on the business side.
Hooks Reference
For usage guides and examples, see Hooks.
Event Overview
| Event | Trigger | Controllable behavior |
|---|
PreToolUse | Before a tool call | Block / allow / modify input |
PostToolUse | After a tool succeeds | Audit / inject context / override output |
PostToolUseFailure | After a tool fails | Error handling / logging |
UserPromptSubmit | Before a user prompt is sent | Inject context / block |
SessionStart | When a session starts | Initialize / inject context |
SessionEnd | When a session ends | Cleanup / logging |
Stop | When AI stops generating | Block stop and force continuation |
SubagentStart | When a subagent starts | Observe / log |
SubagentStop | When a subagent stops | Observe / log |
PreCompact | Before context compaction | Observe / log |
PostCompact | After context compaction | Observe / log |
CwdChanged | When the working directory changes | Observe / log |
InstructionsLoaded | When an instructions file is loaded | Observe / log |
FileChanged | When a file is created/modified/deleted | Observe / log |
PermissionRequest | When a permission is requested | Auto-approve / deny permission requests |
HookEvent
Union type of registrable hook events.
HookEvent = Literal[
"PreToolUse",
"PostToolUse",
"PostToolUseFailure",
"UserPromptSubmit",
"SessionStart",
"SessionEnd",
"Stop",
"SubagentStart",
"SubagentStop",
"PreCompact",
"PostCompact",
"CwdChanged",
"InstructionsLoaded",
"FileChanged",
"PermissionRequest",
]
HookCallback
HookCallback = Callable[
[HookInput, str | None, HookCallbackOptions],
Awaitable[HookJSONOutput],
]
HookMatcher
@dataclass
class HookMatcher:
hooks: list[HookCallback]
matcher: str | None = None
timeout: int | None = None # seconds, default 60
| Field | Type | Description |
|---|
hooks | list[HookCallback] | List of callbacks executed when matched |
matcher | str | None | Optional regex; filters by tool_name |
timeout | int | None | Optional timeout (seconds); default 60 |
Common input fields for all hook events.
@dataclass
class BaseHookInput:
hook_event_name: str
session_id: str
transcript_path: str
cwd: str
| Field | Type | Description |
|---|
hook_event_name | str | Event-type identifier (e.g. "PreToolUse") |
session_id | str | Unique identifier of the current session |
transcript_path | str | Path to the session transcript file (JSONL format) |
cwd | str | Current working directory of the session |
HookJSONOutput
Return type of a hook callback.
@dataclass
class HookJSONOutput:
continue_: bool | None = None # JSON key is "continue"
stop_reason: str | None = None # JSON key is "stopReason"
decision: str | None = None
reason: str | None = None
hook_specific_output: dict | None = None # JSON key is "hookSpecificOutput"
| Field | Type | Default | Description |
|---|
continue_ | bool | None | None (equivalent to True) | Set to False to terminate the session. Effective only for PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStop |
stop_reason | str | None | None | Human-readable reason for stopping the session (used with continue_=False) |
decision | str | None | None | "approve" or "block". "block" blocks tool execution; for the Stop event, "block" blocks stopping and forces continuation |
reason | str | None | None | Reason for the decision (shown to the model; for the "block" decision on a Stop event, injected as a continuation prompt into the context) |
hook_specific_output | dict | None | None | Event-specific output (see each event type) |
In the Python SDK, continue_ corresponds to the JSON key "continue" (to avoid the keyword conflict).
When multiple hooks return conflicting decision values, "deny" / "block" takes priority (the strictest rule wins).
@dataclass
class PreToolUseHookInput(BaseHookInput):
hook_event_name: Literal["PreToolUse"]
permission_mode: str | None
tool_name: str
tool_input: Any
| Field | Type | Description |
|---|
permission_mode | str | None | Current permission mode of the session |
tool_name | str | Name of the tool being invoked |
tool_input | Any | Parameters passed to the tool |
hook_specific_output (hookSpecificOutput):
| Field | Type | Description |
|---|
hookEventName | "PreToolUse" | Must be set |
permissionDecision | str | "allow" / "deny" / "ask" / "defer" |
permissionDecisionReason | str | Reason for the permission decision |
updatedInput | dict | Modified tool input that replaces the original tool_input |
additionalContext | str | Extra context injected into the model’s next turn |
PostToolUseHookInput
@dataclass
class PostToolUseHookInput(BaseHookInput):
hook_event_name: Literal["PostToolUse"]
tool_name: str
tool_input: Any
tool_response: Any
| Field | Type | Description |
|---|
tool_name | str | Name of the tool being invoked |
tool_input | Any | Parameters passed to the tool |
tool_response | Any | Result of tool execution |
Output behavior:
| Field | Location | Behavior |
|---|
hookSpecificOutput.updatedToolOutput | Event-specific output | Overrides tool_response; the model only sees the overridden value |
hookSpecificOutput.additionalContext | Event-specific output | Appends supplemental context without modifying the original result |
decision: "block" + reason | Top-level output | Prevents the agent from further processing the tool result |
hook_specific_output (hookSpecificOutput):
| Field | Type | Description |
|---|
hookEventName | "PostToolUse" | Must be set |
updatedToolOutput | str | Override the tool response content |
additionalContext | str | Extra context appended alongside the tool result |
When multiple hooks set updatedToolOutput, the last non-empty value wins. For chained transformations, perform them sequentially within a single callback.
PostToolUseFailureHookInput
@dataclass
class PostToolUseFailureHookInput(BaseHookInput):
hook_event_name: Literal["PostToolUseFailure"]
tool_name: str
tool_input: Any
error: str
is_interrupt: bool | None
| Field | Type | Description |
|---|
tool_name | str | Name of the failed tool |
tool_input | Any | Parameters passed to the tool |
error | str | Error message |
is_interrupt | bool | None | Whether caused by an interrupt/abort |
@dataclass
class UserPromptSubmitHookInput(BaseHookInput):
hook_event_name: Literal["UserPromptSubmit"]
prompt: str
| Field | Type | Description |
|---|
prompt | str | Text entered by the user |
hook_specific_output (hookSpecificOutput):
| Field | Type | Description |
|---|
hookEventName | "UserPromptSubmit" | Must be set |
additionalContext | str | Extra context appended to the user prompt |
@dataclass
class SessionStartHookInput(BaseHookInput):
hook_event_name: Literal["SessionStart"]
source: str
| Field | Type | Description |
|---|
source | str | Reason for starting the session: "startup" / "resume" / "clear" / "compact" |
hook_specific_output (hookSpecificOutput):
| Field | Type | Description |
|---|
hookEventName | "SessionStart" | Must be set |
additionalContext | str | Context injected at the start of the session |
@dataclass
class SessionEndHookInput(BaseHookInput):
hook_event_name: Literal["SessionEnd"]
reason: str
| Field | Type | Description |
|---|
reason | str | Reason the session ended: "clear" / "resume" / "logout" / "prompt_input_exit" / "other" / "bypass_permissions_disabled" |
@dataclass
class StopHookInput(BaseHookInput):
hook_event_name: Literal["Stop"]
stop_hook_active: bool
| Field | Type | Description |
|---|
stop_hook_active | bool | Whether a Stop hook is currently blocking stopping |
Returning {"decision": "block", "reason": "..."} blocks the AI from stopping and forces continuation. reason is injected into the model context as a continuation prompt.
@dataclass
class SubagentStartHookInput(BaseHookInput):
hook_event_name: Literal["SubagentStart"]
agent_id: str
agent_type: str
| Field | Type | Description |
|---|
agent_id | str | Unique identifier of the subagent instance |
agent_type | str | Type / role of the subagent |
@dataclass
class SubagentStopHookInput(BaseHookInput):
hook_event_name: Literal["SubagentStop"]
stop_hook_active: bool
| Field | Type | Description |
|---|
stop_hook_active | bool | Whether a Stop hook is currently blocking stopping |
@dataclass
class PreCompactHookInput(BaseHookInput):
hook_event_name: Literal["PreCompact"]
trigger: str
custom_instructions: str | None
| Field | Type | Description |
|---|
trigger | str | Trigger reason: "manual" / "auto" |
custom_instructions | str | None | Custom instructions for the compaction summary |
PostCompactHookInput
@dataclass
class PostCompactHookInput(BaseHookInput):
hook_event_name: Literal["PostCompact"]
trigger: str
compact_summary: str
| Field | Type | Description |
|---|
trigger | str | Trigger reason: "manual" / "auto" |
compact_summary | str | Summary generated after context compaction |
@dataclass
class CwdChangedHookInput(BaseHookInput):
hook_event_name: Literal["CwdChanged"]
old_cwd: str
new_cwd: str
| Field | Type | Description |
|---|
old_cwd | str | Working directory before the change |
new_cwd | str | Working directory after the change |
@dataclass
class InstructionsLoadedHookInput(BaseHookInput):
hook_event_name: Literal["InstructionsLoaded"]
load_reason: str
| Field | Type | Description |
|---|
load_reason | str | Load reason: "nested_traversal" / "path_glob_match" |
@dataclass
class FileChangedHookInput(BaseHookInput):
hook_event_name: Literal["FileChanged"]
file_path: str
event: str
| Field | Type | Description |
|---|
file_path | str | Path of the changed file |
event | str | Filesystem event: "change" / "add" / "unlink" |
@dataclass
class PermissionRequestHookInput(BaseHookInput):
hook_event_name: Literal["PermissionRequest"]
tool_name: str
tool_input: Any
permission_suggestions: list | None
| Field | Type | Description |
|---|
tool_name | str | Tool requesting permission |
tool_input | Any | Tool input parameters |
permission_suggestions | list | None | Suggested permission rules |
hook_specific_output (hookSpecificOutput):
| Field | Type | Description |
|---|
hookEventName | "PermissionRequest" | Must be set |
decision | dict | Permission decision (see below) |
decision is one of:
- Approve:
{"behavior": "allow", "updatedInput": {...}, "updatedPermissions": [...]}
- Deny:
{"behavior": "deny", "message": "..."}
Message Types
AssistantMessage
The complete AI reply, delivered once per turn. content is a list of TextBlock and ToolUseBlock.
@dataclass
class AssistantMessage:
content: list[TextBlock | ToolUseBlock]
parent_tool_use_id: str | None = None
session_id: str | None = None
uuid: str | None = None
ResultMessage
The final message at the end of the entire session.
@dataclass
class ResultMessage:
subtype: str # "success" | "error_max_turns" | "error_during_execution" | ...
duration_ms: int
num_turns: int
session_id: str
total_cost_usd: float | None = None
result: str | None = None # only present when subtype == "success"
SystemMessage
Session system message. When subtype == "init", data carries initialization information (session_id, model, tools, etc.).
@dataclass
class SystemMessage:
subtype: str # "init" | "compact_boundary" | "status" | "mcp_status_change" | ...
data: dict[str, Any]
StreamEvent
Requires include_partial_messages=True; streamed token-by-token.
@dataclass
class StreamEvent:
uuid: str
session_id: str
event: dict[str, Any] # upstream-compatible raw stream event
parent_tool_use_id: str | None = None
event["type"] values:
event["type"] | Description |
|---|
message_start | Message begins |
content_block_start | Block starts (text / tool_use / thinking) |
content_block_delta | Increment: text_delta / input_json_delta / thinking_delta |
content_block_stop | Block ends |
message_delta | Message-level state change (e.g. stop_reason) |
message_stop | Complete turn ends |
For full usage, see Streaming Output.
Content Blocks
Elements in AssistantMessage.content:
@dataclass
class TextBlock:
text: str
@dataclass
class ToolUseBlock:
id: str
name: str
input: dict[str, Any]