Agent 一切状态皆文件:哈啰 AI 基建中的 JuiceFS 存储实践

2026-08-11
高杨

随着 AI 业务发展,哈啰需要同时支撑模型训练、数据处理和 Agent 等多类负载。针对原有多套系统并存带来的数据分散、访问方式不统一和跨云流转复杂等问题,哈啰在生产环境中引入 JuiceFS,构建统一文件数据底座,连接 Notebook、训练、评测和 Agent 等环境。

其中,Agent 已成为重要的业务形态,并已覆盖多个实际业务场景;面向员工的个人 Agent“贾维斯·龙虾”也已在内部落地。与训练场景主要关注高性能 I/O 和海量文件访问不同,Agent 更关注任务过程中的数据流转、状态保存和结果沉淀。本文将重点介绍哈啰如何使用 JuiceFS 持久化用户人格、会话、记忆、Skill、执行轨迹和业务产物,并通过共享 Workspace 实现 Agent 状态与计算 Pod 解耦。

01 哈啰 AI 基建与存储现状

哈啰业务场景丰富,包含大量客服、审核、调度、分析和播报等工作。为支撑这些业务中的 AI 应用,哈啰逐步建设了覆盖模型开发、训练、推理和服务的 AI 平台与大模型平台。

AI 平台覆盖 Notebook、训练、离线推理、在线推理和镜像构建等场景。Notebook 需要挂载用户目录和数据集,训练与推理任务需要读取输入数据、模型及中间结果,镜像构建过程也会产生可复用的缓存。

大模型平台负责聚合不同模型服务,并提供模型调用和压测能力。模型压测过程中产生的数据和报告同样需要持久化保存。

针对不同类型的数据和工作负载,哈啰主要采用三类存储:CPFS 提供高性能并行存储;JuiceFS 作为平台共享文件底座,主要承载 Notebook 环境、任务数据集、模型压测报告和镜像构建缓存等需要 POSIX 语义、多节点共享和统一命名空间的数据;对象存储则主要承载多媒体附件等对象数据。目前,仍有少量开发机目录作为历史过渡方案,尚未迁移至统一存储体系。整体上,这套存储架构已经覆盖 AI 平台中的数据持久化与跨环境共享需求。

哈啰 AI 基建中的存储架构
哈啰 AI 基建中的存储架构

02 Agent 业务带来的存储新需求

随着 Agent 逐步进入实际业务,哈啰的 AI 能力已从模型开发、训练和推理,延伸到能够调用工具并完成业务流程的 Agent。相关应用覆盖两轮车辆运维照片审核、顺风车行程录音审核等场景,在扩大审核覆盖范围、减少人工投入和降低服务成本等方面取得了实际效果。

在垂直 Agent 落地的基础上,哈啰进一步推出面向员工的个人 Agent“贾维斯·龙虾”。员工可以通过钉钉或 Web 与龙虾交互,结合长期记忆、Skill、MCP 和 Cron 等能力,完成答疑、报表、审核和分析等日常工作。

与面向单一业务流程的垂直 Agent 相比,个人 Agent 需要在多轮交互和连续任务之间持续保留状态。在 IT 支持场景中,龙虾需要保存用户问题、附件、会话、知识库检索结果和工具调用结果;在运营日报场景中,需要持续读取历史底表和脚本,并生成新的数据表、图表和报告;在运营归因场景中,还需要保留取数脚本、中间数据和最终分析结果,以便后续检查和重新执行。

一个长期运行的 Agent 需要保存五类数据:

  • 用户人格和行为配置;
  • 会话历史与运行状态;
  • 可跨会话使用的长期记忆;
  • Skill、MCP、渠道和定时任务配置;
  • 脚本、CSV、Excel、图表和报告等业务产物。

这些状态具有明显的文件形态:人格以 Markdown 保存,会话历史使用 SQLite,Skill 由目录和脚本组成,任务配置采用 JSON,业务成果则以数据表、图表和代码等普通文件存在。Agent 的完整状态最终表现为一组持续产生、修改并相互关联的文件和目录。

这类长期运行的 Agent 需要一套统一的共享存储层,支持 POSIX 语义、SQLite WAL、原子重命名和大量小文件访问,并允许多个 Pod 共享同一份用户状态。即使计算 Pod 被销毁、重建或重新调度,用户的人格、会话、记忆和任务产物仍需持续保留。

03 JuiceFS 如何承载 Agent 的完整状态

为满足上述需求,哈啰将现有的 JuiceFS 共享文件底座延伸至 Agent 场景,用于承载每位用户的完整 Workspace。平台按照员工账号为每位用户分配独立的龙虾目录,路径结构类似:

