Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

文件管理、挂载与数据流

这一章专门描述“文件从哪里来、到哪里去、run 结束后谁负责保留这些文件”,以及同一 session 内文件与会话怎样保持连续。

1. backend 本地磁盘的目录布局

FileBackedStore(文件型状态存储)在 storage_root 下维护三类核心目录和一个状态文件:

backend/data/
├── state.json
├── uploads/
└── workspaces/
    └── <run-id>/
        ├── in/
        ├── out/
        └── tmp/

含义如下:

  • state.json:保存 SessionRecordArtifactRecordMountGrantRecordRunRecord
  • uploads/:保存用户上传的原始文件副本
  • workspaces/<run-id>/in/:本轮输入
  • workspaces/<run-id>/out/:本轮输出
  • workspaces/<run-id>/tmp/:本轮临时文件

这意味着当前 backend 采用的是本地持久化控制面

2. K8s 和 backend 分别保留什么

这部分需要先分清楚。

  • SessionRecord.history(会话历史)、ArtifactRecord(文件记录)、RunRecord(运行记录)由 backend 保存在 state.json
  • uploads/workspaces/ 由 backend 所在主机磁盘保存
  • JobPodConfigMapSecret 由 K8s 管理
  • checkpoint 文件或状态数据库如果写在挂载卷内,就跟随宿主机目录或 PVC 持久化

因此:

  • 对话历史不保存在 K8s
  • K8s 负责容器对象生命周期
  • backend 负责会话、文件索引和结果索引
  • checkpoint 是否可恢复,取决于 agentcore 把它写到哪里

3. 用户上传文件怎样进入系统

3.1 HTTP 上传入口

上传接口是:

POST /api/v1/artifacts/upload

它使用 multipart/form-data,当前识别的字段有:

  • sessionId
  • file
  • relativePath

3.2 backend 落盘逻辑

ApplicationService::upload_artifact_bytes(上传文件字节流)会先校验 session 是否可写,然后调用 FileBackedStore::save_artifact_bytes(保存文件字节流)。后者会:

  1. 清洗文件名
  2. 清洗相对路径
  3. 把文件写到 uploads/
  4. state.json 里写入 ArtifactRecord

3.3 relative_path 的意义

relative_path 不会影响原始上传文件在 uploads/ 的落盘位置,但会影响后续 create_workspace(创建工作区)时复制进 /workspace/in 的目录结构。

4. create_workspace 到底做了什么

FileBackedStore::create_workspace(创建工作区)是文件流的核心步骤之一。

4.1 它会创建固定三层目录

<workspace-root>/
├── in/
├── out/
└── tmp/

4.2 它会把 artifact 复制进 in/

  • 如果 artifact 有 relative_path,按相对目录复制
  • 否则落成 <artifact-id>-<sanitized-file-name>

4.3 它会设置宽松目录权限

当前 Unix 上会把工作区目录权限设成 0o777,目的是避免 backend 进程创建目录后,容器内 UID 1000 无法访问。这个做法解决了当前单机联调中的权限问题,后续如果要提高隔离强度,应改成更细粒度的 owner / group 管理。

5. 容器怎样拿到这些文件

job_builder::build_job(构建 Job)会把宿主机工作区通过 hostPath 挂到容器内的 /workspace。因此当前路径关系是:

用户文件
  -> 上传到 backend /uploads
  -> create_run 时复制到 /workspaces/<run-id>/in
  -> 通过 hostPath 挂到容器 /workspace/in

同一个 Job 还会把 agent package 通过 ConfigMap 挂到:

/opt/agent/package

如果本次运行绑定了 MountGrantRecord(挂载授权记录),还会额外挂一个用户授权目录。

6. run-context.json 怎样生成

在提交 Job 前,ApplicationService::write_run_context(写入运行上下文)会把一个关键文件写到:

/workspace/in/run-context.json

它包含:

  • sessionId
  • latestRunId
  • history
  • package
  • builtInTools
  • envFacts
  • inputArtifacts
  • systemPrompt
  • userPrompt

容器里的 agentcore 会读取这个上下文文件获取本轮所需信息。

7. 会话连续性与文件连续性

这一节从“用户关掉页面后,下次回来还能不能接着聊”这个角度解释当前代码。

7.1 当前已经保证的连续性

当前代码已经保证三类连续性:

  1. 对话历史连续
    SessionRecord.history 会持久化保存。
  2. 上传文件连续
    ArtifactRecorduploads/ 下的原始文件会保留。
  3. 历史结果文件连续
    RunRecordworkspaces/<run-id>/out/ 会保留。

7.2 一轮 run 完成后,历史怎样进入下一轮

