Session Lifecycle
What a conversation is, the events one prompt produces, and how to steer or cancel a run.
A conversation is one thread of messages inside an agent. Each agent has a root conversation, which prompt uses by default, and can hold more conversations. A conversation:
- Keeps the full history of messages and tool calls.
- Handles one run at a time, calling the model and tools until the model is done. Prompts that arrive during a run wait in a queue.
- Streams events to every client that watches it.
- Summarizes older messages when the context fills up.
- Survives sleep, crashes, and upgrades. See Architecture.
This client connects to the Quickstart agent, watches the root conversation, sends one prompt, and prints the events it receives:
import { createClient } from "rivetkit/client";
import type { registry } from "../quickstart/server";
const client = createClient<typeof registry>();
const conn = client.agent.getOrCreate(["user-123"]).connect();
// The text of the current answer printed so far.
let printed = "";
conn.on("pi.events", ({ events }) => {
for (const event of events) {
if (event.type === "run_start") console.log("[run]");
if (event.type === "message_update") {
for (const change of event.changes) {
if (change.type === "text_delta") {
process.stdout.write(change.delta);
printed += change.delta;
}
}
}
if (event.type === "message_end") {
// A short answer can arrive whole here, with no text_delta before it.
const message = event.entry.model?.[0];
if (message?.role === "assistant") {
const text = message.content
.flatMap((block) => (block.type === "text" ? [block.text] : []))
.join("");
process.stdout.write(text.slice(printed.length));
}
printed = "";
}
if (event.type === "tool_execution_start") console.log(`\n> ${event.toolName}`);
if (event.type === "compaction_start") console.log("\n[compacting]");
if (event.type === "run_end") console.log("\n[done]");
}
});
const root = await conn.harness.root();
await conn.conversation.watchEvents(root.id);
await conn.prompt("Write an isPalindrome function in TypeScript to palindrome.ts, and add tests for it in palindrome.test.ts.");
await conn.dispose();
Events
The conversation.watchEvents(id) action returns a snapshot of the conversation, including a partial answer. Then the agent sends pi.events to that connection only. Each frame has conversationId, seq, and an events array. A frame with seq 0 replaces everything the client had, and a gap in seq means call watchEvents again.
| Event type | When it fires |
|---|---|
run_start, run_end | A run starts or ends. |
turn_start, turn_end | A model response starts, or it and its tool calls are done. |
message_start, message_end | A message starts or is complete: your prompt, a model reply, or a tool result. |
message_update | The model streams output. Each item in changes has a type, such as text_delta, thinking_delta, or toolcall_delta. |
tool_execution_start, tool_execution_end | A tool starts or finishes. |
auto_retry_start, auto_retry_end | A failed model call is retried. |
compaction_start, compaction_end | Older messages are summarized to free up context. |
usage_changed | The conversation’s token and cost totals change. |
A short answer can arrive whole in message_end, with no text_delta before it, so print the rest of the text there. Events are not replayed. To read earlier messages, call conversation.context(id), which returns { messages }. To stop listening, call conversation.unwatchEvents(id).
Steer, follow up, or cancel
While a run is in progress, prompt(text, { whenBusy }) decides what happens to the new prompt:
whenBusy | What it does |
|---|---|
"followUp" | The default. The agent reads the message once it has no tool calls left, and the run continues. |
"steer" | The agent reads the message after its current tool calls finish, before its next model call. |
"reject" | The prompt fails with an error, and nothing is queued. |
On an idle conversation, the prompt runs right away. To cancel a run, call conversation.abort(id). It stops any command still running in the sandbox. Daytona can’t cancel a command, so it runs until it exits or times out.
Only abort stops a run. If prompt times out or its client disconnects, prompt rejects, and the run keeps going until it finishes. Send the prompt again with the same requestId to get its result.
Compaction and errors
- When the context fills up, the agent summarizes older messages and keeps going. Call
conversation.compact(id)to do it yourself. The conversation keeps working while the summary is written. - A failed model call is retried automatically. If it keeps failing,
promptreturns{ status: "unanswered", reason }, and the conversation is kept.
Next: Client SDK, how to call an agent from your backend or a browser.