Agent Store Backend 运行时文档
本文档基于当前 backend/ 源码实现整理,目标是把“今天这套代码到底怎么运行、怎么编排、怎么隔离、怎样保持会话连续性、哪些地方已经实现、哪些地方还需要补齐”说清楚。
文档目标
这本书重点说明以下问题:
agent-store-sandbox-backend(云端运行时编排后端)如何接收session(会话)、artifact(上传文件)、mount grant(挂载授权)和run(一次运行)agentcore(Agent 核心运行时)与agent package(开发者提供的 Agent 包)如何组合成一次真实运行- 当前隔离粒度到底是“一个用户一个环境”“一个会话一个环境”还是“一个运行一个环境”
- 容器与宿主机、用户上传文件、本地 Helper 之间如何交换文件
- 一次运行从 API 请求到 K3s
Job(Kubernetes 批处理任务)结束的完整生命周期是什么 - 同一
session(会话)内的历史、结果文件和可恢复状态如何保持连续 - 当前代码里哪些能力已经落地,哪些能力还属于下一阶段演进
文档中的统一术语
本文档统一使用下面这组术语:
agentcore(Agent 核心运行时):负责执行推理、文件操作、状态恢复、checkpoint、工具调用的运行时执行器。它可以是 Rust 二进制,也可以是镜像内的其它可执行入口。agent package(Agent 包):开发者提供的提示词、工具声明、环境事实、策略和附带资源。RuntimeProfile(运行时配置模板):描述基础镜像、容器入口、资源限制、安装策略和执行模式的模板。SessionRecord(逻辑会话记录):保存同一会话的历史消息、默认包、状态和最近一次运行引用。RunRecord(一次运行记录):保存一次真实运行的工作区、状态、K8s 资源引用和结果索引。
当前代码里的 RuntimeFamily::Codex、RuntimeFamily::ClaudeCode、codex-standard、claude-code-standard 这些名字仍会出现在源码和配置里。本文档把它们统一视为样例 runtime family / profile 标识符。从运行时架构角度,真正决定行为的是:
- 基础镜像
image - 容器入口
executable - 安装策略
installStrategy - 执行模式
executionMode - 工作区挂载与资源限制
也就是说,架构并不依赖 agentcore 的实现语言,差异主要体现在基础镜像和容器入口。
一句话结论
当前实现的物理隔离单元是一次 RunRecord(一次运行记录),它会被渲染成一个独立的 K3s Job 和一个独立 Pod;SessionRecord(逻辑会话记录)负责保存多轮历史和会话状态。
当前代码已经保证:
- 同一
session内的对话历史连续 - 已上传文件和历史结果文件长期保留
- 用户关闭前端或关闭 session 后,后续仍可重新打开同一
session
当前代码还没有把“同一 session 的 agentcore checkpoint 目录”做成显式 API 和显式挂载元数据;文档会把这一点作为当前边界和建议方案写清楚。
必须先澄清的三个事实
1. 当前按 run 创建独立容器
同一个 SessionRecord(逻辑会话记录)会跨越多次 create_run(创建运行)。每一轮都会新建一个独立工作区、独立 Job、独立 Pod。前一轮结束后继续复用的是:
SessionRecord.history(会话消息历史)ArtifactRecord(上传文件记录)- 已落盘的历史结果文件
- 选定的
agent package - 选定的
RuntimeProfile
2. 当前采用“共享运行时模板 + 按 run 注入 package”的模式
RuntimeProfile(运行时配置模板)提供共享的运行时底座;AgentPackageManifest(Agent 包清单)提供本次运行要注入的业务层资源;LaunchPlan(启动计划)把两者与当前工作区、当前 provider 凭据、当前挂载授权拼成一次独立运行。
3. 当前会话连续性主要来自持久化状态
当前代码里,会话连续性主要来自:
SessionRecord.historySessionRecord.latest_run_idArtifactRecordRunRecordbackend/data/workspaces/<run-id>/out/下的结果文件
如果 agentcore 自带 checkpoint,建议把 checkpoint 写到持久化挂载目录,并由 backend 保存“session_id -> checkpoint_root”的索引;当前代码还没有把这块做成显式 API。
代码入口
如果要从源码反向阅读整个流程,建议按下面顺序看:
backend/src/main.rsbackend/src/lib.rsbackend/src/api/mod.rsbackend/src/service.rsbackend/src/k8s/mod.rsbackend/src/k8s/job_builder.rsbackend/src/storage/mod.rsbackend/src/provider.rsbackend/src/package/mod.rs
其中有两个 K8s 相关入口需要先记住:
backend/src/k8s/mod.rs:通过kube(Rust Kubernetes 客户端)执行 namespace、service account、job、pod、node 相关的读写和状态检查。backend/src/k8s/job_builder.rs:通过k8s_openapi(Rust Kubernetes 资源类型定义)在内存里构造ConfigMap、Secret、Job,再渲染成 YAML 或提交到集群。
接下来的章节会按这个顺序拆开讲。
安装与本地预览
这一章只说明如何把当前项目跑起来,以及如何预览本书本身。
1. Rust Workspace
仓库根目录是一个 Cargo workspace,包含两个成员:
backend/macos-helper/
在仓库根执行:
cargo test --workspace
如果需要更严格的静态检查:
cargo clippy --workspace --tests -- -D warnings
2. 启动云端后端
2.1 本地 dry-run 预览模式
这种模式不会真的调用 Kubernetes API,只会把即将提交的资源渲染出来。
export AGENT_STORE_BACKEND_CLUSTER_MODE=dry-run
cargo run -p agent-store-sandbox-backend
2.2 真实 K3s 模式
这种模式会调用 K3s,把每次运行提交成 Job。
export AGENT_STORE_BACKEND_BIND_ADDR=0.0.0.0:8090
export AGENT_STORE_BACKEND_CLUSTER_MODE=apply
export AGENT_STORE_BACKEND_DEFAULT_NAMESPACE=agent-store-runtime-demo
export AGENT_STORE_BACKEND_DEFAULT_SERVICE_ACCOUNT=agent-runner
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
export DEEPSEEK_API_KEY=replace-me
export DEEPSEEK_MODEL=deepseek-chat
export DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
cargo run -p agent-store-sandbox-backend
2.3 远程部署
当前仓库已经提供了部署模板:
backend/deploy/backend.env.example:环境变量模板backend/deploy/agent-store-backend.service:systemd 单元模板backend/deploy/install_backend_remote.sh:一键远程安装脚本
3. 启动本地 Helper
agent-store-macos-helper(macOS 本地桥接服务)负责把本地工作目录同步成云端 artifact,并把远端结果回写到本地。
export AGENT_STORE_BACKEND_BASE_URL=http://127.0.0.1:8090
export AGENT_STORE_USER_ID=demo-user
export MACOS_HELPER_BIND_ADDR=127.0.0.1:7480
export MACOS_HELPER_POLL_TIMEOUT_MS=120000
cargo run -p agent-store-macos-helper
浏览器访问:
http://127.0.0.1:7480
4. 打包 Helper
bash macos-helper/scripts/package_macos_helper.sh
输出文件:
macos-helper/dist/Agent Store Helper.appmacos-helper/dist/Agent Store Helper.zip
5. 构建本书
本书采用 mdBook 目录结构。首次安装工具:
cargo install mdbook --locked
构建 HTML:
mdbook build backend/docs
本地预览(本机装了 mdbook 时会启用热重载;否则回退到直接托管已构建好的 backend/docs/book/):
bash backend/scripts/serve_docs.sh
访问:
http://127.0.0.1:3000
如果要把文档跑在远端机器或沙箱并允许任意位置访问,且远端已经有 backend/docs/book/index.html:
DOCS_SERVE_MODE=static MDBOOK_HOST=0.0.0.0 MDBOOK_PORT=3452 \
bash backend/scripts/serve_docs.sh
浏览器访问:
http://<remote-host>:3452
6. 快速回归命令
后端附带一个 smoke 脚本:
BACKEND_BASE_URL=http://127.0.0.1:8090 \
AGENT_STORE_USER_ID=smoke-user \
bash backend/scripts/smoke_backend_run.sh
如果只想看资源渲染,把环境变量 AGENT_STORE_APPLY_TO_CLUSTER=false 加进去即可。
运行时总体架构
这一章解释 backend/ 里“谁负责什么”,以及一次运行到底由哪些对象拼出来。
1. 启动时会装配哪些核心组件
build_application(应用装配入口)在启动时会把下面这些组件接起来:
| 代码对象 | 当前职责 |
|---|---|
AppConfig(进程级配置) | 读取 HTTP 监听地址、存储根目录、包目录、运行时配置、集群模式、默认命名空间、默认服务账号、挂载白名单、Job TTL |
FileBackedStore(文件型状态存储) | 维护 state.json、uploads/、workspaces/,保存 session、artifact、mount grant、run |
PackageCatalog(Agent 包目录索引) | 把 backend/packages/* 加载成内存里的 LoadedPackage(已加载 Agent 包) |
RuntimeProfileCatalog(运行时模板目录索引) | 把 backend/config/runtime_profiles.yaml 加载成内存里的 RuntimeProfile |
K3sOrchestrator(K3s 编排器) | 渲染和可选提交 ConfigMap、Secret、Job,并观察 K8s 状态 |
ApplicationService(应用服务层) | 串起 API、状态存储、工作区准备、K8s 提交、状态刷新、多轮会话同步 |
Router(HTTP 路由) | 对外暴露 /api/v1/* 接口 |
这意味着当前后端是一个“单进程控制面 + K3s 执行面”的结构:
- API 请求先进入
Router(HTTP 路由) Router调ApplicationService(应用服务层)ApplicationService同时依赖本地持久化状态和K3sOrchestrator(K3s 编排器)- 真正执行
agentcore的单元是被后端提交出去的 K3sJob
2. 当前系统里最重要的几个对象
2.1 SessionRecord(逻辑会话记录)
SessionRecord 表示“同一用户的一段逻辑会话”。核心字段包括:
user_iddefault_packagestatushistorylatest_run_idclosed_at
它的职责是保存多轮对话时间线,容器实例由后续的 run 单独创建。
2.2 ArtifactRecord(上传文件记录)
ArtifactRecord 表示用户上传到 backend 的文件元数据。文件本体会先落到 uploads/,后续在创建 run 时复制进当前 run 的工作区。
2.3 MountGrantRecord(宿主机挂载授权记录)
MountGrantRecord 表示“允许某个用户把某个 backend 所在主机目录挂进容器”。它是显式授权对象,包含:
host_pathmount_pathaccess_mode
2.4 RunRecord(一次运行记录)
RunRecord 描述一次真实运行,包含:
- 所属会话
session_id - 使用的包
package_name - 使用的运行时模板
runtime_profile - 工作区模式
workspace_mode - 输入文件
artifact_ids - 可选挂载授权
mount_grant_id - provider 环境变量键名
provider_env_keys - 工作区根目录
workspace_root - 运行状态
status - K8s 资源引用
resources - 渲染结果
rendered
当前物理隔离单元就是 RunRecord。
2.5 RuntimeProfile(运行时配置模板)
RuntimeProfile 决定一次 run 的基础运行时底座,主要包括:
- 基础镜像
image - 容器入口
executable - 安装策略
installStrategy - 执行模式
executionMode - 默认模型和默认 Base URL
- namespace、service account
- CPU、内存、临时存储的 requests / limits
从运行时设计上看,RuntimeProfile 表达的是“怎样启动一个 agentcore 容器”,并不绑定实现语言。
2.6 AgentPackageManifest(Agent 包清单)
AgentPackageManifest 定义 agent package 本身的业务层意图,主要包括:
runtimeentrypolicyassets
当前包目录示例位于:
backend/packages/codex-reviewer/backend/packages/claude-code-builder/
这些目录名是样例包名,架构层仍统一按 agent package 理解。
3. agentcore 和 agent package 当前怎样组合
3.1 agentcore 在当前代码里对应什么
如果把 agentcore 理解为“真正提供执行底座的运行时”,那在当前代码里它主要对应三部分:
RuntimeProfile(运行时配置模板)- 运行时镜像
image - 容器入口
executable
它们以共享模板的方式存在。
3.2 agent package 在当前代码里对应什么
如果把 agent package 理解为“开发者提供的提示词、工具声明、环境事实和策略”,那它对应:
agent.yamlprompt/system.mdbuild_in_tools.yamlenv/env_facts.yaml- 其它包内文本文件
这些文件在运行时由后端加载成 LoadedPackage(已加载 Agent 包),再通过 ConfigMap 注入 Pod 内的 /opt/agent/package。
3.3 两者的绑定时机
LaunchPlan(一次运行的不可变启动计划)是 agentcore 和 agent package 真正结合的地方。它同时持有:
packageruntime_profileworkspace_modeprovider_envmount_grantworkspace_host_pathnamespaceservice_account
随后 K3sOrchestrator 会把这个 LaunchPlan 渲染成三类资源:
ConfigMapSecretJob
3.4 当前组合模式的结论
结论很明确:
- 共享的是
RuntimeProfile和基础镜像 - 独立的是每次运行的 Pod、工作区、
ConfigMap、Secret - 一次 run 会把“共享运行时模板 + 当前
agent package+ 当前工作区 + 当前 provider 凭据”拼成一个独立运行单元
4. 当前隔离模型与连续性模型
4.1 逻辑隔离按 session
SessionRecord 保存:
- 历史消息
- 默认包
- 会话状态
- 最近一次 run
它定义的是“同一段对话时间线”。
4.2 物理隔离按 run
每次 create_run(创建运行)都会创建:
- 一个独立工作区目录
- 一个独立
ConfigMap - 一个可选独立
Secret - 一个独立 K3s
Job - 一个独立 Pod
这意味着当前模型是:
- 逻辑连续性按
session - 物理执行隔离按
run
4.3 会话连续性今天靠什么保持
当前代码里的连续性主要来自三块持久化数据:
SessionRecord.history(会话消息历史)ArtifactRecord(上传文件记录)和uploads/下的原始文件RunRecord(运行记录)和workspaces/<run-id>/out/下的结果文件
ApplicationService::sync_session_conversation_history(同步会话历史)会在新 run 开始前刷新该 session 下旧 run 的状态;ApplicationService::sync_run_assistant_message(同步助手回答)会把已完成 run 的 final-answer.md 回填进 SessionRecord.history。这样同一 session 的下一轮可以拿到前一轮回答。
4.4 agentcore 的 checkpoint 连续性应该怎样接入
从通用运行时架构看,agentcore 的 checkpoint 连续性应该依赖持久化挂载路径,典型模式是:
- 为每个
session分配一个持久化状态目录 - 在同一
session的每次 run 中都把这个目录挂到固定路径 - 由
agentcore在该目录内保存 checkpoint、状态数据库、工具缓存和恢复索引 - 由 backend 保存
session_id -> checkpoint_root的索引
当前代码已经具备:
- run 级工作区持久化
- session / run 元数据持久化
- reopen 同一
session
当前代码还没有显式的:
checkpoint_root字段- session 级持久化状态卷
- resume API
这部分会在“当前实现边界”章节里继续说明。
5. K8s Rust 调用在当前架构中的位置
这一节专门把代码和功能对应起来。
5.1 backend/src/k8s/mod.rs 负责真正访问集群
K3sOrchestrator::new(K3s 编排器初始化)通过 Client::try_default() 创建 kube 客户端,并调用:
ensure_namespace:确保 namespace 存在ensure_service_account:确保 service account 存在且关闭自动挂载 token
K3sOrchestrator::submit_run(提交运行)负责:
- 调
job_builder::build_bundle在内存里构造ConfigMap、Secret、Job - 在
apply模式下调用Api<ConfigMap>::create - 调用
Api<Secret>::create - 调用
Api<Job>::create
K3sOrchestrator::inspect_run_status(检查运行状态)负责:
Api<Job>::get_opt读取 Job 状态Api<Pod>::list读取同一 Job 对应 Pod 的状态- 把 Job / Pod 观察结果映射成
RunStatus
K3sOrchestrator::cluster_status(读取集群状态)负责:
Api<Node>::list读取节点- 汇总可调度节点数、taint、磁盘压力和阻塞原因
K3sOrchestrator::cancel_run(取消运行)负责:
Api<Job>::deleteApi<ConfigMap>::deleteApi<Secret>::delete
5.2 backend/src/k8s/job_builder.rs 负责把计划渲染成资源
job_builder::build_bundle(构建运行资源包)会:
- 构建承载 package 文件的
ConfigMap - 构建承载 provider 凭据的
Secret - 构建真正执行 run 的
Job
build_job(构建 Job)会把下面这些设置落到资源定义里:
/workspace工作区挂载/opt/agent/package包目录挂载- 可选的用户授权挂载
allowPrivilegeEscalation: falsecapabilities.drop = ["ALL"]runAsNonRoot = truefsGroup = 1000seccompProfile = RuntimeDefaultttl_seconds_after_finished
5.3 backend/src/service.rs 负责把业务对象和 K8s 编排串起来
ApplicationService::create_run(创建运行)是控制流入口,它会:
- 校验 session
- 解析 package
- 解析
RuntimeProfile - 合并 provider 配置
- 校验输入文件和挂载
- 做集群预检
- 同步历史回答
- 追加本轮用户消息
- 创建工作区
- 写
run-context.json - 组装
LaunchPlan - 调
K3sOrchestrator::submit_run - 持久化
RunRecord
6. 当前样例 runtime profile 如何理解
runtime_profiles.yaml 里当前有多组样例 profile。它们在代码里沿用了历史命名,例如:
codex-standardclaude-code-standardcodex-cli-standardclaude-code-cli-standard
这些名字只代表当前仓库里已有的样例配置。架构层需要关注的是下面这些字段:
imageexecutableinstallStrategyexecutionModerequestslimits
因此如果后续接入新的 Rust agentcore、Python agentcore 或其它容器入口,后端运行时模型本身不需要变化,变化点主要是:
- 基础镜像
- 容器入口
- checkpoint 目录约定
- 执行模式适配器
7. backend 与 agentcore 的责任边界
当前更合理的责任边界是:
agentcore负责自己的执行逻辑、checkpoint、工作目录状态、工具调用和恢复逻辑agent package负责提示词、工具声明、环境事实和策略- backend 负责保存
session、run、上传文件、工作区目录、K8s 资源引用、结果文件索引以及 reopen/resume 入口
这条边界与 agentcore 的实现语言无关。
隔离模型、权限模型与安全边界
这一章专门回答“隔离做到哪一层”“权限到底有多强”“哪些能力是声明性元数据,哪些能力已经强制生效”。
1. 隔离粒度:当前分成三层
当前代码里实际上存在三种不同层次的隔离。
1.1 user_id(用户所有权边界)是对象级隔离
后端要求所有用户态 API 都带 x-user-id。FileBackedStore(文件型状态存储)在读取 SessionRecord、ArtifactRecord、MountGrantRecord、RunRecord 时都会调用 ensure_owner(所有者校验)。
这意味着:
- 一个用户看不到另一个用户的 session / run / artifact / mount grant
- 即使知道 UUID,跨用户读取也会返回 “not found”
这是一层应用层所有权隔离。当前代码没有扩展成完整身份认证体系。
1.2 SessionRecord(逻辑会话记录)是上下文隔离层
一个 SessionRecord 拥有自己的:
history- 默认 package
- 关闭状态
它能保证多轮对话上下文按 session 归组。一个 session 可以连续创建多个 run,这些 run 共享逻辑历史,容器实例按 run 单独创建。
1.3 RunRecord(一次运行记录)是物理执行隔离
每次 create_run 都会创建:
- 一个独立工作区目录
- 一个独立
ConfigMap - 一个可选独立
Secret - 一个独立 K3s
Job - 一个独立 Pod
所以当前最强的实际隔离边界是:
一次 run = 一个独立容器执行环境
2. 当前隔离强度怎么理解
如果用“隔离强度”来拆,当前可以分成四类。
2.1 进程与文件系统隔离:中等偏强
每个 run 都在独立 Pod 中运行,工作区也是独立目录,默认路径是:
- 宿主机:
backend/data/workspaces/<run-id>/ - 容器内:
/workspace
因此不同 run 的输出文件天然分目录隔离。
当前工作区挂载方式是 hostPath(宿主机目录直挂)。当前隔离强度属于较轻量的容器级隔离;如果后续要提升隔离,可以继续引入 PVC、CSI 或更强沙箱方案。
2.2 Linux 权限隔离:中等
Job 里的容器安全上下文当前明确设置了:
allowPrivilegeEscalation: falsecapabilities.drop = ["ALL"]runAsNonRoot: truerunAsUser: 1000runAsGroup: 1000fsGroup: 1000seccompProfile.type = RuntimeDefaultautomountServiceAccountToken: falserestartPolicy: Never
这说明当前 Pod 默认不会拿到:
- root 身份
- Linux capabilities
- 自动挂载的 K8s API token
这部分是代码里真实生效的安全边界。
2.3 网络隔离:弱
AgentPackageManifest(Agent 包清单)里虽然有 networkProfile(网络档位),例如 restricted-egress,但当前 backend 不会依据这个字段创建 NetworkPolicy。
networkProfile现在只是包元数据和提示信息- 真正能否访问外网,取决于集群网络、节点出网能力、镜像内工具和 provider API 可达性
所以当前网络隔离强度偏弱,不能把 networkProfile 当成已落地的强制策略。
2.4 能力权限隔离:部分生效,部分只是声明
PackagePolicy(Agent 包策略)里的两个字段要分开看:
workspaceMode:真实生效,会影响输入文件是 copy 还是 mountpermissionProfile:当前只是一段包策略元数据,没有被后端强制映射成 Linux/K8s/工具权限
所以目前真正“强制落地”的权限主要来自:
- Pod 安全上下文
- 工作区 copy / mount 选择
- 挂载白名单
readOnly/readWrite挂载模式
permissionProfile 当前承担的是描述和标识作用。
3. 一个 session、一个用户、一个 run,分别隔离什么
| 隔离单元 | 当前是否独立容器 | 当前是否独立工作区 | 当前是否独立历史 | 当前用途 |
|---|---|---|---|---|
| 用户 | 否 | 否 | 否 | 作为所有权边界 |
| Session | 否 | 否 | 是 | 作为多轮上下文边界 |
| Run | 是 | 是 | 只记录本轮 | 作为实际执行边界 |
因此如果要回答“目前是一个会话一个隔离环境,还是一个用户一个隔离环境”,答案是:
- 逻辑隔离按 session
- 物理隔离按 run
4. copy 和 mount 两种工作区模式的安全差异
4.1 WorkspaceMode::Copy(复制模式)
复制模式下,后端会把 ArtifactRecord 对应的文件从 uploads/ 复制到当前 run 的 /workspace/in。
优点:
- 每次 run 的输入快照固定
- 容器只看到本轮复制进去的文件
- 不会直接暴露宿主机原始目录树
代价:
- 大目录会产生重复拷贝
- 用户本地后续改动不会自动映射到已提交的 run
4.2 WorkspaceMode::Mount(挂载模式)
挂载模式下,必须先创建 MountGrantRecord。后端会把 grant 的 host_path 作为额外 hostPath volume 挂到容器内的 mount_path。
优点:
- 不需要复制大目录
- 容器看到的是宿主机目录的实时内容
风险:
- 隔离性明显弱于 copy
- 一旦授权
readWrite,容器就可以直接修改宿主机目录
因此 mount 模式本质上是“受控直通”能力。
5. 挂载授权当前怎么控
validate_mount_path(挂载路径校验)会做三件事:
- 把用户给的路径 canonicalize
- 确认它是一个已存在目录
- 确认它落在
AGENT_STORE_BACKEND_ALLOWED_MOUNT_ROOTS白名单内
默认白名单是:
backend/data/workspaces
这说明当前 mount grant 的默认设计目标是:
- 给 backend 自己管理的目录做受控回挂
- 默认不开放整个宿主机文件系统
6. 容器内到底能访问什么文件
当前容器内常见文件可分成四类。
6.1 始终存在的工作区
/workspace/in/workspace/out/workspace/tmp
这三者都来自 run 对应的宿主机工作区目录。
6.2 始终存在的 package 目录
/opt/agent/package
这里挂的是 ConfigMap,里面是 package 的文本资产。
6.3 可选的 provider 凭据
provider 凭据不会作为文件卷挂进去,而是通过 envFrom.secretRef 注入环境变量。
6.4 可选的用户授权挂载
如果本次 run 绑定了 MountGrantRecord,还会额外挂一个 user-mount volume 到指定路径。
7. 容器如何访问外部工具、宿主机工具和本地工具
7.1 容器内可以访问哪些“外部工具”
当前真正可执行的工具只有三类:
- 基础镜像里自带的工具,例如
bash、node inlineNpm(运行时内联 npm 安装)装出来的 CLI- 容器内通过 HTTP 访问的模型 API
7.2 build_in_tools.yaml 当前是提示词工具清单
build_in_tools.yaml(内置工具清单)虽然声明了 builtin.read、builtin.write、builtin.ask_user 等工具,但当前实现只是把它们写进 run-context.json 供模型参考。
后端目前没有:
- 独立的 tool call RPC 通道
- tool execution dispatcher
- tool result callback 回路
当前实现里,这一层承担的是提示词上下文拼装职责。
7.3 容器不能直接访问 backend 主机任意工具
当前没有实现:
- 宿主机命令代理
- SSH proxy 执行
- helper 本地工具转发
- 本地 IDE / Git / shell 代理
除非某个宿主机目录被显式 mount 进来,否则容器看到的只是:
- 镜像内容
- 工作区内容
- package 内容
- 环境变量
7.4 容器也不能直接访问 macOS 本地工具
macos-helper 当前承担的是“文件同步桥”职责。它会把本地文件上传成 artifact,当前没有把 macOS 上的 shell、git、编辑器或 CLI 映射成远端容器可调用工具。
7.5 支持本地工具调用的可行方案
如果后续要让远端 agentcore 访问本地工具,当前最可行的方案有三类:
macos-helper(macOS 本地桥接服务)增加本地工具 RPC 层,由 helper 执行 Git、shell、编辑器等受控工具- helper 与 backend 之间建立长连接工具隧道,由 backend 转发工具调用请求
- 继续保持当前文件同步模式,把本地工具执行放在 helper 侧完成,再把结果同步成文件或补丁
无论采用哪种方案,都需要补齐:
- 工具白名单
- 参数校验
- 会话级认证
- 审批模型
- 超时与取消
- 审计日志
7.6 当前未实现点
当前代码还没有:
- 远端容器到本地 helper 的工具调用协议
- helper 侧的受控命令执行器
- 本地工具的权限和审批模型
- 本地工具执行结果回写到
agentcore的标准事件流
8. provider 凭据隔离如何做
ProviderDefaults(provider 默认值合并器)会合并三层来源:
- backend 进程环境变量
RuntimeProfile里的默认baseUrl/model- 单次请求的
provider_env
合并后得到的环境变量会被写成每 run 独立的 K8s Secret。渲染返回给前端时,providerSecretYaml 里的值会被替换成 <redacted>。
这说明当前 provider 凭据隔离粒度是:
- 按 run 注入
- 按 Secret 隔离
- 按用户 API 读取权限隔离
9. 当前安全边界的真实结论
如果只基于代码,不做理想化表述,可以下结论:
- 当前已经有“每 run 独立 Pod + 非 root + drop capabilities + seccomp + 禁止自动 SA token”这一层容器安全基线。
- 当前默认更安全的输入方式是
copy,mount属于更强权限能力。 - 当前 mount 具备白名单和只读/读写控制,但仍然属于较强权限能力,应谨慎开放。
- 当前
permissionProfile、networkProfile主要还是描述性元数据,不应误认为强制安全策略。 - 当前没有把本地 macOS 工具或 backend 宿主机工具安全地暴露给容器的机制。
文件管理、挂载与数据流
这一章专门描述“文件从哪里来、到哪里去、run 结束后谁负责保留这些文件”,以及同一 session 内文件与会话怎样保持连续。
1. backend 本地磁盘的目录布局
FileBackedStore(文件型状态存储)在 storage_root 下维护三类核心目录和一个状态文件:
backend/data/
├── state.json
├── uploads/
└── workspaces/
└── <run-id>/
├── in/
├── out/
└── tmp/
含义如下:
state.json:保存SessionRecord、ArtifactRecord、MountGrantRecord、RunRecorduploads/:保存用户上传的原始文件副本workspaces/<run-id>/in/:本轮输入workspaces/<run-id>/out/:本轮输出workspaces/<run-id>/tmp/:本轮临时文件
这意味着当前 backend 采用的是本地持久化控制面。
2. K8s 和 backend 分别保留什么
这部分需要先分清楚。
SessionRecord.history(会话历史)、ArtifactRecord(文件记录)、RunRecord(运行记录)由 backend 保存在state.jsonuploads/和workspaces/由 backend 所在主机磁盘保存Job、Pod、ConfigMap、Secret由 K8s 管理checkpoint文件或状态数据库如果写在挂载卷内,就跟随宿主机目录或 PVC 持久化
因此:
- 对话历史不保存在 K8s
- K8s 负责容器对象生命周期
- backend 负责会话、文件索引和结果索引
- checkpoint 是否可恢复,取决于
agentcore把它写到哪里
3. 用户上传文件怎样进入系统
3.1 HTTP 上传入口
上传接口是:
POST /api/v1/artifacts/upload
它使用 multipart/form-data,当前识别的字段有:
sessionIdfilerelativePath
3.2 backend 落盘逻辑
ApplicationService::upload_artifact_bytes(上传文件字节流)会先校验 session 是否可写,然后调用 FileBackedStore::save_artifact_bytes(保存文件字节流)。后者会:
- 清洗文件名
- 清洗相对路径
- 把文件写到
uploads/ - 在
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
它包含:
sessionIdlatestRunIdhistorypackagebuiltInToolsenvFactsinputArtifactssystemPromptuserPrompt
容器里的 agentcore 会读取这个上下文文件获取本轮所需信息。
7. 会话连续性与文件连续性
这一节从“用户关掉页面后,下次回来还能不能接着聊”这个角度解释当前代码。
7.1 当前已经保证的连续性
当前代码已经保证三类连续性:
- 对话历史连续
SessionRecord.history会持久化保存。 - 上传文件连续
ArtifactRecord和uploads/下的原始文件会保留。 - 历史结果文件连续
RunRecord和workspaces/<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.historySessionRecord.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的共享工作目录 - 同一
session的checkpoint_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中的session、artifact、run元数据uploads/中的原始上传文件workspaces/<run-id>/out/中的结果文件
9.3 ConfigMap / Secret 的当前处理
当前完成态 run 的 ConfigMap 和 Secret 还没有统一的后台 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/
推荐流程是:
- 第一次 run 时,为
session_id创建session-state/<session-id>/ - 后续同一
session的每次 run 都把该目录挂到固定路径 agentcore把 checkpoint 和状态数据库始终写在这个固定路径- backend 在
SessionRecord或新的SessionStateRecord中保存该路径索引
这样可以保证:
- 文本历史连续
- 文件结果连续
agentcorecheckpoint 也连续
当前代码离这一步还差显式元数据和显式挂载规则。
运行生命周期与容器编排流程
这一章从用户视角出发,解释当前代码里一次会话、一次运行和一次容器编排到底怎样流动。
1. 从用户视角看,一次会话会经历什么
用户在产品里看到的主流程可以理解为下面几步:
- 打开一个
session(会话) - 上传文件,或选择已有文件
- 选择一个
agent package - 发送一轮 prompt
- backend 为这轮 prompt 创建一个新的
run - K3s 为这个
run拉起一个独立 Pod agentcore在 Pod 里处理本轮任务并写结果- backend 把结果状态写回
run,把主回答写回session history - 用户离开页面,稍后回来继续使用同一个
session
这个流程里,“连续的是 session”,“独立的是 run”。
2. 后端进程生命周期
main(进程入口)启动后会依次完成:
- 初始化 tracing
- 兼容旧环境变量别名
- 解析
AppConfig(进程级配置) - 调
build_application(应用装配入口) - 绑定 HTTP 地址
- 用 axum 对外提供 API
- 监听退出信号并优雅关闭
如果 backend 运行在 Apply 模式,K3sOrchestrator::new(K3s 编排器初始化)还会确保:
- namespace 存在
- service account 存在
- service account 关闭自动挂载 token
2.1 启动伪代码
async fn main() {
init_tracing(); // 初始化日志
apply_legacy_env_aliases(); // 兼容旧环境变量
let config = AppConfig::parse(); // 解析进程配置
let app = build_application(config.clone()).await?; // 装配 store/catalog/orchestrator/router
let listener = TcpListener::bind(config.bind_addr).await?; // 绑定 HTTP 地址
axum::serve(listener, app.router)
.with_graceful_shutdown(shutdown_signal())
.await?;
}
3. session 的生命周期
SessionRecord(逻辑会话记录)当前只有两个状态:
ACTIVECLOSED
生命周期如下:
POST /api/v1/sessions创建,初始为ACTIVE- 可反复用于多轮
create_run POST /api/v1/sessions/{id}/close后变成CLOSEDPOST /api/v1/sessions/{id}/reopen后回到ACTIVECLOSED状态下仍可查询历史和结果
这里要特别注意:close 表示“归档并拒绝新写入”,会话数据会继续保留。
4. run 的生命周期
RunRecord(一次运行记录)常见状态包括:
PREVIEWEDSUBMITTEDPENDINGCOMPLETEDFAILEDCANCELLED
其生命周期是:
- 用户请求创建 run
- backend 准备工作区和上下文
- backend 提交或渲染 K3s 资源
- Pod 执行
- backend 刷新状态
- run 进入完成、失败或取消态
5. create_run 的完整用户视角流程
5.1 用户看到的过程
从用户角度,一次“发送消息”背后发生的是:
- backend 读取当前会话
- backend 读取本轮所选文件和挂载授权
- backend 检查集群是否还能接收新任务
- backend 把旧回答补回会话历史
- backend 把本轮用户输入追加到会话历史
- backend 创建本轮独立工作区
- backend 把上下文写成
run-context.json - backend 构造
ConfigMap、Secret、Job - K3s 启动本轮 Pod
- Pod 把本轮结果写回工作区
5.2 当前代码的伪代码
#![allow(unused)]
fn main() {
async fn create_run(command: CreateRunCommand) -> RunRecord {
let run_id = new_uuid_v7(); // 生成本轮唯一 id
let session = store.get_session(command.user_id, command.session_id).await?; // 读取会话
ensure_session_accepts_writes(session)?; // 关闭态会话拒绝新 run
let package = packages.get(command.package_name)?; // 读取 agent package
let runtime_profile = resolve_runtime_profile(command, package)?; // 解析运行时模板
let provider_env = merge_provider_env(runtime_profile, command.provider_env); // 合并 provider 配置
let artifacts = store.get_artifacts(command.user_id, command.artifact_ids).await?; // 读取输入文件
let mount_grant = resolve_mount_grant(command.mount_grant_id).await?; // 读取可选挂载授权
let workspace_mode = resolve_workspace_mode(command, package); // 决定 copy 或 mount
ensure_cluster_accepts_runs().await?; // 检查是否有可调度节点
sync_session_conversation_history(command.user_id, command.session_id).await; // 把旧 run 的回答补回 history
append_user_message(command.session_id, run_id, command.user_prompt).await?; // 写入本轮用户消息
let workspace_root = create_workspace(run_id, artifacts).await?; // 创建 /in /out /tmp
write_run_context(workspace_root, session, package, artifacts, command.user_prompt).await?; // 写上下文文件
let plan = LaunchPlan { ... }; // 汇总 package / runtime / workspace / provider / k8s 参数
let outcome = orchestrator.submit_run(plan).await?; // 渲染并提交 ConfigMap / Secret / Job
let run = build_run_record(run_id, workspace_root, outcome); // 落盘 RunRecord
store.insert_run(run).await
}
}
6. K3s 资源怎样从业务对象变成 Pod
6.1 ApplicationService(应用服务层)负责业务装配
ApplicationService::create_run 会把会话、文件、挂载、provider 环境变量和 RuntimeProfile 组装成 LaunchPlan。
6.2 job_builder 负责渲染资源定义
job_builder::build_bundle(构建运行资源包)会在内存里构造:
- 承载
agent package文件的ConfigMap - 承载 provider 环境变量的
Secret - 真正执行 run 的
Job
6.3 K3sOrchestrator 负责和集群交互
K3sOrchestrator::submit_run(提交运行)在 apply 模式下会调用:
Api<ConfigMap>::createApi<Secret>::createApi<Job>::create
这就是当前 Rust 侧真正把业务运行提交到 K8s 的代码路径。
7. Pod 内执行生命周期
7.1 容器启动后先做什么
job_builder::build_run_script(构建容器启动脚本)会生成启动脚本,脚本大致会:
- 创建
/workspace/in、/workspace/out、/workspace/tmp - 打印 run、session、user、workspace 信息
- 如有挂载授权,列出挂载目录文件
- 根据
installStrategy决定是否做运行时安装 - 执行 verify 命令
- 输出 package 和输入文件信息
- 进入具体执行模式
7.2 当前执行模式怎样理解
当前代码里的 ExecutionMode(执行模式)主要有两种:
CliVerifyOpenAiCompatibleApi
它们表达的是容器内执行入口的行为方式。从架构角度,这仍然属于 agentcore 的执行适配层。
7.3 容器内执行伪代码
容器启动
-> 读取 /workspace/in/run-context.json
-> 读取 /opt/agent/package/*
-> 读取 provider 环境变量
-> 运行 agentcore
-> 把主回答写到 /workspace/out/final-answer.md
-> 把调试信息写到 /workspace/out/*
-> 进程退出
8. backend 如何知道 run 已经结束
ApplicationService::refresh_run_status(刷新运行状态)会做两类检查。
8.1 第一类:看输出目录
它会检查:
runtime-error.txtfinal-answer.mdprovider-response.jsonllm-run-summary.jsonexecution-mode.txt
如果这些文件已经出现,就可以直接推断 run 已完成或失败。
8.2 第二类:看 K8s Job / Pod 状态
如果输出目录还看不出来,backend 会调用 K3sOrchestrator::inspect_run_status(检查运行状态),内部通过:
Api<Job>::get_optApi<Pod>::list
读取 Job 和 Pod,再映射成 RunStatus。
8.3 状态刷新伪代码
#![allow(unused)]
fn main() {
async fn refresh_run_status(run: RunRecord) -> RunRecord {
if let Some(status) = detect_output_status(run.workspace_root.join("out")).await {
update_run_status(run, status); // 输出已经落盘,直接完成状态刷新
} else if let Some(observation) = orchestrator.inspect_run_status(&run).await? {
update_run_status(run, observation); // 读取 Kubernetes Job / Pod 状态
}
if run.status == COMPLETED {
sync_run_assistant_message(&run).await?; // 把 final-answer.md 写回 session history
}
persist_run(run).await // 保存新的 run 状态
}
}
9. 同一 session 内如何保持连续性
这是当前生命周期里最关键的一部分。
9.1 文本对话连续性
文本对话连续性由下面两个动作保证:
sync_session_conversation_history(同步会话历史)
在新 run 开始前刷新同一 session 下旧 run 的状态。sync_run_assistant_message(同步助手回答)
在 run 完成后读取final-answer.md,把它回填进SessionRecord.history。
因此当前同一 session 的下一轮一定可以拿到上一轮的主回答。
9.2 文件结果连续性
run 完成后,当前代码会保留:
RunRecordworkspace_rootworkspaces/<run-id>/out/*
因此用户下次回来时,仍能查看历史结果文件。
9.3 上传文件连续性
只要 ArtifactRecord 和 uploads/ 下的文件还在,后续 run 就可以继续引用这些文件。
9.4 agentcore checkpoint 连续性
从运行时模型看,agentcore checkpoint 连续性应采用“同一 session 挂回同一状态目录”的方式。当前代码已经支持:
- 按 session 保存历史
- 按 session reopen
- 按 run 保存结果
当前代码还没有显式支持:
session级状态卷checkpoint_root元数据- 自动把上一轮 checkpoint 挂回下一轮
因此今天已经落地的是“会话历史连续”,即将补齐的是“agentcore 内部状态连续”。
10. 用户离开后再回来,会发生什么
10.1 只是关闭前端页面
如果用户只是关闭前端页面,没有关闭 session,那么后续直接拿原来的 session_id 继续创建 run 即可。
10.2 显式关闭 session
如果用户调用了 close,会话仍保留历史和结果,但会拒绝新的写入。后续调用 reopen 后,同一个 session_id 又可以继续创建新的 run。
10.3 一个月后继续同一个 session
今天这套代码已经能保证下面这些内容在一个月后仍可恢复:
- 会话历史
- 上传文件索引
- 历史 run 记录
- 历史结果文件
如果还要恢复 agentcore 内部 checkpoint,就需要把 checkpoint 写到持久化挂载目录,并在 backend 里保存该目录索引。这是当前生命周期模型下一步最自然的扩展点。
11. run 结束后,K8s 如何处理资源
当前语义如下:
- Job / Pod:由 Job TTL 或取消逻辑处理
ConfigMap/Secret:取消时会显式删除,正常完成后的统一 GC 还需补齐workspaces/:backend 主机磁盘继续保留state.json:backend 继续保留
K8s 只负责容器对象生命周期,不负责清理对话历史和业务文件索引。
当前实现边界与演进建议
这一章用于把今天代码里已经实现到哪里、还没有实现到哪里,明确说清楚。
1. 当前已经实现的能力
1.1 控制面与状态管理
已经具备:
session、artifact、mount grant、run的完整 APISessionStatus的close / reopen语义x-user-id基础所有权隔离- 本地
state.json持久化
1.2 运行时编排
已经具备:
RuntimeProfile装载agent package装载- 每 run 独立 K3s
Job - package
ConfigMap - provider
Secret - Job / Pod 状态观察
- 集群节点健康与预检 API
1.3 文件管理
已经具备:
- 上传文件持久化
- 相对路径保留
- 每 run 独立工作区
- 输出文件列举与安全读取
- mount grant 白名单校验
1.4 多轮对话
已经具备:
SessionRecord.history累积- 已完成 run 的主回答回填
- 同一
session的 reopen 后继续运行
2. 当前最重要的实现边界
2.1 当前 session 连续性来自持久化状态
当前代码已经连续保存:
- 对话历史
- 已上传文件
- 历史结果文件
- run 元数据
当前代码还没有复用:
- 常驻容器进程
- 进程内缓存
- shell 状态
- 工具服务进程
因此当前连续性属于“持久化状态连续”。
2.2 当前还没有显式的 session 级 checkpoint 模型
这是当前最需要被说清楚的边界。
今天的代码里:
- 每个
run都有独立workspace_root - 历史结果文件会长期保留
- 会话历史会长期保留
今天的代码里还没有:
checkpoint_root字段- session 级持久化状态卷
- 自动把上一轮 checkpoint 挂到下一轮
- resume token / resume handle API
因此,如果 agentcore 本身支持 checkpoint,当前 backend 还需要再补一层“session 级状态目录索引”,才能把 agentcore 的内部状态恢复完整接上。
2.3 当前 runtime family 名称仍是样例标识符
当前源码里的 RuntimeFamily(运行时家族)枚举仍使用样例标识符,例如:
CodexClaudeCode
这不影响运行时架构本身,因为真正决定容器行为的是:
imageexecutableinstallStrategyexecutionMode
但它说明当前代码在“可扩展 family 标识”上仍有代码级约束。如果后续要把 runtime family 做成完全开放的扩展点,可以把它进一步放宽成字符串或可扩展枚举。
2.4 当前 K8s 资源回收和工作区回收还没有完全打通
当前 run 结束后:
- Job / Pod 会被 TTL 或取消逻辑处理
- workspace、artifact、run record、session history 会继续保留
- 正常完成态 run 的
ConfigMap和Secret还没有统一 GC
因此如果后续 run 数量很多,需要补充后台 GC 或 retention policy,把:
- 旧
workspaces/ - 旧
uploads/ - 旧
ConfigMap - 旧
Secret
纳入统一清理策略。
2.5 当前工作区基于 hostPath
hostPath 的优点是简单直观、便于单节点 K3s 验证。当前代价也很明确:
- 强依赖 backend 所在节点的本地磁盘
- 磁盘压力会直接影响调度
- 不适合做跨节点迁移
- 隔离强度弱于更高级的沙箱方案
2.6 当前 backend 采用本地持久化控制面
FileBackedStore 使用本地:
state.jsonuploads/workspaces/
所以现在 backend 更像:
- 单实例控制面
- 方便联调和演示的最小实现
多副本水平扩展控制面和外部数据库支撑的生产级 control plane 还需要后续演进。
2.7 当前没有流式输出和事件推送
现在前端 / helper 获取 run 进度主要靠轮询:
GET /api/v1/runs/{id}GET /api/v1/runs/{id}/results
当前没有:
- WebSocket
- SSE
- stdout / stderr 实时流
- token 级别的模型输出流
2.8 当前没有“远端容器直接调用本地用户工具”的桥
这点必须明确写清楚。
当前没有:
- 本地 Git CLI 代理
- 本地 shell 代理
- 本地 IDE / editor RPC
- 本地文件系统 FUSE 挂载代理
macos-helper 当前只负责文件同步、结果回写和本地 UI 代理。
3. 支持本地工具调用的可行方案
虽然当前没有本地工具桥,但有几种可行方案可以接到现有架构上。
3.1 方案 A:Helper 本地工具 RPC 桥
最自然的方案是让 macos-helper(macOS 本地桥接服务)增加一个本地工具执行层:
- helper 在本地暴露受控 RPC 接口
- backend 在创建 run 时下发一组 session 级或 run 级工具描述
agentcore通过受控 API 调用 helper- helper 在本机执行 Git、shell、编辑器脚本等工具
- 执行结果再回传给
agentcore
这个方案需要补齐:
- 工具白名单
- 参数校验
- 审批模型
- 超时和取消
- 审计日志
- 会话级认证
3.2 方案 B:Helper 反向长连接工具隧道
可以让 helper 主动和 backend 保持长连接,由 backend 把工具请求转发给 helper,再由 helper 本地执行。
这个方案的好处是:
- 后端不需要直接访问用户本机
- 连接模型更适合 NAT 后的用户机器
需要补齐的能力包括:
- 双向流式消息协议
- 工具请求队列
- 断线重连
- session / run 级权限绑定
3.3 方案 C:本地只负责文件,同步后由远端容器执行
这是当前代码最接近的模式:
- helper 只负责把本地目录同步成
artifact - 远端容器只处理文件,不直接调用本地工具
它实现简单,安全边界清楚,但能力上限较低。
3.4 方案 D:受控宿主机工具代理
如果未来不仅要访问本地工具,还要访问 backend 所在 Linux 主机上的受控工具,也可以增加宿主机工具代理层。
这个方案需要非常严格的:
- 白名单
- 身份隔离
- 审计
- 资源限制
因为它的权限风险明显高于文件同步。
4. 当前架构为什么仍然是合理的
尽管上面有不少边界,但以当前代码目标来看,这套方案仍然成立,因为它先解决了最核心的几件事:
- 把 Agent 运行从 backend 进程里剥离到了独立容器
- 把
agent package、运行时模板、provider、workspace 拆成了清晰的组合关系 - 把 session 多轮上下文和 run 物理隔离分开建模
- 让前端、helper、backend、K3s 之间有了可验证的 API 契约
5. 如果继续演进,优先级建议是什么
5.1 第一优先级:补齐 session 级状态目录
优先补齐:
checkpoint_root- session 级持久化状态卷
- resume / restore API
- 输出文件到下一轮输入的受控提升机制
这是把“会话历史连续”升级成“完整运行时状态连续”的关键一步。
5.2 第二优先级:把“声明型策略”变成“强执行策略”
优先补齐:
permissionProfile到真实能力矩阵networkProfile到真实NetworkPolicybuiltInTools到真实工具调度层
5.3 第三优先级:补齐本地工具桥
建议优先落地 Helper 侧 RPC 桥,因为它和当前“本地 Helper + 云端 backend”结构最匹配。
5.4 第四优先级:把工作区从 hostPath 升级出去
重点是:
- 降低宿主机磁盘压力对调度的直接影响
- 支持更强的多节点与弹性调度
- 把 workspace 生命周期和 node 生命周期更好地解耦
5.5 第五优先级:把 backend 升级成可水平扩展控制面
也就是逐步把:
state.json- 本地 uploads
- 本地 workspaces 元数据
迁移到更适合多副本的外部存储体系。
6. 当前技术方案的最终判断
如果基于当前源码评价,这套 backend 已经具备“Agent 商店运行时最小可用控制面”的完整骨架:
- 会话有了
- 文件有了
- 挂载有了
- run 有了
- K3s 编排有了
- provider 注入有了
- 多轮历史有了
- 节点预检和状态观察也有了
当前离“生产级多租户高隔离 Agent Runtime 平台”的主要差距集中在:
- session 级 checkpoint 连续性
- 本地工具桥
- 强权限
- 强网络策略
- 工作区存储外部化
- 控制面外部化持久存储