> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://platform.bctrl.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://platform.bctrl.ai/_mcp/server.

# Conversations

> Persistent Agent message threads bound to a Runtime.

A **Conversation** is a durable message thread for an Agent. It is bound to a
Runtime, so the Agent works in the same automation environment as the rest of
your application.

Each message starts one Agent turn. A turn runs inside a Run, which gives you
the Run ID for trace, events, recording, and audit data. A Conversation keeps
the messages and turn state together so you can build a persistent automation
or chat experience.

```text
Space → Runtime → Conversation → message → Agent turn → Run
```

## Create a Conversation

Creating a Conversation requires a Runtime. The Runtime must be active:

```ts
const runtime = await bctrl.runtimes.create({});

const conversation = await bctrl.conversations.create({
  runtimeId: runtime.id,
});

console.log(conversation.id, conversation.status);
```

Conversations use BCTRL's hosted automation implementation internally. You can
choose a model, attach a Toolset, and give the Conversation a title.

### Create fields

| Parameter   | Type     | Required | Description                                                   |
| ----------- | -------- | -------- | ------------------------------------------------------------- |
| `runtimeId` | `string` | Yes      | Active Runtime where the Agent works                          |
| `model`     | `string` | No       | Model ID from the [AI model catalog](/sdk/ai#choose-a-model). |
| `toolsetId` | `string` | No       | Toolset available during turns                                |
| `title`     | `string` | No       | Human-readable Conversation title                             |

## Send a message

Send a message to start an Agent turn:

```ts
const turn = await bctrl.conversations.messages.create(conversation.id, {
  text: "Open the checkout page and tell me which payment options are shown.",
});

console.log(turn.turnId);
console.log(turn.runId);       // Run created for this turn
console.log(turn.streamCursor); // Start of the event stream
```

There can be only one active turn in a Conversation. Wait for the current turn
to finish before sending another message.

### Message fields

| Parameter | Type       | Required | Description                                                                   |
| --------- | ---------- | -------- | ----------------------------------------------------------------------------- |
| `text`    | `string`   | Yes      | Message text, up to 100,000 characters                                        |
| `model`   | `string`   | No       | Override with a model ID from the [AI model catalog](/sdk/ai#choose-a-model). |
| `pageId`  | `string`   | No       | Browser page to use as the turn's starting page                               |
| `fileIds` | `string[]` | No       | Files to attach to the message; up to 50                                      |

The accepted turn response contains `turnId`, `messageId`, `runId`, `spanId`,
and `streamCursor`.

Use the [AI models](/sdk/ai) page to discover models and configure provider
credentials. Use [Tools](/sdk/tools) to make additional capabilities available
to the turn.

## Follow a turn

Stream normalized Conversation events while the Agent works:

```ts
for await (const event of bctrl.conversations.events.stream(
  conversation.id,
  { after: turn.streamCursor },
)) {
  switch (event.type) {
    case "turn.progress":
      console.log(event.text);
      break;
    case "message.delta":
      process.stdout.write(event.text);
      break;
    case "tool.started":
      console.log("Tool:", event.tool);
      break;
    case "input.required":
      console.log("The Agent needs input:", event.prompt);
      break;
    case "turn.completed":
    case "turn.failed":
    case "turn.cancelled":
    case "turn.timed_out":
      console.log("Turn ended:", event.type);
      break;
  }
}
```

The stream includes turn progress, message deltas, Tool activity, input
requests, and terminal turn events. Pass the last event ID as `after` to
reconnect without starting over.

## Conversation events

| Event                 | Meaning                                               |
| --------------------- | ----------------------------------------------------- |
| `turn.started`        | The turn started and exposes its `runId` and `spanId` |
| `turn.progress`       | The Agent reported progress text                      |
| `message.started`     | An Agent message started                              |
| `message.delta`       | A partial message was produced                        |
| `message.completed`   | An Agent message is complete                          |
| `tool.started`        | A Tool call started                                   |
| `tool.progress`       | A Tool reported progress                              |
| `tool.requires_input` | A Tool call is waiting for input                      |
| `tool.completed`      | A Tool call completed                                 |
| `tool.failed`         | A Tool call failed                                    |
| `input.required`      | The Agent is waiting for a human response             |
| `input.responded`     | The requested input was received                      |
| `turn.completed`      | The turn completed successfully                       |
| `turn.failed`         | The turn failed                                       |
| `turn.cancelled`      | The turn was cancelled                                |
| `turn.timed_out`      | The turn exceeded its time limit                      |

## Read and manage Conversations

```ts
const detail = await bctrl.conversations.get(conversation.id);
console.log(detail.messages);

for await (const item of bctrl.conversations.iter({
  runtimeId: runtime.id,
  status: "idle",
})) {
  console.log(item.id, item.title, item.updatedAt);
}

const cancelled = await bctrl.conversations.cancel(conversation.id);
console.log(cancelled.cancelled);
```

Use `status: "active"` to find Conversations with a running turn, or
`status: "idle"` to find Conversations ready for another message.

## Conversation fields

| Field          | Type                 | Always present | Description                                |
| -------------- | -------------------- | -------------- | ------------------------------------------ |
| `id`           | `string`             | Yes            | Unique Conversation identifier.            |
| `runtimeId`    | `string`             | Yes            | Runtime where turns execute.               |
| `model`        | `string`             | Yes            | Model used by default.                     |
| `toolsetId`    | `string \| null`     | Yes            | Toolset available to turns, if configured. |
| `title`        | `string \| null`     | Yes            | Optional human-readable title.             |
| `status`       | `"idle" \| "active"` | Yes            | Whether a turn is running.                 |
| `activeTurnId` | `string \| null`     | Yes            | Current turn, if one is active.            |
| `createdAt`    | `string`             | Yes            | Creation timestamp.                        |
| `updatedAt`    | `string`             | Yes            | Last update timestamp.                     |

#### Message shape

```ts
type Message = {
  id: string;
  conversationId: string;
  sequence: number;
  role: "system" | "user" | "assistant";
  text: string;
  fileIds: string[];
  model: string | null;
  runId: string | null;
  turnId: string | null;
  spanId: string | null;
  metadata: Record<string, unknown> | null;
  createdAt: string;
};
```

## Conversation, Run, and View

These resources answer different questions:

| Resource         | Answers                                     |
| ---------------- | ------------------------------------------- |
| **Conversation** | What has the user and Agent said?           |
| **Run**          | What happened during one execution?         |
| **View**         | What can a person see or control right now? |

Use the `runId` on an accepted turn or Message to open the execution record in
[Runs](/sdk/runs). Use [Views](/sdk/views) when you want to present the live
Runtime or its completed recording to a person.