/userdata/aiplatform-jarvis-claw/user/<staff_id>/data/workspace

用户的人格、会话、记忆、Skill、任务配置和业务产物均保存在该目录中,龙虾 Pod 只负责计算。Pod 销毁、重建或重新调度后,只需重新挂载原有 Workspace,即可继续使用此前的状态,实现 Agent 有状态、计算 Pod 无状态。

Workspace 中保存哪些 Agent 状态?

Workspace 中包含 AGENTS.mdSOUL.mdPROFILE.mdHEARTBEAT.mdMEMORY.md 等人格文件,用于描述 Agent 的身份、行为方式和用户偏好,并会被组合进 System Prompt。

会话和运行状态主要保存在 sessions/transcripts/dialog/tool_results/history.dbchats.jsoninbox_events.json 等文件和目录中。其中,history.db 使用 SQLite,并同时产生 WAL 和 SHM 文件。用户消息会在一轮任务开始时写入 transcripts/,即使后续任务中断,已经产生的会话内容仍能保留。

长期记忆保存在 memory/digest/mem_agent/mem_session/mem_metadata/resource/ 等目录中。龙虾基于 ReMe,以 Memory as File 的方式管理记忆:一部分由 Agent 在执行过程中主动写入,另一部分由系统定期回顾历史对话,提炼用户偏好和长期有效的信息,再写入 digest/ 等目录。文字稿将后一过程称为“Dream”。

Workspace 中还保存 cron_jobs.jsoncron_runs/mcp.jsonchannels.jsondingtalk_webhooks.jsonskills/skill.json 等任务与能力配置。Skill 包以 ZIP 形式保存在对象存储中,Pod 启动时再拉取并解压到用户 Workspace。

Agent 生成的脚本、数据表、图表和报告,则保存在 output/scripts/ 以及相关的 Excel、CSV 和 SVG 文件中。实际 Workspace 中还包括浏览器数据、备份和迁移文件等辅助内容。这些文件和目录共同构成 Agent 的完整状态,并在任务执行过程中持续产生、修改和积累。

统一文件系统如何支持数据流转与运维?

将 Agent Workspace 统一保存在 JuiceFS 中,不仅实现了状态持久化和存算分离,也使其能够与 Notebook、训练和评测等环境共享同一套文件系统。

在权限允许的情况下,Notebook 可以直接访问 Agent 的 Workspace。Notebook 中准备的数据可以直接交给 Agent,Agent 生成的数据表和脚本也可以继续用于分析或评测,无须在不同系统之间反复上传和复制。

巡检、迁移、备份和按用户统计存储用量等工作,也可以通过 lsdursync 等常规工具完成。工程师能够直接查看会话、记忆、配置和任务产物,定位执行过程中的问题。

不过,随着 Workspace 从普通数据目录演变为 Agent 可以主动读写并执行脚本的工作空间,平台除了保障文件持久化和共享,还需要建立相应的访问控制与执行边界。

04 共享 Workspace 的安全边界

Agent 不仅需要读取和修改 Workspace 中的文件,还可能通过 Shell、MCP 和 REPL 执行命令或代码。如果缺少有效约束,其访问范围可能超出用户 Workspace,进而触及其他用户的数据、服务源码及容器中的敏感文件。

因此,哈啰从用户数据隔离、工具调用治理、文件访问控制和子进程管理四个层面建立安全边界。

如何隔离不同用户的数据?

哈啰通过 JuiceFS 与 OpenACL 管理文件系统权限,为每位用户划分独立的 Workspace,并控制不同用户可以挂载和访问的目录范围。通过这一机制,每个 Agent 只能访问获得授权的 Workspace,无法读取其他用户的数据。

文件系统权限建立了用户之间的基础数据边界,但无法覆盖 Agent 在容器内部执行工具和代码时产生的全部访问行为。因此,在用户级权限之上,还需要进一步约束工具调用及其执行环境。

如何判断工具调用是否允许执行?

当模型决定调用工具时,系统不会立即执行,而是先由治理层通过 assert_policy() 对本次操作作出判断。

Shell 工具调用的治理与沙箱执行链路
Shell 工具调用的治理与沙箱执行链路

治理结果包括四种类型:

  • ALLOW:允许执行;
  • DENY:拒绝未知工具、sudo 等危险操作;
  • ASK:请求人工审批;
  • SANDBOX_FALLBACK:转入沙箱环境执行。

