---
title: "Session State"
description: "Persist and resume eve client sessions with durable session IDs and stream cursors."
---

The TypeScript client uses one durable session ID for messages, controls, and
streams. A `ClientSession` adds a local `streamIndex` cursor so reconnects do not
replay events already consumed by that handle.

## Read and persist state

Create the session with its first turn, then persist `session.state` after
consuming the response:

```ts
const { session, response } = await client.sessions.create({
  message: "Create a launch checklist.",
});

await response.result();
await saveSessionState(session.state);
```

The state is deliberately small:

```ts
interface ClientSessionState {
  sessionId: string;
  streamIndex: number;
}
```

It is a remote stream cursor, not a transcript. Persist events separately when
your application renders chat history.

## Resume a saved session

Attach a fixed handle to the saved ID and restore its cursor:

```ts
import type { ClientSessionState } from "eve/client";

const saved = (await loadSessionState()) as ClientSessionState;
const session = client.sessions.attach(saved.sessionId, {
  streamIndex: saved.streamIndex,
});

const response = await session.send("Now shorten it.");
console.log((await response.result()).message);
```

`attach()` performs no request. Every later operation targets exactly
`saved.sessionId`; it never follows or creates a replacement.

## Waiting, completed, and reset sessions

A session that emits `session.waiting` accepts another message through the same
handle. A terminal or reset session does not. Start a fresh conversation
explicitly with another `client.sessions.create(firstTurn)` call.

```ts
const reset = await session.reset({ reason: "Start over" });
const { session: fresh, response } = await client.sessions.create({ message: "Begin again." });
```

The old `session` remains pinned to its retired ID; `fresh` owns a different ID.

## Multiple sessions

Create one handle per conversation:

```ts
const { session: research, response: researchResponse } = await client.sessions.create({
  message: "Research competitors.",
});
const { session: support, response: supportResponse } = await client.sessions.create({
  message: "Draft a support reply.",
});

await Promise.all([researchResponse.result(), supportResponse.result()]);
await save("research", research.state);
await save("support", support.state);
```

The shared `Client` owns host, auth, headers, and redirect policy. Each
`ClientSession` owns its fixed ID and stream cursor.

## Reconnect an existing stream

`stream()` starts from the handle's saved cursor and advances it as events are
read:

```ts
const session = client.sessions.attach(saved.sessionId, {
  streamIndex: saved.streamIndex,
});

for await (const event of session.stream()) {
  console.log(event.type);
}
```

Use `send()` for new input. For explicit cursors, tail-relative reads, and
bounded catch-up, see [Streaming](./streaming#open-a-stream-manually).

## What to read next

- [Streaming](./streaming): stream events and reconnect by index
- [Sessions, runs & streaming](../../concepts/sessions-runs-and-streaming): the raw HTTP contract
- [eve channel](../../channels/eve): the ID-addressed routes
