Cortex Code Agent SDK reference – Python

This topic provides the complete API reference for the Cortex Code Agent SDK for Python, including all functions, classes, and types.

Installation

pip install cortex-code-agent-sdk

Requires Python 3.10 or later. Dependencies: anyio, mcp, typing_extensions. Import the package as cortex_code_agent_sdk. The SDK expects the Cortex Code CLI to be installed separately. If it is not on your PATH, set CORTEX_CODE_CLI_PATH=/path/to/cortex or pass cli_path in CortexCodeAgentOptions.

Functions

query()

The primary function for interacting with Cortex Code. Returns an async iterator that yields messages as they arrive.

async def query(
    *,
    prompt: str | AsyncIterable[dict],
    options: CortexCodeAgentOptions | None = None,
    transport: Transport | None = None,
) -> AsyncIterator[Message]: ...

Parameters

ParameterTypeDescription
promptstr | AsyncIterable[dict]User prompt string, or async iterable of message dicts for streaming input
optionsCortexCodeAgentOptions | NoneConfiguration options. Defaults to CortexCodeAgentOptions()
transportTransport | NoneCustom transport. Defaults to subprocess CLI transport

Returns

An async iterator yielding Message objects (AssistantMessage, ResultMessage, UserMessage, SystemMessage, StreamEvent).

Example

import asyncio
from cortex_code_agent_sdk import query, AssistantMessage, ResultMessage, CortexCodeAgentOptions