需要人工审批时,系统通过侧信道将审批请求发送至前端。用户批准后,经过泛化处理的规则可以写入 policy.yaml,供后续同类调用继续使用。

对于 Shell 操作,execute_shell_command() 会将当前工作目录固定在用户 Workspace,而不是服务源码目录,并根据治理结果选择相应的沙箱配置。配置优先采用治理层动态生成的细粒度规则,其次采用服务启动时注册的全局默认配置;裸进程模式仅允许在本地开发环境中使用。

这一层决定工具调用是否可以执行。对于获得许可的操作,平台还需要根据不同的运行方式限制其实际可访问的文件范围。

如何限制主进程与子进程的文件访问?

Agent 访问文件主要存在两种形式,需要分别采取不同的控制机制。

read_filewrite_fileeditgrepglobview_image 等文件工具直接运行在服务主进程中,不会创建子进程,因此无法通过 bubblewrap 拦截。系统通过 is_protected_path() 等应用层路径检查,将相对路径限定在用户 Workspace,同时阻止访问 /app 等服务源码和安装目录。目录列举过程中,受保护的条目也会被过滤。

Shell、stdio MCP Server 和执行模型生成代码的 REPL 则会创建子进程。对于这类操作,仅依靠应用层路径检查无法形成完整边界,因此系统使用 bubblewrap,通过 Linux Mount Namespace 为子进程构造独立的文件系统视图。

在这一受限视图中,用户 Workspace 以可读写方式挂载,经过批准的其他路径按规则开放;/app 等服务源码目录被隐藏或设置为只读,~/.ssh~/.aws 等敏感路径则被禁止访问。由此,Agent 可以正常操作用户 Workspace,但无法访问容器中的全部文件。

应用层路径守卫与沙箱隔离的访问效果
应用层路径守卫与沙箱隔离的访问效果

线上环境如果声明使用 bubblewrap,但实际无法创建沙箱,Shell 和 MCP 会采用 fail-closed 策略,直接拒绝执行,不会退化为缺少隔离的裸进程。REPL 在执行模型生成的 Python 代码时同样要求沙箱可用,否则拒绝执行。

如何管理子进程的生命周期与沙箱边界?

每次需要创建子进程的工具调用,都会使用独立的沙箱和进程组,并启用 --die-with-parent。当用户执行 /stop、任务超时,或者同一 Scope 中的新任务取代旧任务时,系统会逐层取消当前任务,并通过 killpg 清理整个进程组,避免 Shell、MCP 或其子进程继续在后台运行。

stdio MCP Server 虽然由 SDK 自行启动,但其执行命令同样会增加 bubblewrap 前缀,与 Shell 使用相同的文件系统视图。REPL 执行模型生成的 Python 代码时也必须进入沙箱,从而避免 Shell 之外的执行方式成为绕过安全边界的路径。

在具体实现中,使用 Tmpfs 覆盖 /app 后,还需要将其重新挂载为只读,避免向临时 /app 写入文件时出现静默成功;进入沙箱前,也需要清理指向受保护路径的 VIRTUAL_ENVPATH,以避免 Python 运行环境异常。服务启动时还会在沙箱中实际执行 /bin/true 完成自检,而不只是判断 bubblewrap 二进制文件是否存在。

当前方案主要解决文件系统和服务源码的隔离问题,尚未通过 --unshare-net 隔离网络;除执行超时外,也暂未设置 CPU 和内存配额。

05 小结

哈啰的实践可以归纳为四点。第一,Agent 一切状态皆文件。 人格是 Markdown,历史是 SQLite,Skill 是目录,记忆和配置是文件,业务产物是 Excel、CSV、图表和脚本。

第二,共享文件系统实现状态持久化和存算分离。 Agent Pod 只负责计算,状态和 Workspace 保存在 JuiceFS 中。Pod 可以随时重建,用户数据不会随计算实例消失。

第三,同一文件系统连接多个 AI 环境。 Notebook、训练、评测和龙虾可以访问同一套文件,数据不需要在不同系统之间反复复制,运维也可以回归常规文件操作。

第四,开放 Workspace 的同时建立分层安全边界。JuiceFS 与 OpenACL 管理用户级文件系统权限,应用层路径检查限制主进程文件工具的访问范围,bubblewrap 控制 Shell、MCP 和代码执行产生的子进程。

下一步,我们希望推动 Agent 自进化。Agent 的埋点、记忆和评测数据持续沉淀在文件系统中。模型会继续增强,而这些沉淀下来的数据将成为 Agent 后续演进的基础。

Author

高杨
哈啰算法平台负责人

相关博客