ApplicationService::sync_session_conversation_history(同步会话历史)会在新 run 创建前刷新同一 session 下旧 run 的状态。

ApplicationService::sync_run_assistant_message(同步助手回答)会在某个 run 完成后读取:

workspaces/<run-id>/out/final-answer.md

然后把其中内容追加为一条 assistant 消息写入 SessionRecord.history

因此下一次同一 session 再创建 run 时,run-context.json 里已经包含前一轮回答。

7.3 用户关闭前端,再次打开同一 session

如果用户只是关闭前端页面、退出 Helper 或暂时离线,而没有调用 POST /api/v1/sessions/{id}/close,那下面这些内容都会保留:

  • SessionRecord.history
  • SessionRecord.latest_run_id
  • 已上传的 artifact
  • 历史 RunRecord
  • 历史 run 的 out/ 结果文件

这时前端只需要重新使用原来的 session_id 调用:

  • GET /api/v1/sessions/{id}
  • GET /api/v1/runs

随后继续 POST /api/v1/runs 即可。

7.4 session 被关闭后,再回来继续

如果调用了 POST /api/v1/sessions/{id}/close,backend 会把 SessionRecord.status 设为 CLOSED。当前行为是:

  • 历史记录保留
  • artifacts 保留
  • runs 保留
  • 结果文件保留
  • 新的上传、挂载授权和 run 会被拒绝

如果之后还要继续同一会话,需要先调用:

POST /api/v1/sessions/{id}/reopen

reopen 只会改变 session 的可写状态,不会删除任何历史数据。因此一个月后再打开相同 session,历史和结果文件仍然在。

7.5 目前“连续”的边界在哪里

当前代码已经连续保存:

  • 文本对话历史
  • 上传文件
  • 历史结果文件

当前代码还没有自动连续保存:

  • 同一 session 的共享工作目录
  • 同一 sessioncheckpoint_root
  • 上一轮输出文件自动进入下一轮输入

因此今天这套代码能保证“会话历史连续”和“结果文件可追溯”,但“agentcore 内部 checkpoint 的自动恢复”还需要显式的 session 级状态目录设计。

8. checkpoint 文件或状态数据库应写到哪里

如果 agentcore 需要保存:

  • checkpoint 文件
  • SQLite / RocksDB / LMDB 这类状态数据库
  • 工具缓存
  • 中间索引

这些数据都应写到持久化挂载目录中。当前最直接的路径是 /workspace 下的子目录,例如:

/workspace/state/
/workspace/checkpoints/
/workspace/checkpoint-db/

如果它们只写在容器根文件系统里,Pod 删除后数据就一起消失。

9. run 结束后,K8s 如何处理文件、对话历史和 checkpoint

这是当前文档必须说清楚的一点。

9.1 K8s 负责的部分

K8s 负责:

  • Pod 结束
  • Job 结束
  • Job TTL 到期后的 Job / Pod 回收
  • 显式 cancel 时的资源删除

9.2 backend 负责的部分

backend 负责:

  • state.json 中的 sessionartifactrun 元数据
  • uploads/ 中的原始上传文件
  • workspaces/<run-id>/out/ 中的结果文件

9.3 ConfigMap / Secret 的当前处理

当前完成态 run 的 ConfigMapSecret 还没有统一的后台 GC。取消 run 时会显式删除;正常完成后主要依赖后续清理策略补齐。

9.4 checkpoint 的当前处理语义

K8s 本身不理解 checkpoint 语义,它只知道卷挂载和容器对象。真正决定 checkpoint 是否保留的是落盘位置

  • checkpoint 写在容器根文件系统:Pod 删除后丢失
  • checkpoint 写在挂载卷:Pod 删除后仍保留

当前代码没有独立的 checkpoint 路径索引和恢复 API,所以这部分连续性仍取决于 agentcore 的落盘约定和后续的 session 级状态卷设计。

10. 如果要支持“同一 session 长期继续运行”,推荐的数据模型

从通用运行时设计看,更完整的模型是:

backend/data/
├── state.json
├── uploads/
├── workspaces/<run-id>/
└── session-state/<session-id>/
    ├── checkpoints/
    ├── checkpoint-db/
    └── tool-cache/

推荐流程是:

  1. 第一次 run 时,为 session_id 创建 session-state/<session-id>/
  2. 后续同一 session 的每次 run 都把该目录挂到固定路径
  3. agentcore 把 checkpoint 和状态数据库始终写在这个固定路径
  4. backend 在 SessionRecord 或新的 SessionStateRecord 中保存该路径索引

这样可以保证:

  • 文本历史连续
  • 文件结果连续
  • agentcore checkpoint 也连续

当前代码离这一步还差显式元数据和显式挂载规则。