# 用户隔离与会话存储 Agent 支持个人会话和项目会话。会话始终属于登录用户,项目归属可选;同一项目中的其他用户不会因此获得聊天记录或计算文件的访问权。 ## 身份与范围 | 范围 | 请求凭据 | Server 验证 | 工作空间 | | --- | --- | --- | --- | | 个人会话 | Bearer | `/api/v1/access-context` 验证用户 | 用户目录中的 `personal/` | | 项目会话 | Bearer、`X-Project-Id` | `/api/v1/agent-auth-context` 验证用户与项目权限 | 用户目录中的项目键目录 | 无项目头不等于匿名请求。生产环境始终验证 Bearer,验证失败不回退为本地用户。开发环境的无凭据请求使用同一个本地身份,不能用于多用户部署。 个人会话不取得项目业务凭据,调用依赖这些凭据的 TJWater API 会返回 `PROJECT_CONTEXT_REQUIRED`。任务执行和任务协作仍要求项目权限。个人记忆范围的键包含用户身份,避免所有个人会话共用一个记忆桶。 前端面板的“对话归属”支持个人会话和当前项目。切换会开启新对话,保留旧会话及其原归属,历史缓存按认证来源和范围区分。当前没有把已有会话搬入或移出项目的接口。 ## 文件结构 根目录由 `CONVERSATION_WORKSPACE_ROOT` 管理,默认是 `data/session-workspaces/`: ```text actor-<用户键>/ ├── personal/ │ └── session-<会话 ID 的 SHA-256>/ └── project-<项目键>/ └── session-<会话 ID 的 SHA-256>/ ├── chat/ │ ├── owner.json │ ├── messages.json │ ├── transcript.json │ └── learning.json └── workspace/ ├── files/ ├── tmp/ └── .cache/ ``` 个人和项目会话具有相同的内部结构。用户和项目键来自经过验证的身份,不从工具参数或浏览器路径直接取值。会话 ID 使用完整 SHA-256 形成目录名,原 ID 保存在 `chat/owner.json` 中。 `messages.json` 保留前端聊天状态,包括消息、工具展示和执行计划;`transcript.json` 是经过持久化策略处理的问答摘要,供历史搜索和学习使用,最多保留配置的轮数。摘要不是完整原生执行轨迹。 `learning.json` 保存该会话的学习检查、复盘进度。它与聊天记录共用归属校验和串行写入保护,位于宿主管理的 `chat/`,不向模型开放。 `data/session-metadata/` 是归属、标题、父会话、用途和任务身份的索引。它为会话 ID 冲突检查、列表及所有会话接口提供同一归属来源。新会话创建也会初始化对应的聊天目录。 历史索引在服务进程内按用户和范围缓存,创建、更新和删除同步维护缓存。同一数据根目录由一个 Agent 进程写入;修改底层索引文件后需要重启,当前不支持多个 Agent 进程并发写入同一目录。 ## 隔离边界 | 对象 | 当前边界 | | --- | --- | | 会话接口 | 先校验用户、个人或项目范围,再读取状态;无权访问统一返回未找到 | | 聊天目录 | 校验归属文件和规范路径;拒绝目录、记录文件中的符号链接 | | 聊天搜索 | 只扫描当前用户与范围的摘要目录 | | 原生文件工具 | 只允许当前原生会话的 `workspace/`;会话哈希不匹配时拒绝读取 | | Shell | 在 `workspace/files/` 运行,Landlock/seccomp 限制文件、网络和进程访问 | | 记忆 | 用户记忆按用户隔离;项目记忆在项目内共享;个人工作空间记忆按用户隔离 | | Skill | 按部署实例共享,不能当作用户私人文件夹 | | MCP | 连接配置与上游账号按实例管理,工具权限按会话应用;上游数据授权由 MCP 服务负责 | | OpenCode 数据库 | 仍是实例共享的原生数据库,访问依赖服务密码和可信 Agent 路由 | `chat/` 是计算目录的同级目录,目录权限为 `0700`,记录文件为 `0600`。模型和 Shell 不能直接读写聊天记录;需要查询历史时使用受范围约束的 `session_search`。 这是产品用户隔离和会话沙箱,不是为每个用户启动一个 Linux 账号、容器或独立 OpenCode 实例。服务宿主账号和管理员仍可管理全部持久化数据。原生数据库默认位置为 `~/.local/share/opencode/opencode.db`,工作空间不会取代它,部署仍需保留该数据卷。见 [OpenCode 存储说明](https://opencode.ai/v2/docs/troubleshooting/#service-files)。 ## 旧记录与保留 启动时依据会话索引迁移旧 UI 状态和学习进度,并依据摘要的用户、范围与会话身份迁移旧摘要。旧文件经逐字节备份核对后归入 `PERSISTENCE_BACKUP_DIR/legacy-chat/actor-*/<范围>/session-/chat/`,文件名包含内容哈希;源文件只在备份核对成功后移除。重复迁移不会覆盖已存在的新记录。`legacy-chat/migration.json` 保存版本、来源目录时间戳与迁移结果,来源未变化时跳过重复导入。缺少归属索引的 UI 状态和学习进度保留在旧目录,不猜测归属。历史匿名记录保留原本的本地用户范围,不自动划归某个登录用户。 旧计算目录在该会话继续执行前迁入对应工作空间,使用原生 Session API 更新目录绑定。文件保留,目标冲突停止迁移,绑定更新失败可重试。聊天目录的迁移不需要模型推理。 迁移核对完成后,已迁入工作空间的旧记录备份可以清理,保留 `legacy-chat/migration.json` 作为完成记录。当前本机的 139 份迁移备份及三个空迁移目录已清理,现行聊天和学习记录仍在对应的 `chat/` 中。使用工作空间存储时,初始化不再创建旧迁移目录。 归档会话保留记录。删除会话先写入索引删除状态和 `chat/deleted.json`,会话立即从列表、读取和搜索中隐藏;随后停止聊天与学习任务,移除该原生会话、消息、摘要、学习进度、计算目录及其旧记录和迁移备份。保留最小归属和删除标记,拒绝迟到写入、会话 ID 再使用以及旧数据重新导入。清理未完成时返回 `503 SESSION_CLEANUP_PENDING`,重复同一用户、范围的删除请求可继续清理。 审计日志、已经形成的用户或项目记忆、共享 Skills、Server 业务报告及部署级离线备份有各自的保留职责,不随会话删除。工作空间仍没有自动过期清理,不依据目录为空就删除历史目录。 标题生成、学习检查和复盘在成功、失败及取消后释放自己的原生辅助会话和计算目录;清理失败会记录日志。它们不作为用户聊天长期保留。 持久化默认 `data/` 可同时保存 Agent 索引、工作空间和记忆;Skill 目录与 OpenCode 数据库仍需各自的数据卷。自定义工作空间根目录也需要单独挂载。