async def main():
    async for message in query(
        prompt="Fix the bug in utils.py",
        options=CortexCodeAgentOptions(cwd="/path/to/project"),
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if hasattr(block, "text"):
                    print(block.text, end="")
        elif isinstance(message, ResultMessage):
            print(f"\nDone: {message.subtype}")

asyncio.run(main())

CortexCodeSDKClient

For multi-turn interactive conversations. Supports the async context manager protocol.

from cortex_code_agent_sdk import CortexCodeSDKClient, CortexCodeAgentOptions, AssistantMessage

async with CortexCodeSDKClient(CortexCodeAgentOptions(permission_mode="bypassPermissions", allow_dangerously_skip_permissions=True)) as client:
    # First turn
    await client.query("What files are in this directory?")
    async for msg in client.receive_response():
        if isinstance(msg, AssistantMessage):
            for b in msg.content:
                if hasattr(b, "text"):
                    print(b.text, end="")

    # Second turn (context preserved)
    await client.query("Now refactor the main function")
    async for msg in client.receive_response():
        if isinstance(msg, AssistantMessage):
            for b in msg.content:
                if hasattr(b, "text"):
                    print(b.text, end="")

Methods

MethodDescription
connect()Connect to the CLI process and keep the session open for later turns.
query(prompt, session_id?)Send a prompt to the agent
receive_messages()Yield all messages from the agent
receive_response()Yield messages until a ResultMessage (inclusive)
interrupt()Send interrupt signal
set_permission_mode(mode)Change the permission mode for later turns in the conversation
set_model(model)Change model during conversation
stop_task(task_id)Stop a running subagent task
disconnect()Disconnect from the CLI process

Options

Configuration passed to query() or CortexCodeSDKClient.

OptionTypeDefaultDescription
cwdstr | Path | NoneNoneWorking directory for the session
modelstr | NoneNoneModel to use. Use "auto" for automatic selection, or a specific identifier like "claude-sonnet-4-6". See Supported models.
connectionstr | NoneNoneSnowflake connection name from Snowflake CLI connection settings, typically ~/.snowflake/connections.toml, with ~/.snowflake/config.toml also supported for existing setups. If omitted, the CLI uses default_connection_name from the TOML file. See Configuring connections.
profilestr | NoneNoneProfile name (loads from ~/.snowflake/cortex/profiles/)
permission_modePermissionMode | NoneNone"default" | "autoAcceptPlans" | "plan" | "bypassPermissions". Note: "bypassPermissions" requires allow_dangerously_skip_permissions=True. In "plan" mode, AskUserQuestion and ExitPlanMode can be routed through can_use_tool; denying ExitPlanMode keeps planning active, and approving it exits plan mode so later turns resume normal permissions.
allow_dangerously_skip_permissionsboolFalseSafety flag required when using permission_mode="bypassPermissions". This flag alone does not bypass permissions; it only allows the bypass when explicitly requested via permission_mode.
allowed_toolslist[str][]Tools to auto-approve without prompting
disallowed_toolslist[str][]Tools to always deny
max_turnsint | NoneNoneLimit the number of agent turns per query
effortstr | NoneNoneModel effort level: "minimal" | "low" | "medium" | "high" | "max"
system_promptstr | SystemPromptPreset | NoneNoneCustom system prompt. Pass a string for a full override, or {"type": "preset", "append": "extra text"} to append to the default prompt.
continue_conversationboolFalseContinue the most recent session instead of starting a new one
resumestr | NoneNoneSession ID to resume a previous conversation
fork_sessionboolFalseWhen resuming, fork to a new session ID instead of continuing the previous session
add_dirslist[str | Path][]Additional directories to add to the agent’s context
envdict[str, str]{}Environment variables to pass to the CLI process
pluginslist[SdkPluginConfig][]Plugin configurations. Currently supports local plugins: {"type": "local", "path": "/path/to/plugin"}
abort_eventasyncio.Event | NoneNoneAn asyncio.Event that, when set, sends an interrupt to the running agent. The session stays alive for further prompts. Python equivalent of AbortController in the TypeScript SDK.
mcp_serversdict[str, McpServerConfig]{}External MCP server configurations. Pass a dict that maps server names to stdio, HTTP, or SSE configs. The current SDK transport supports dict-based MCP configuration only.
hooksdict | NoneNoneHook event handlers (see Hooks)
can_use_toolCanUseTool | NoneNoneCustom tool permission callback (see Tool permissions)
include_partial_messagesboolFalseInclude token-level streaming events (StreamEvent)
output_formatdict | NoneNoneStructured output format. Example: {"type": "json_schema", "schema": {...}}
no_mcpboolFalseDisable MCP servers
session_idstr | NoneNoneExplicit session ID to use
setting_sourceslist[str] | NoneNoneSetting sources to load: "user" | "project" | "local"
cli_pathstr | Path | Noneos.environ.get("CORTEX_CODE_CLI_PATH") or "cortex"Path to CLI binary. If omitted, the SDK first checks CORTEX_CODE_CLI_PATH and otherwise falls back to cortex on PATH.
extra_argsdict[str, str | None]{}Additional CLI flags as key-value pairs. Use None value for boolean flags.
stderrCallable[[str], None] | NoneNoneCallback invoked with each line of CLI stderr output

Message types

AssistantMessage

Emitted when the agent produces a response. Contains one or more content blocks.

@dataclass
class AssistantMessage:
    content: list[ContentBlock]
    model: str
    parent_tool_use_id: str | None = None
    error: AssistantMessageError | None = None

AssistantMessageError is one of: "authentication_failed", "billing_error", "rate_limit", "invalid_request", "server_error", "unknown".

ResultMessage

Emitted when the agent finishes a turn. Check subtype and is_error for success or failure.

@dataclass
class ResultMessage:
    subtype: str
    duration_ms: int
    duration_api_ms: int
    is_error: bool
    num_turns: int
    session_id: str
    stop_reason: str | None = None
    total_cost_usd: float | None = None
    usage: dict[str, Any] | None = None
    result: str | None = None
    structured_output: Any = None

UserMessage

Echoed back when a user message is processed.

@dataclass
class UserMessage:
    content: str | list[ContentBlock]
    uuid: str | None = None
    parent_tool_use_id: str | None = None
    tool_use_result: dict[str, Any] | None = None

SystemMessage

System events such as session initialization and task updates.

@dataclass
class SystemMessage:
    subtype: str
    data: dict[str, Any]

The SDK also provides specialized subclasses for task-related system messages:

SubclassDescription
TaskStartedMessageEmitted when a subagent task starts. Fields: task_id, description, uuid, session_id, tool_use_id, task_type.
TaskProgressMessageEmitted while a task is running. Fields: task_id, description, usage (TaskUsage), uuid, session_id, last_tool_name.
TaskNotificationMessageEmitted when a task completes, fails, or is stopped. Fields: task_id, status ("completed" | "failed" | "stopped"), output_file, summary, uuid, session_id, usage.

These subclasses extend SystemMessage, so existing isinstance(msg, SystemMessage) checks continue to match.

StreamEvent

Partial message updates during token-level streaming. Requires include_partial_messages=True.

@dataclass
class StreamEvent:
    uuid: str
    session_id: str
    event: dict[str, Any]   # Partial text/thinking stream event from Cortex Code
    parent_tool_use_id: str | None = None

StreamEvent is emitted for partial text and thinking blocks. Complete tool calls still arrive as AssistantMessage content blocks, and tool results still arrive as UserMessage content blocks.

Content blocks

TypeFields
TextBlock.type = "text", .text: str
ThinkingBlock.type = "thinking", .thinking: str, .signature: str
ToolUseBlock.type = "tool_use", .id: str, .name: str, .input: dict
ToolResultBlock.type = "tool_result", .tool_use_id: str, .content: str | list | None, .is_error: bool | None

Hooks

Hooks let you intercept lifecycle events for logging, validation, or custom behavior.

Configuring hooks

Hooks are configured via the hooks option on CortexCodeAgentOptions. Each hook event maps to a list of HookMatcher objects.

from cortex_code_agent_sdk import CortexCodeAgentOptions, HookMatcher

async def my_pre_tool_hook(input_data, tool_use_id, context):
    print(f"Tool {input_data['tool_name']} about to run")
    return {"continue_": True}

options = CortexCodeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(
                matcher="Bash",          # Only match Bash tool, or None for all
                hooks=[my_pre_tool_hook],
                timeout=30.0,            # Timeout in seconds (default: 60)
            ),
        ],
    },
)

Hook callback signature

HookCallback = Callable[
    [HookInput, str | None, HookContext],
    Awaitable[HookJSONOutput],
]
  • input: Strongly-typed hook input (see table below)
  • tool_use_id: Optional tool use identifier
  • context: HookContext with a signal field (reserved for future abort signal support)

