Multi-turn sessions and streaming input

This topic describes the main ways to send prompts to the Cortex Code Agent SDK: single-prompt queries when you want query() to manage the session lifecycle for you, streamed input for incremental prompt delivery, and multi-turn sessions for interactive conversations that maintain context across exchanges.

Input patterns

The SDK provides three input patterns:

Mode

When to use

API

Single prompt input

Send one prompt with query() and let the SDK manage the session lifecycle for you. Output still streams until the agent produces a ResultMessage.

query() in both TypeScript and Python

Streamed input

Send user messages incrementally from an async iterable instead of a single prompt string

query() in both TypeScript and Python, plus Query.streamInput() in TypeScript

Multi-turn session

Interactive conversations: send multiple prompts, maintain context, control the session lifecycle

createCortexCodeSession() (TypeScript) or CortexCodeSDKClient (Python)

Single-prompt queries

The query() function is the simplest way to use the SDK. It can send either a single prompt string or an async iterable of SDK user messages, and it streams back events until the agent produces a ResultMessage. In this mode, “single prompt” refers to the input pattern: output still streams normally.

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

for await (const message of query({
  prompt: "Explain how the auth module works",
  options: { cwd: process.cwd() },
})) {
  if (message.type === "assistant") {
    for (const block of message.content) {
      if (block.type === "text") process.stdout.write(block.text);
    }
  }
}

The query() function manages the full session lifecycle for you: it creates a session, sends the prompt, yields events, and closes the session when the result arrives or the iterator is exhausted.

This is different from maxTurns / max_turns. A single-prompt query still allows the agent to take as many internal turns as needed, subject to your configured turn limit. Setting maxTurns: 1 or max_turns=1 is a separate constraint that limits the agent to one internal turn and can produce an error_max_turns result.

Multi-turn sessions

For conversations that require multiple exchanges, use a session. The agent retains context between turns, so later prompts can reference files read, analysis performed, and topics discussed in earlier turns.

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

const session = await createCortexCodeSession({ cwd: process.cwd() });

// First turn
await session.send("Read the database connection module");
for await (const event of session.stream()) {
  if (event.type === "assistant") {
    for (const b of event.content) {
      if (b.type === "text") process.stdout.write(b.text);
    }
  }
  if (event.type === "result") break;
}

// Second turn -- context from the first turn is preserved
await session.send("What error handling patterns does it use?");
for await (const event of session.stream()) {
  if (event.type === "assistant") {
    for (const b of event.content) {
      if (b.type === "text") process.stdout.write(b.text);
    }
  }
  if (event.type === "result") break;
}

await session.close();

Session lifecycle

  1. Call createCortexCodeSession(options) to start a session. This spawns the CLI process.

  2. Call session.send(prompt) to send a user message.

  3. Iterate session.stream() to receive responses. Stop iterating when you see a result event.

  4. Repeat steps 2–3 for additional turns.

  5. Call session.close() to end the session and clean up the process.

Continue a previous session

You can resume a conversation from a previous session. The agent loads the prior conversation history and continues where it left off.

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

// Continue the most recent conversation
const session = await createCortexCodeSession({
  cwd: process.cwd(),
  continue: true,
});

await session.send("What were we working on?");
for await (const event of session.stream()) {
  if (event.type === "result") break;
}
await session.close();

You can also resume a specific session by ID:

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  resume: "previous-session-id",
});

Fork a session

Forking creates a new session that starts with the full conversation history of an existing session. The original session is not modified. This is useful for exploring alternative approaches without losing the original conversation.

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

const forked = await createCortexCodeSession({
  cwd: process.cwd(),
  resume: "previous-session-id",
  forkSession: true,
});

await forked.send("Let's try a different approach");
for await (const event of forked.stream()) {
  if (event.type === "result") break;
}
await forked.close();

Interrupt a turn

Your application can request an interrupt while the agent is processing a turn. This has the same effect as pressing Esc in the CLI. The session stays alive for further prompts.

Direct interrupt call

Call the interrupt() method to send an interrupt request directly:

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

const session = await createCortexCodeSession({ cwd: process.cwd() });

await session.send("Analyze every file in this large codebase");

// Interrupt after 10 seconds
setTimeout(() => session.interrupt(), 10_000);

for await (const event of session.stream()) {
  if (event.type === "result") break;
}

// Session is still alive -- send another prompt
await session.send("Just summarize the top-level structure instead");
for await (const event of session.stream()) {
  if (event.type === "result") break;
}

await session.close();

Abort controller / abort event

You can also pass an abort signal at session creation time. When the signal fires, the SDK automatically sends the same interrupt request.

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

const controller = new AbortController();

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  abortController: controller,
});

await session.send("Analyze every file in this large codebase");

// Trigger interrupt from anywhere
setTimeout(() => controller.abort(), 10_000);

for await (const event of session.stream()) {
  if (event.type === "result") break;
}
await session.close();

Note

In TypeScript, pass an AbortController whose abort() method triggers the interrupt request. In Python, pass an asyncio.Event whose set() method triggers the interrupt request. In both cases, the session stays alive after interruption.