Agent Scope Java 2.x 系列【29】Harness:在沙箱容器中运行 Skill 执行机制
文章目录
1. 概述
沙箱容器提供强隔离环境,所有 Shell 命令、文件 IO 操作都在容器内部执行,保证宿主环境不受污染。Skill 的脚本文件(*.sh、*.py 等)存放在宿主文件系统,但 Agent 需要在隔离容器内执行这些脚本。
HarnessAgent 架采用 物化 → 工作区投影 → 容器内执行 三阶段透明处理流程,对上层 Agent 屏蔽文件分发细节。
2. Skill 分类与路径映射
Skill 分为两大来源,四大层级:
-
工作区
skill:workspace/skills/<userId>/skills/
-
市场
skill:- 项目全局
Git/MySQL/Nacos/classpath
两大来源的 skill 在进入沙箱前存放位置不同,最终在容器内拥有固定路径:
- 工作区:
/workspace/skills/<name> - 技能市场:
/workspace/.skills-cache/
3. 核心三步执行流程
3.1 第一步:市场 Skill 物化落地
市场 Skill 从配置中心拉取后仅为内存字节数组,无法直接被 Shell 调用。在每一轮推理调用开始前,MarketplaceStager 将资源持久化写入宿主目录:./.skills-cache/ 。
落地策略:
- 文件 SHA-256 增量写入
对每个文件计算内容哈希,仅写入发生变更的文件,未变更文件直接复用本地缓存,减少磁盘IO。 - 孤儿目录自动清理
同步清理已下架、已移除的Skill残留目录,防止缓存目录无限膨胀。 - 自动恢复脚本可执行权限
资源入库序列化时会丢失POSIX文件权限,框架使用启发式规则自动补充+x权限:- 规则
1:文件头部包含shebang #!; - 规则
2:后缀为脚本类型:.sh/.bash/.py/.rb/.pl/.js/.mjs; - 权限策略:仅对已有读权限的文件追加执行权限;
- 静态文件(
.json/.md/.txt)保持默认权限644。
- 规则
仅针对市场
Skill,工作区Skill已存在于磁盘,直接跳过此步骤。
3.2 第二步:工作区投影进入沙箱
沙箱实例调用 start() 启动时,Harness 将宿主工作区静态资源打包为 tar 压缩包,解压后注入容器内的 /workspace 目录。
配置项 workspaceProjectionRoots 默认包含下面目录:
AGENTS.md
skills/
subagents/
knowledge/
.skills-cache/
自动包含两类 Skill 目录,两块内容会一同被打包,批量注入沙箱:
- 工作区
skills/ - 上一步生成的缓存目录
.skills-cache/
缓存优化机制:
- 框架对所有待投影文件整体计算
SHA-256指纹:- 本次哈希
=上一次哈希:跳过文件注入,复用容器内已有文件; - 文件内容发生改动:重新打包并同步文件到沙箱。
- 本次哈希
- 多次调用
call()执行技能,不会反复拷贝不变的文件,提升沙箱启动速度。
两个配置开关配置位置(DockerFilesystemSpec / KubernetesFilesystemSpec
):
| 配置项 | 作用 |
|---|---|
| workspaceProjectionRoots(List) | 自定义需要打包投影的根目录,默认只包含 skills、.skills-cache 等 |
| workspaceProjectionEnabled(false) | 关闭整个工作区投影;关闭后沙箱内不存在技能文件,脚本无法运行 |
3.3 第三步:自动渲染容器路径,执行脚本
框架自动给技能文件拼接容器内的绝对路径,Agent 只负责执行命令,完全不用关心文件来自本地工作区还是技能缓存。
3.3.1 自动替换文件根路径
沙箱运行时,框架会统一把路径前缀改成容器内部目录:
| 技能来源 | 容器内最终路径前缀 |
|---|---|
| 本地工作区自定义技能 | /workspace/skills/<技能名> |
| 从技能市场下载的缓存技能 | /workspace/.skills-cache/<来源>/<技能名> |
3.3.2 自动生成执行命令
框架拼接出完整绝对路径,最终执行 Shell 命令类似:
python3 /workspace/skills/code-reviewer/scripts/run-checks.sh <目标路径>
命令直接在沙箱容器内运行,读取的就是上一步投影进来的文件。
3.3.3 路径自适应能力
如果沙箱运行时工作区挂载根目录发生变更(例如运行根目录改为 /home/agentscope/workspace),框架会自动替换路径前缀,保证命令始终指向正确文件,Agent 无需修改代码。
4. 跨调用保留执行副作用
多次连续调用 call() 执行同一个技能脚本时,会产生两类运行数据:
- 静态文件:
skills源码、配置文件(由工作区投影每次注入) - 运行副作用数据:
- 用
pip install、npm install安装的第三方依赖包 - 代码编译产出、临时文件、生成的业务产物
- 用
副作用数据只存在于容器运行时,默认沙箱销毁后全部丢失,下一次调用又要重新安装依赖,速度很慢。
开启快照配置 snapshotSpec(...) 后,框架会把容器内整个 /workspace 目录打包保存为镜像快照,并绑定一个唯一的 scope key。
完整执行顺序(同一 scopeKey):
- 本次调用开始:先拉取并恢复快照,把上一次执行留下的依赖、编译产物全部还原到容器里。
- 再执行第二步:叠加执行工作区文件投影(
hydrate),只覆盖本地有变更的技能源码,不会覆盖已经装好的依赖。 - 运行脚本,产生新的副作用。
- 本次调用结束:更新快照,持久化新的产物与依赖。
最终效果:
- 第一次运行:全新容器,执行安装依赖。
- 后续同
scopeKey的调用:直接恢复快照,依赖已存在,不需要重复执行pip/npm install。 - 技能代码文件依旧会按投影机制实时更新,代码改了会覆盖,已安装的包保留。
快照用来持久化脚本运行产生的安装包、编译产物这类运行副作用。只要调用使用相同
scopeKey,沙箱先还原上一次的运行环境,再覆盖最新的技能代码,做到依赖只装一次,反复调用零重复安装。
5. 两种 Skill 加载链路完全隔离
5.1 读取 Skill 元信息(不需要沙箱)
调用 load_skill_through_path 加载 SKILL.md、引用文档、入参定义时,直接读取宿主文件或内存数据,不经过容器、不依赖目录投影。
即便关闭 workspaceProjectionEnabled,Skill 描述依然可以正常加载。
5.2 执行 Shell 脚本(必须走沙箱+投影流程)
只有调用 execute_shell_command 运行脚本文件时,才需要完成「物化→投影→容器执行」整套流程。关闭投影会直接导致脚本文件在容器内找不到,执行报错。
更多推荐


所有评论(0)