随着 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 平台中的数据持久化与跨环境共享需求。
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.md、SOUL.md、PROFILE.md、HEARTBEAT.md 和 MEMORY.md 等人格文件,用于描述 Agent 的身份、行为方式和用户偏好,并会被组合进 System Prompt。
会话和运行状态主要保存在 sessions/、transcripts/、dialog/、tool_results/、history.db、chats.json 和 inbox_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.json、cron_runs/、mcp.json、channels.json、dingtalk_webhooks.json、skills/ 和 skill.json 等任务与能力配置。Skill 包以 ZIP 形式保存在对象存储中,Pod 启动时再拉取并解压到用户 Workspace。
Agent 生成的脚本、数据表、图表和报告,则保存在 output/、scripts/ 以及相关的 Excel、CSV 和 SVG 文件中。实际 Workspace 中还包括浏览器数据、备份和迁移文件等辅助内容。这些文件和目录共同构成 Agent 的完整状态,并在任务执行过程中持续产生、修改和积累。
统一文件系统如何支持数据流转与运维?
将 Agent Workspace 统一保存在 JuiceFS 中,不仅实现了状态持久化和存算分离,也使其能够与 Notebook、训练和评测等环境共享同一套文件系统。
在权限允许的情况下,Notebook 可以直接访问 Agent 的 Workspace。Notebook 中准备的数据可以直接交给 Agent,Agent 生成的数据表和脚本也可以继续用于分析或评测,无须在不同系统之间反复上传和复制。
巡检、迁移、备份和按用户统计存储用量等工作,也可以通过 ls、du、rsync 等常规工具完成。工程师能够直接查看会话、记忆、配置和任务产物,定位执行过程中的问题。
不过,随着 Workspace 从普通数据目录演变为 Agent 可以主动读写并执行脚本的工作空间,平台除了保障文件持久化和共享,还需要建立相应的访问控制与执行边界。
04 共享 Workspace 的安全边界
Agent 不仅需要读取和修改 Workspace 中的文件,还可能通过 Shell、MCP 和 REPL 执行命令或代码。如果缺少有效约束,其访问范围可能超出用户 Workspace,进而触及其他用户的数据、服务源码及容器中的敏感文件。
因此,哈啰从用户数据隔离、工具调用治理、文件访问控制和子进程管理四个层面建立安全边界。
如何隔离不同用户的数据?
哈啰通过 JuiceFS 与 OpenACL 管理文件系统权限,为每位用户划分独立的 Workspace,并控制不同用户可以挂载和访问的目录范围。通过这一机制,每个 Agent 只能访问获得授权的 Workspace,无法读取其他用户的数据。
文件系统权限建立了用户之间的基础数据边界,但无法覆盖 Agent 在容器内部执行工具和代码时产生的全部访问行为。因此,在用户级权限之上,还需要进一步约束工具调用及其执行环境。
如何判断工具调用是否允许执行?
当模型决定调用工具时,系统不会立即执行,而是先由治理层通过 assert_policy() 对本次操作作出判断。
治理结果包括四种类型:
ALLOW:允许执行;DENY:拒绝未知工具、sudo等危险操作;ASK:请求人工审批;SANDBOX_FALLBACK:转入沙箱环境执行。
需要人工审批时,系统通过侧信道将审批请求发送至前端。用户批准后,经过泛化处理的规则可以写入 policy.yaml,供后续同类调用继续使用。
对于 Shell 操作,execute_shell_command() 会将当前工作目录固定在用户 Workspace,而不是服务源码目录,并根据治理结果选择相应的沙箱配置。配置优先采用治理层动态生成的细粒度规则,其次采用服务启动时注册的全局默认配置;裸进程模式仅允许在本地开发环境中使用。
这一层决定工具调用是否可以执行。对于获得许可的操作,平台还需要根据不同的运行方式限制其实际可访问的文件范围。
如何限制主进程与子进程的文件访问?
Agent 访问文件主要存在两种形式,需要分别采取不同的控制机制。
read_file、write_file、edit、grep、glob 和 view_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_ENV 和 PATH,以避免 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 后续演进的基础。