The easiest way to create one is to trigger a chat.agent task,
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions._index/route.tsx
index 1d193d2979..3d2bbea6d9 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions._index/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions._index/route.tsx
@@ -129,10 +129,12 @@ function SessionsHelpTooltip() {
What is a session?
- A session is a pair of streams: input for incoming user messages, and output for
- everything the agent produces, including AI generation parts (text, reasoning, tool
- calls, etc.) and any custom data parts your task emits. Sessions also orchestrate the
- execution of agent runs, so a single conversation can span many task triggers.
+ A session is a stateful execution of an agent, with two-way streaming and durable
+ compute. A single session can have multiple runs associated with it, so one
+ conversation can span many task triggers. The input stream carries incoming user
+ messages, and the output stream carries everything the agent produces, including AI
+ generation parts (text, reasoning, tool calls, etc.) and any custom data parts your
+ task emits.
diff --git a/docs/ai-chat/how-it-works.mdx b/docs/ai-chat/how-it-works.mdx
index b77e1cb0a3..9980d935ff 100644
--- a/docs/ai-chat/how-it-works.mdx
+++ b/docs/ai-chat/how-it-works.mdx
@@ -14,9 +14,9 @@ This page explains how `chat.agent` is put together, what each piece does on a s
**What you don't have to think about**: SSE reconnects, WebSocket backpressure, container cold starts, whether a worker is currently running, or how to re-deliver chunks the client missed during a reload. The platform handles those. **What you do have to think about**: idempotency in your `run()` function, and how much state you keep in memory between turns versus persist in your own database.
-## The primary noun: a chat session is a pair of streams and a task
+## The primary noun: the chat session
-A **chat session** is the unit chat.agent owns. It is three things bound together:
+A **chat session** is a stateful execution of an agent: two-way streaming plus durable compute, able to span multiple runs. It is the unit chat.agent owns, and it is three things bound together:
- An **inbox** channel called `.in` — every user message lands here as a record.
- An **outbox** channel called `.out` — every assistant chunk leaves through here.
diff --git a/docs/ai-chat/sessions.mdx b/docs/ai-chat/sessions.mdx
index f003b06f7e..65786f42e3 100644
--- a/docs/ai-chat/sessions.mdx
+++ b/docs/ai-chat/sessions.mdx
@@ -1,16 +1,18 @@
---
title: "Sessions"
sidebarTitle: "Sessions"
-description: "A Session is a pair of durable streams — input carries your users' messages to the agent, output carries everything the agent produces back — plus orchestration of the runs that process them."
+description: "A Session is a stateful execution of an agent, with two-way streaming and durable compute. A single Session can have multiple runs associated with it."
---
import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
-**A Session is a pair of durable streams.** The input stream (`.in`) carries incoming user messages to your task. The output stream (`.out`) carries everything the agent produces back to your clients: AI generation parts (text, reasoning, tool calls) and any custom data parts you write.
+**A Session is a stateful execution of an agent.** It includes two-way streaming and durable compute, and a single Session can have multiple runs associated with it.
-Sessions also **orchestrate the runs that process those streams**. A Session is keyed on your stable id (`externalId` — for chat, the `chatId`) and owns its current run: when a run suspends, idles out, or hands off to a new version, the Session starts or swaps to a fresh run and the streams carry on. Clients keep sending and reading against the same id; they never know a run changed underneath.
+The **two-way streaming** is a pair of durable streams. The input stream (`.in`) carries incoming user messages to your task. The output stream (`.out`) carries everything the agent produces back to your clients: AI generation parts (text, reasoning, tool calls) and any custom data parts you write.
+
+The **durable compute** is the runs that process those streams. A Session is keyed on your stable id (`externalId` — for chat, the `chatId`) and owns its current run: when a run suspends, idles out, or hands off to a new version, the Session starts or swaps to a fresh run and the streams carry on. Clients keep sending and reading against the same id; they never know a run changed underneath.
```mermaid
flowchart LR
diff --git a/packages/trigger-sdk/src/v3/sessions.ts b/packages/trigger-sdk/src/v3/sessions.ts
index fc47a03791..d2a33c09fe 100644
--- a/packages/trigger-sdk/src/v3/sessions.ts
+++ b/packages/trigger-sdk/src/v3/sessions.ts
@@ -86,8 +86,9 @@ export function __setSessionStartImplForTests(impl: SessionStartImpl | undefined
}
/**
- * Start a {@link Session} — a durable, task-bound, bidirectional I/O
- * primitive. The server creates the row (idempotent on `externalId`)
+ * Start a {@link Session} — a stateful execution of an agent, with
+ * two-way streaming and durable compute, that can span multiple runs.
+ * The server creates the row (idempotent on `externalId`)
* and triggers the first run from `triggerConfig` in one round-trip.
* Returns the new run's id and a session-scoped public access token
* for browser-side use against `.in/append`, `.out` SSE, and