> ## 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.

# 外置会话存储

qodercli 默认把会话历史保存在运行它的机器上。如果服务运行在多台机器、容器或 Serverless 环境中，下一次请求可能由另一台机器处理，导致之前的会话无法继续。

外置会话存储让应用把会话历史同步到自己的共享存储中。之后，无论请求落在哪台机器上，都可以通过 session ID 继续原来的会话。

适合使用外置存储的场景包括：

* 服务有多个实例，请求可能在实例之间切换
* 容器或 Serverless 环境的本地磁盘不可靠
* 需要自行管理会话数据的权限、加密、备份或保留周期

如果应用始终运行在同一台机器上，通常不需要配置外置存储。

<div id="工作方式" />

## 工作方式

```text theme={null}
first request
  query()
    -> qodercli saves the session locally
    -> the SDK sends new session history to your store

later request
  query(..., resume=session_id)
    -> the SDK reads session history from your store
    -> qodercli continues the session
```

外置存储是本地会话的额外副本，不会关闭 qodercli 的本地会话记录。应用不需要理解或管理 qodercli 的本地文件格式。

<div id="快速开始" />

## 快速开始

SDK 提供 `InMemorySessionStore`，用于在接入真实存储前验证流程。它只把数据保存在当前进程中，进程退出后数据会丢失。

```python theme={null}
from qoder_agent_sdk import (
    InMemorySessionStore,
    QoderAgentOptions,
    ResultMessage,
    qodercli_auth,
    query,
)

store = InMemorySessionStore()
session_id = None

options = QoderAgentOptions(
    auth=qodercli_auth(),
    cwd="/path/to/project",
    session_store=store,
)
async for message in query(prompt="Inspect this project.", options=options):
    if isinstance(message, ResultMessage):
        session_id = message.session_id
```

继续会话时，把记录的 `session_id` 传给 `resume`，并再次传入同一个 store：

```python theme={null}
resume_options = QoderAgentOptions(
    auth=qodercli_auth(),
    cwd="/path/to/project",
    session_store=store,
    resume=session_id,
)
```

不同机器必须使用相同的 `cwd`，SDK 才能把请求识别为同一个项目。

<div id="接入自己的存储" />

## 接入自己的存储

SDK 不提供可直接用于生产的外置存储实现。应用需要实现 `SessionStore` 协议，把数据写入自己选择的共享存储。

最小实现只需要两个异步方法：

| 方法                     | 应用需要完成的工作                      |
| ---------------------- | ------------------------------ |
| `append(key, entries)` | 按顺序保存 SDK 提供的新增会话数据            |
| `load(key)`            | 返回该 key 下的全部会话数据；不存在时返回 `None` |

以下方法按需实现：

| 方法                           | 实现后支持的能力                                        |
| ---------------------------- | ----------------------------------------------- |
| `list_sessions(project_key)` | 使用 `continue_conversation=True` 恢复最近会话，以及列出外置会话 |
| `delete(key)`                | 从外置存储删除会话                                       |
| `list_subkeys(key)`          | 完整恢复和查看子代理产生的会话数据                               |

把 `key` 和 `entries` 当作 SDK 提供的不透明数据即可。应用负责保存和原样返回，不需要解析其中的消息内容。

<div id="存储实现检查清单" />

## 存储实现检查清单

只有负责开发 SessionStore 的人员需要关注以下约束：

* 同一个 key 的数据必须保持追加顺序，`load` 返回完整历史。
* 不同 key 的数据必须相互隔离。
* SDK 可能重试失败的 `append`，生产实现需要考虑重复提交。
* `list_sessions` 的 `mtime` 使用 Unix 毫秒时间戳，并且只返回主会话。
* 删除主会话时，`delete` 还需要删除该会话的子数据。
* `list_subkeys` 只返回相对标识，不要返回绝对路径或包含 `.`、`..` 的路径。
* 多个进程可能同时写入同一会话时，存储端需要保证写入顺序。

可以使用 `qoder_agent_sdk.testing` 中的 `run_session_store_conformance` 检查通用行为，并为实际存储补充并发和重试测试。连接管理、权限、加密、备份、迁移和数据保留由应用负责。

<div id="在应用中使用" />

## 在应用中使用

每次需要保存或恢复外置会话时，都要在 `QoderAgentOptions.session_store` 中传入 store。

* 已知 session ID：使用 `resume`
* 恢复该项目最近的会话：使用 `continue_conversation=True`，并实现 `list_sessions`
* 管理外置会话：使用 `list_sessions_from_store`、`get_session_messages_from_store`、`rename_session_via_store`、`tag_session_via_store`、`fork_session_via_store` 或 `delete_session_via_store`
* 迁移已有本地会话：使用 `import_session_to_store`

<div id="限制与运维" />

## 限制与运维

* 外置写入失败不会中断当前对话。最终失败时，SDK 会产生 `SDKMirrorErrorMessage`；对数据完整性有要求的应用应监控该消息。
* 外置读取默认等待 60 秒，可以通过 `load_timeout_ms` 调整。
* `session_store_flush` 默认为 `"batched"`；`"eager"` 写入更及时，但会增加存储请求。
* 外置存储中找不到显式指定的 session ID 时，SDK 仍会尝试使用本机上的同 ID 会话。
* Python SDK 不能把外置会话存储与文件 checkpoint、自定义 transport 或实验性的 Cloud Agent runtime 同时使用，并且只支持内置 subprocess transport。
* SessionStore 只保存会话历史，不保存鉴权状态、应用配置、文件 checkpoint 或数据保留策略。