Hook events

EventInput TypeDescription
PreToolUsePreToolUseHookInputBefore a tool is executed. Fields: tool_name, tool_input, tool_use_id.
PostToolUsePostToolUseHookInputAfter a tool completes. Fields: tool_name, tool_input, tool_response, tool_use_id.
UserPromptSubmitUserPromptSubmitHookInputWhen user submits a prompt. Fields: prompt.
StopStopHookInputWhen the agent stops. Fields: stop_hook_active.
SubagentStopSubagentStopHookInputWhen a subagent finishes. Fields: stop_hook_active.
NotificationNotificationHookInputOn notification events. Fields: message, notification_type.
PermissionRequestPermissionRequestHookInputWhen a tool requests permission. Fields: tool_name, tool_input.
PreCompactPreCompactHookInputBefore context compaction. Fields: trigger ("manual" | "auto"), custom_instructions.

All hook inputs include base fields: session_id, transcript_path, cwd, and optionally permission_mode.

Hook output

Hook callbacks return a synchronous output object:

# Synchronous output
SyncHookJSONOutput = TypedDict("SyncHookJSONOutput", {
    "continue_": bool,               # Whether agent should proceed (default: True)
    "stopReason": str,                # Message shown when continue_ is False
    "decision": Literal["block"],     # Block the action
    "reason": str,                    # Feedback for the agent
    "hookSpecificOutput": ...,        # Event-specific controls
}, total=False)

Note

The Python SDK uses continue_ (with a trailing underscore) to avoid the Python keyword conflict. This is automatically converted to continue when sent to the CLI.

Tool permissions

The can_use_tool callback lets you programmatically control tool permissions.

For many ordinary tool permission checks, the callback input contains fields such as {"action": ..., "resource": ...}. The allow/deny result and optional deny message are used for these checks. updated_input is used for SDK-routed pseudo-tools such as AskUserQuestion and ExitPlanMode, which contain tool-specific fields.

from typing import Any

from cortex_code_agent_sdk import (
    CortexCodeAgentOptions,
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)

async def my_permission_handler(
    tool_name: str,
    tool_input: dict[str, Any],
    context: ToolPermissionContext,
) -> PermissionResultAllow | PermissionResultDeny:
    if tool_name == "Write" and str(tool_input.get("resource", "")).endswith(".env"):
        return PermissionResultDeny(message="Writing env files is not allowed")
    return PermissionResultAllow()

options = CortexCodeAgentOptions(can_use_tool=my_permission_handler)

Types

CanUseTool = Callable[
    [str, dict[str, Any], ToolPermissionContext],
    Awaitable[PermissionResult],
]

@dataclass
class ToolPermissionContext:
    signal: Any | None = None
    blocked_path: str | None = None
    decision_reason: str | None = None
    tool_use_id: str = ""
    agent_id: str | None = None

@dataclass
class PermissionResultAllow:
    behavior: Literal["allow"] = "allow"
    updated_input: dict[str, Any] | None = None
    tool_use_id: str | None = None

@dataclass
class PermissionResultDeny:
    behavior: Literal["deny"] = "deny"
    message: str = ""
    interrupt: bool = False
    tool_use_id: str | None = None

MCP server configuration

The mcp_servers option accepts a dict mapping server names to external MCP server configurations:

Config typeDescription
McpStdioServerConfigExternal process via stdio. Fields: command, args, env.
McpSSEServerConfigServer-sent events. Fields: type="sse", url, headers.
McpHttpServerConfigHTTP transport. Fields: type="http", url, headers.

Example

options = CortexCodeAgentOptions(
    mcp_servers={
        "external": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
         },
     },
 )

Error handling

from cortex_code_agent_sdk import (
    query, CortexCodeAgentOptions, ResultMessage,
    CortexCodeSDKError, CLINotFoundError, CLIConnectionError,
)

try:
    async for message in query(prompt="...", options=CortexCodeAgentOptions()):
        if isinstance(message, ResultMessage) and message.is_error:
            print(f"Agent error ({message.subtype}): {message.result}")
except CLINotFoundError:
    print("Cortex CLI not found. Is it installed and on PATH?")
except CLIConnectionError:
    print("Failed to connect to CLI process")
except CortexCodeSDKError as e:
    print(f"SDK error: {e}")

Error types

ExceptionDescription
CortexCodeSDKErrorBase exception for all SDK errors
CLINotFoundErrorCLI binary not found on PATH
CLIConnectionErrorFailed to connect to or communicate with CLI
ProcessErrorCLI process exited unexpectedly
CLIJSONDecodeErrorFailed to parse JSON from CLI output

Legal notices

Where your configuration of Cortex Code uses a model provided on the Model and Service Pass-Through Terms, your use of that model is further subject to the terms for that model on that page.

The data classification of inputs and outputs are as set forth in the following table.

Input data classificationOutput data classificationDesignation
Usage DataCustomer DataCovered AI Features [1]

For additional information, refer to Snowflake AI and ML.