> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# External Session Storage

By default, qodercli keeps session history on the machine where it runs. In a multi-instance, container, or serverless deployment, the next request may reach a different machine that cannot read the earlier session.

External session storage keeps a **mirror** of each session in storage that your application controls. The mirror is an additional copy: qodercli still writes the session locally, and any host can continue a session later by using its session ID.

Use external storage when:

* requests can move between service instances
* local disks are temporary or unreliable
* your application must control access, encryption, backups, or retention

For an application that always runs on one machine, local session storage is usually enough.

<div id="how-it-works" />

## How it works

```text theme={null}
first request
  query()
    -> qodercli writes the session locally
    -> the SDK mirrors new session entries to your store

later request, on any host
  query({ resume: sessionId })
    -> the SDK loads session history from your store
    -> qodercli continues the session
```

Two properties follow from this design:

* **Writes are best-effort.** The SDK mirrors entries in the background. A failed write is reported but never stops the running conversation; see [Operations](#operations).
* **Entries are opaque.** Your store persists and returns SDK-defined transcript entries as-is. Your application does not need to understand qodercli's local file format.

<div id="quick-start" />

## Quick start

`InMemorySessionStore` keeps data in the current process only. Use it to validate the wiring — that queries save to and resume from a store — before you connect a shared backend. It cannot demonstrate cross-host resume, because the data is gone when the process exits; for that, implement a real store. See [Implementing a store](#implementing-a-store).

```typescript theme={null}
import {
  InMemorySessionStore,
  qodercliAuth,
  query,
} from '@qoder-ai/qoder-agent-sdk';

const store = new InMemorySessionStore();
const options = {
  auth: qodercliAuth(),
  cwd: '/path/to/project',
  sessionStore: store,
};

// First query: run and capture the session ID.
let sessionId: string | undefined;
for await (const message of query({
  prompt: 'Remember the number 42.',
  options,
})) {
  if (message.type === 'result') sessionId = message.session_id;
}
if (!sessionId) throw new Error('The first query did not return a session ID.');

// Later query: resume from the store and the model recalls prior context.
for await (const message of query({
  prompt: 'What number did I ask you to remember?',
  options: { ...options, resume: sessionId },
})) {
  if (message.type === 'result') console.log(message.result); // -> "42"
}
```

Every host must use the same `cwd`, so the SDK identifies requests as belonging to the same project.

<div id="using-a-store" />

## Using a store in your application

Pass the store in `options.sessionStore` for every query that should mirror or restore a session, then choose how to select the session:

* **Known session ID** — pass `resume: sessionId`.
* **Most recent session for the project** — pass `continue: true`. This requires the store to implement `listSessions`.

<div id="managing-stored-sessions" />

### Managing stored sessions

The session-management functions accept the same `sessionStore` option and operate on the store instead of local files:

```typescript theme={null}
import {
  listSessions,
  getSessionInfo,
  getSessionMessages,
  listSubagents,
  getSubagentMessages,
  renameSession,
  tagSession,
  forkSession,
  deleteSession,
} from '@qoder-ai/qoder-agent-sdk';

const project = { dir: '/path/to/project', sessionStore: store };
const sessions = await listSessions(project);
const messages = await getSessionMessages(sessionId, project);
await renameSession(sessionId, 'Investigation #1', project);
await deleteSession(sessionId, project);
```

`listSessions` requires the store to implement `listSessions`; `listSubagents` requires `listSubkeys`; `deleteSession` requires `delete`. `getSubagentMessages` also uses `listSubkeys` when it is available.

To copy an existing local session into the store — for example, when migrating a host that previously ran without external storage — use `importSessionToStore`:

```typescript theme={null}
import { importSessionToStore } from '@qoder-ai/qoder-agent-sdk';

await importSessionToStore(sessionId, store, { dir: '/path/to/project' });
```

<div id="implementing-a-store" />

## Implementing a store

The SDK does not ship a production-ready store; your application implements the `SessionStore` interface against shared storage of its choice. For runnable Redis and PostgreSQL reference implementations that show the usage, see the [TypeScript samples](https://github.com/QoderAI/qoder-agent-sdk-samples/tree/main/typescript/external-session-storage) or [Python samples](https://github.com/QoderAI/qoder-agent-sdk-samples/tree/main/python/external-session-storage). They are starting points, not production-ready packages.

```typescript theme={null}
type SessionKey = {
  projectKey: string; // derived from cwd; identifies the project
  sessionId: string; // the session UUID
  subpath?: string; // set for subagent transcripts, e.g. "subagents/agent-<id>"
};

type SessionStoreEntry = {
  type: string;
  uuid?: string;
  timestamp?: string;
  [key: string]: unknown; // opaque transcript line — store and return unchanged
};

interface SessionStore {
  // Required
  append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
  load(key: SessionKey): Promise<SessionStoreEntry[] | null>;
  // Optional — implement only the features you need
  listSessions?(
    projectKey: string,
  ): Promise<Array<{ sessionId: string; mtime: number }>>;
  delete?(key: SessionKey): Promise<void>;
  listSubkeys?(key: Omit<SessionKey, 'subpath'>): Promise<string[]>;
}
```

A `SessionKey` identifies one transcript. A main session has no `subpath`; each subagent transcript reuses the same `projectKey` and `sessionId` with a distinct `subpath`. Treat keys and entries as opaque — store and return them without interpreting the message content.

The two required methods provide save and resume. Each optional method unlocks a feature:

| Method           | Feature enabled                                                       |
| ---------------- | --------------------------------------------------------------------- |
| `append`, `load` | Mirror a session and resume it by ID — required                       |
| `listSessions`   | Resume the latest session with `continue: true`; list stored sessions |
| `delete`         | Delete a session from the store                                       |
| `listSubkeys`    | Fully restore and inspect subagent transcripts                        |

<div id="implementation-checklist" />

### Implementation checklist

* **Preserve append order per key**, and return the complete history from `load`. Replay depends on order.
* **Isolate keys.** Never return one key's entries under another.
* **Make `append` idempotent.** The SDK may retry a failed write with the same entries, so a retry must not duplicate history.
* **Return Unix epoch milliseconds** in `listSessions().mtime`, and list main sessions only — those without a `subpath`.
* **Cascade deletes.** Deleting a main session must also delete its subagent transcripts.
* **Return relative identifiers from `listSubkeys`** — never absolute paths, and never a path containing `.` or `..`. These become storage keys, and traversal segments would let a transcript escape its namespace.
* **Serialize concurrent writers** for the same session in the storage layer if more than one process can write it.

Run the `SessionStore` conformance tests in the SDK repository to check this common behavior, and add backend-specific concurrency and retry tests. Connection management, access control, encryption, backup, migrations, and retention remain your application's responsibility.

<div id="operations" />

## Operations

**Failure handling.** An external write failure does not stop the current conversation. After the final retry fails, the SDK emits a `system/mirror_error` message. Monitor it when mirror completeness matters — the conversation can succeed even when a write does not.

**Tuning.**

* External reads wait up to 60 seconds by default. Configure this with `loadTimeoutMs`.
* `sessionStoreFlush` defaults to `'batched'`. Set it to `'eager'` to mirror entries sooner, at the cost of more storage requests.

**Constraints.**

* If an explicitly resumed session is missing from the store, the SDK can still fall back to a local session with the same ID.
* `sessionStore` cannot be combined with `persistSession: false`, file checkpointing, a custom transport provider, or the experimental Cloud Agent runtime.
* The store is supported on the built-in Process and Worker transports.
* A store holds session history only. It does not hold authentication state, application configuration, file checkpoints, or retention policy.
