Conversations

View as Markdown

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.

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

Create a Conversation

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

1const runtime = await bctrl.runtimes.create({});
2
3const conversation = await bctrl.conversations.create({
4 runtimeId: runtime.id,
5});
6
7console.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

ParameterTypeRequiredDescription
runtimeIdstringYesActive Runtime where the Agent works
modelstringNoModel ID from the AI model catalog.
toolsetIdstringNoToolset available during turns
titlestringNoHuman-readable Conversation title

Send a message

Send a message to start an Agent turn:

1const turn = await bctrl.conversations.messages.create(conversation.id, {
2 text: "Open the checkout page and tell me which payment options are shown.",
3});
4
5console.log(turn.turnId);
6console.log(turn.runId); // Run created for this turn
7console.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

ParameterTypeRequiredDescription
textstringYesMessage text, up to 100,000 characters
modelstringNoOverride with a model ID from the AI model catalog.
pageIdstringNoBrowser page to use as the turn’s starting page
fileIdsstring[]NoFiles to attach to the message; up to 50

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

Use the AI models page to discover models and configure provider credentials. Use Tools to make additional capabilities available to the turn.

Follow a turn

Stream normalized Conversation events while the Agent works:

1for await (const event of bctrl.conversations.events.stream(
2 conversation.id,
3 { after: turn.streamCursor },
4)) {
5 switch (event.type) {
6 case "turn.progress":
7 console.log(event.text);
8 break;
9 case "message.delta":
10 process.stdout.write(event.text);
11 break;
12 case "tool.started":
13 console.log("Tool:", event.tool);
14 break;
15 case "input.required":
16 console.log("The Agent needs input:", event.prompt);
17 break;
18 case "turn.completed":
19 case "turn.failed":
20 case "turn.cancelled":
21 case "turn.timed_out":
22 console.log("Turn ended:", event.type);
23 break;
24 }
25}

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

EventMeaning
turn.startedThe turn started and exposes its runId and spanId
turn.progressThe Agent reported progress text
message.startedAn Agent message started
message.deltaA partial message was produced
message.completedAn Agent message is complete
tool.startedA Tool call started
tool.progressA Tool reported progress
tool.requires_inputA Tool call is waiting for input
tool.completedA Tool call completed
tool.failedA Tool call failed
input.requiredThe Agent is waiting for a human response
input.respondedThe requested input was received
turn.completedThe turn completed successfully
turn.failedThe turn failed
turn.cancelledThe turn was cancelled
turn.timed_outThe turn exceeded its time limit

Read and manage Conversations

1const detail = await bctrl.conversations.get(conversation.id);
2console.log(detail.messages);
3
4for await (const item of bctrl.conversations.iter({
5 runtimeId: runtime.id,
6 status: "idle",
7})) {
8 console.log(item.id, item.title, item.updatedAt);
9}
10
11const cancelled = await bctrl.conversations.cancel(conversation.id);
12console.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

FieldTypeAlways presentDescription
idstringYesUnique Conversation identifier.
runtimeIdstringYesRuntime where turns execute.
modelstringYesModel used by default.
toolsetIdstring | nullYesToolset available to turns, if configured.
titlestring | nullYesOptional human-readable title.
status"idle" | "active"YesWhether a turn is running.
activeTurnIdstring | nullYesCurrent turn, if one is active.
createdAtstringYesCreation timestamp.
updatedAtstringYesLast update timestamp.
1type Message = {
2 id: string;
3 conversationId: string;
4 sequence: number;
5 role: "system" | "user" | "assistant";
6 text: string;
7 fileIds: string[];
8 model: string | null;
9 runId: string | null;
10 turnId: string | null;
11 spanId: string | null;
12 metadata: Record<string, unknown> | null;
13 createdAt: string;
14};

Conversation, Run, and View

These resources answer different questions:

ResourceAnswers
ConversationWhat has the user and Agent said?
RunWhat happened during one execution?
ViewWhat can a person see or control right now?

Use the runId on an accepted turn or Message to open the execution record in Runs. Use Views when you want to present the live Runtime or its completed recording to a person.