Cortex Code Agent SDK quickstart

This topic walks you through building an AI agent that reads a data pipeline script, finds bugs, and fixes them automatically using the Cortex Code Agent SDK.

What you will do:

  1. Set up a project with the Cortex Code Agent SDK.

  2. Create a data pipeline script with some bugs.

  3. Run an agent that finds and fixes the bugs without manual intervention.

Prerequisites

  • Node.js 18+ (for TypeScript) or Python 3.10+ (for Python).

  • Snowflake connection configured through Snowflake CLI connection settings, typically in ~/.snowflake/connections.toml, with ~/.snowflake/config.toml also supported for existing setups (see Configuring connections):

    [my-connection]
    account = "myorg-myaccount"
    user = "myuser"
    authenticator = "externalbrowser"
    

Setup

1. Install the Cortex Code CLI

Install the CLI:

curl -LsS https://ai.snowflake.com/static/cc-scripts/install.sh | sh

Verify the installation:

cortex --version

2. Set up your project

Create and enter a project directory:

mkdir my-agent && cd my-agent

3. Install the SDK

npm init -y
npm install cortex-code-agent-sdk

Create a data pipeline script

Create a data pipeline script with some intentional bugs for the agent to fix:

import json

def load_results(rows):
    """Load query results into a list of campaign dicts."""
    return [
        {
            "campaign": row["campaign_name"],
            "impressions": row["impressions"],
            "clicks": row["clicks"],
            "conversions": row["conversions"],
        }
        for row in rows
    ]

def compute_conversion_rate(results):
    """Add conversion_rate (conversions / clicks) to each campaign."""
    for row in results:
        row["conversion_rate"] = row["conversions"] / row["clicks"]  # Bug: ZeroDivisionError when clicks is 0
    return results

def format_report(results):
    """Return a JSON summary with total conversions and the top campaign."""
    total = sum(r["conversions"] for r in results)
    top = max(results, key=lambda r: r["conversion_rate"])  # Bug: crashes on empty list
    return json.dumps({"total_conversions": total, "top_campaign": top["campaign"]})

This code has two issues:

  1. computeConversionRate / compute_conversion_rate divides by clicks without checking for zero, returning NaN or Infinity (TypeScript) or raising a ZeroDivisionError (Python) for campaigns with no clicks.

  2. formatReport / format_report calls max / reduce on the results list without checking whether it is empty, which raises a ValueError (Python) or TypeError (TypeScript) when there are no rows.

Build an agent that finds and fixes bugs

// agent.mjs
import { query } from "cortex-code-agent-sdk";

// Agentic loop: streams messages as the agent works
for await (const message of query({
  prompt: "Review report.ts for bugs in the data pipeline. Fix any issues you find.",
  options: {
    cwd: process.cwd(),
    connection: "my-connection",          // Snowflake CLI connection name
    allowedTools: ["Read", "Edit", "Bash"],  // Auto-approve these tools without prompting
  },
})) {
  // Print human-readable output
  if (message.type === "assistant") {
    for (const block of message.content) {
      if (block.type === "text") {
        process.stdout.write(block.text);  // Agent's reasoning
      } else if (block.type === "tool_use") {
        console.log(`Tool: ${block.name}`);  // Tool being called
      }
    }
  } else if (message.type === "result") {
    console.log(`\nDone: ${message.subtype}`);  // Final result
  }
}

This code has three main parts:

  1. query(): The main entry point that creates the agentic loop. It returns an async iterator that you consume in your language’s async loop syntax to stream messages as the agent works. See the full API in the TypeScript or Python reference.

  2. prompt: What you want the agent to do. It tells the agent what task to complete.

  3. options: Configuration for the agent. connection specifies which Snowflake CLI connection to authenticate with. allowedTools specifies which tools are auto-approved without prompting, and disallowedTools can block tools entirely. Other options include model, mcp_servers, and more.

The streaming loop runs as the agent thinks, calls tools, observes results, and decides what to do next. Each iteration yields a message: the agent’s reasoning, a tool call, a tool result, or the final outcome. The SDK handles the orchestration.

Run your agent

node agent.mjs

After running, check your report file. You’ll see defensive code handling empty results and zero-click campaigns. Your agent autonomously:

  1. Read the file to understand the code.

  2. Analyzed the logic and identified edge cases that would crash.

  3. Edited the file to add proper error handling.

Multi-turn conversations

For interactive sessions where you send multiple prompts with shared context, use the Client API:

import {
  createCortexCodeSession,
  type CortexCodeEvent,
} from "cortex-code-agent-sdk";

async function printResponse(stream: AsyncIterable<CortexCodeEvent>) {
  for await (const event of stream) {
    if (event.type === "assistant") {
      for (const block of event.content) {
        if (block.type === "text") process.stdout.write(block.text);
      }
    } else if (event.type === "result") {
      break;
    }
  }
}

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  connection: "my-connection",
});

// First turn
await session.send("Summarize what report.ts does and what data it expects.");
await printResponse(session.stream());

// Second turn (same session, remembers context)
await session.send("Now add type annotations to each function.");
await printResponse(session.stream());

await session.close();

Try other prompts

Now that your agent is set up, try some different prompts:

  • "Add comprehensive type hints to all functions in report.py"

  • "Write a SQL query that finds the top 10 campaigns by conversion rate"

  • "Add input validation to all functions in report.py"

  • "Create a README.md documenting the functions in report.py"

Key concepts

Permission modes

Permission modes control the level of human oversight for tool calls:

Mode

Behavior

Use case

"bypassPermissions" (with safety flag)

Runs every tool without prompts. Requires allowDangerouslySkipPermissions: true (TypeScript) or allow_dangerously_skip_permissions=True (Python).

Sandboxed CI, fully trusted environments

"default"

Uses standard permission checks. In SDK sessions, configure allowedTools, disallowedTools, or canUseTool to control permission-checked tools.

Controlled workflows with explicit permission policy

"autoAcceptPlans"

Auto-approves plan requests and plan-exit confirmations. It does not bypass ordinary tool permissions.

Specialized workflows that want plan approvals to proceed automatically

"plan"

Starts in planning; approving ExitPlanMode lets execution continue and later turns resume normal permissions

Code review, analysis

For granular control over individual tool calls, use the canUseTool callback. See Handle approvals and user input for details.

Next steps