# Building Chat and Triage Integrations with Cursor SDK Patterns

> Build chat and triage integrations with Cursor SDK patterns. Use durable patterns for multi-turn bots and resume patterns for scheduled jobs. Learn how to persist state across runs.

- Repository: [Cursor/plugins](https://github.com/cursor/plugins)
- Tags: how-to-guide
- Published: 2026-05-25

---

**Use the durable pattern with `Agent.create` for multi-turn chat bots, and the resume pattern with `Agent.resume` for scheduled triage jobs that persist state across runs.**

The Cursor TypeScript SDK (`@cursor/sdk`) provides a programmatic interface for running Cursor agents in automation workflows. According to the `cursor/plugins` repository, all integrations boil down to three invocation patterns—one-shot, durable, and resume—backed by reference files that encode best practices for runtime choice, authentication, and error handling.

## Understanding the Three Cursor SDK Invocation Patterns

The SDK supports three distinct patterns for invoking agents, each documented in [`cursor-sdk/skills/cursor-sdk/SKILL.md`](https://github.com/cursor/plugins/blob/main/cursor-sdk/skills/cursor-sdk/SKILL.md).

**One-shot pattern** uses `Agent.prompt()` for fire-and-forget tasks. The SDK creates a temporary agent, sends a single prompt, returns the result, and automatically disposes resources. This is ideal for quick prototypes or single-turn commands.

**Durable pattern** uses `Agent.create()` to initialize a long-lived agent instance. You call `agent.send()` to dispatch prompts and receive a `run` object, then stream events via `run.stream()` and finalize with `run.wait()`. This pattern requires explicit disposal using `await agent[Symbol.asyncDispose]()` or the `await using` syntax.

**Resume pattern** uses `Agent.resume()` to reconnect to a persisted agent using its `agentId`. This allows scheduled jobs or interrupted workflows to continue with full conversational context, making it the standard approach for triage automation.

## Building Chat Bots with the Durable Pattern

Chat-style bots require continuity across multiple turns, which maps to the durable pattern. The implementation in [`cursor-sdk/skills/cursor-sdk/references/patterns.md`](https://github.com/cursor/plugins/blob/main/cursor-sdk/skills/cursor-sdk/references/patterns.md) shows how to stream partial outputs while waiting for completion.

```typescript
import { Agent } from "@cursor/sdk";

await using agent = Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2" },
  cloud: { repos: [{ url: "https://github.com/your-org/repo", startingRef: "main" }] },
});

async function chat(userMsg: string) {
  const run = await agent.send(userMsg);
  
  // Stream partial assistant messages to stdout
  for await (const ev of run.stream()) {
    if (ev.type === "assistant") {
      process.stdout.write(ev.message.content?.[0]?.text ?? "");
    }
  }
  
  const result = await run.wait();
  if (result.status !== "finished") {
    console.error(`Run failed with status: ${result.status}`);
  }
}

```

The `await using` declaration ensures the agent disposes correctly even if streaming throws an exception. According to the streaming reference file ([`cursor-sdk/skills/cursor-sdk/references/streaming.md`](https://github.com/cursor/plugins/blob/main/cursor-sdk/skills/cursor-sdk/references/streaming.md)), valid event types include `assistant`, `tool_call`, and `status`.

## Implementing Triage Workflows with the Resume Pattern

Triage jobs that run on a schedule typically use the resume pattern to maintain continuity. The agent's `agentId` is persisted to a file or database between runs, allowing `Agent.resume()` to restore state.

```typescript
import { Agent, CursorAgentError } from "@cursor/sdk";
import { readFile, writeFile } from "node:fs/promises";

const STATE = "/var/lib/triage/state.json";

async function loadState(): Promise<{ agentId?: string }> {
  try { 
    return JSON.parse(await readFile(STATE, "utf-8")); 
  } catch { 
    return {}; 
  }
}

async function saveState(id: string) {
  await writeFile(STATE, JSON.stringify({ agentId: id }));
}

const { agentId } = await loadState();

await using agent = agentId
  ? Agent.resume(agentId, {
      apiKey: process.env.CURSOR_API_KEY!,
      model: { id: "composer-2" },
      cloud: { repos: [{ url: process.env.REPO_URL!, startingRef: "main" }] },
    })
  : Agent.create({
      apiKey: process.env.CURSOR_API_KEY!,
      model: { id: "composer-2" },
      cloud: { repos: [{ url: process.env.REPO_URL!, startingRef: "main" }] },
    });

console.log(`[triage] agent=${agent.agentId}`);

try {
  const run = await agent.send(
    "Triage new Linear tickets opened in the last 24h. Label, assign, comment with next steps."
  );
  const result = await run.wait();
  
  if (result.status === "error") {
    console.error(`[triage] run ${result.id} errored`);
  }
  
  await saveState(agent.agentId);
} catch (err) {
  if (err instanceof CursorAgentError && err.isRetryable) {
    console.error(`[triage] transient error: ${err.message}`);
    // Skip this tick; the next cron run will retry
  } else {
    throw err;
  }
}

```

This pattern appears in lines 92-138 of [`cursor-sdk/skills/cursor-sdk/references/patterns.md`](https://github.com/cursor/plugins/blob/main/cursor-sdk/skills/cursor-sdk/references/patterns.md). The code distinguishes between startup failures (`CursorAgentError`) and runtime failures (`result.status === "error"`), checking `err.isRetryable` to decide whether to retry or escalate.

## Adding External Tooling via MCP Servers

Both chat and triage integrations often need to query external services like Linear, GitHub, or Datadog. The SDK supports Model Context Protocol (MCP) servers declared inline in the agent options.

```typescript
const mcpServers = {
  linear: {
    type: "http",
    url: "https://mcp.linear.app/sse",
    headers: { Authorization: `Bearer ${process.env.LINEAR_API_KEY!}` },
  },
};

await using agent = Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2" },
  cloud: { repos: [{ url: process.env.REPO_URL!, startingRef: "main" }] },
  mcpServers,
});

```

As documented in [`cursor-sdk/skills/cursor-sdk/references/mcp.md`](https://github.com/cursor/plugins/blob/main/cursor-sdk/skills/cursor-sdk/references/mcp.md), these servers provide tool-call support that agents can invoke during triage or conversational workflows.

## Cross-Cutting Concerns and Reference Files

The `cursor/plugins` repository maintains several reference files that encode cross-cutting logic applicable to both chat and triage integrations:

- **[`runtime-choice.md`](https://github.com/cursor/plugins/blob/main/runtime-choice.md)**: Decides between `local: { cwd }` for development and `cloud: { repos }` for production workloads.
- **[`auth.md`](https://github.com/cursor/plugins/blob/main/auth.md)**: Details `CURSOR_API_KEY` handling, including service-account keys and common authentication errors.
- **[`error-handling.md`](https://github.com/cursor/plugins/blob/main/error-handling.md)**: Distinguishes `CursorAgentError` (thrown during agent startup) from `result.status === "error"` (thrown during run execution).
- **[`streaming.md`](https://github.com/cursor/plugins/blob/main/streaming.md)**: Catalogues event types and documents best practices for `run.stream()` consumption.

These files are located in `cursor-sdk/skills/cursor-sdk/references/` and are referenced throughout the SDK skill documentation.

## Summary

- **One-shot pattern (`Agent.prompt`)** works for quick, single-turn tasks but lacks continuity.
- **Durable pattern (`Agent.create`)** powers multi-turn chat bots; always dispose the agent explicitly.
- **Resume pattern (`Agent.resume`)** enables triage jobs to persist conversation state across cron runs by storing the `agentId`.
- **Error handling** must distinguish `CursorAgentError` (startup) from run-time errors and respect `isRetryable` flags.
- **MCP servers** extend agents with external API capabilities required for ticket triage and data retrieval.
- **Reference files** in `cursor-sdk/skills/cursor-sdk/references/` provide the canonical logic for runtime selection, auth, streaming, and error handling.

## Frequently Asked Questions

### What is the difference between `Agent.create` and `Agent.prompt`?

`Agent.create` returns a durable agent instance that maintains state across multiple `send()` calls and requires explicit disposal, while `Agent.prompt` is a static method that creates a temporary agent, sends one message, returns the result, and auto-disposes. Use `Agent.create` for chat bots and `Agent.prompt` for one-off scripts.

### How do I persist state for a triage bot between cron runs?

Persist the `agentId` string to a durable store (JSON file, database, or KV store) after each run. On the next invocation, read the stored ID and pass it to `Agent.resume(agentId, options)` instead of creating a new agent. This restores the previous conversation context and tool-call history.

### How should I handle retries when the SDK throws errors?

Catch `CursorAgentError` instances and check the `isRetryable` property. If true, log the transient failure and skip the current tick (the next cron run will retry). If false, escalate the error. For runtime errors, inspect `result.status === "error"` after `run.wait()` completes.

### When should I add MCP servers to my integration?

Add MCP servers when your chat or triage logic requires tool calls to external systems—such as creating Linear tickets, querying Datadog metrics, or fetching GitHub code owners. Define the `mcpServers` object in the options passed to `Agent.create` or `Agent.resume`, as shown in [`cursor-sdk/skills/cursor-sdk/references/mcp.md`](https://github.com/cursor/plugins/blob/main/cursor-sdk/skills/cursor-sdk/references/mcp.md).