完整的Agent Harness由几个相互配合的核心能力层构成,它们共同决定了Agent的行为和性能:
- 任务规划:通过内置的任务清单功能,将复杂目标分解为一系列可执行的子任务,并追踪每一步的执行状态。
- 虚拟文件系统:为Agent提供一个内置的虚拟文件管理系统,支持读写、编辑、搜索等操作,使其能像人类开发者一样进行工作。
- 子Agent委派:主Agent可以创建专门的“子Agent”来执行特定任务,实现任务隔离和上下文压缩。
- 上下文管理:通过文件系统等策略,管理长对话中的Token消耗,防止因上下文溢出而导致性能下降。
- 可插拔中间件:通过一组Hook函数,在模型和工具调用的关键节点注入自定义逻辑,实现缓存、重试、日志、PII检测等增强功能。
- Skills技能系统:将提示词、文档、脚本等打包为可复用的标准模块,为Agent注入特定领域的知识和能力。并做到自我进化。
- 持久化记忆:为Agent提供短期(对话历史)和长期记忆能力,使其能从历史交互中学习。
项目中Harness架构的核心能力(一):长周期复杂任务的Planning能力
提供 write_todos 工具,Agent 可维护结构化任务列表(pending/in_progress/completed),持久化在 Agent state 中。
项目实现:
| 项目 | 说明 |
|---|---|
| DeepAgents 内置 | write_todos 工具由框架自动注册,无需显式配置 |
| 受益方 | 三个 Agent 全部受益:主 Agent 协调复杂任务、analyst 执行 5 步分析流程、order 执行 4 步订单操作 |
| 典型触发 | analyst 启动时:”ls → 读 SKILL.md → 收集数据 → 分析 → 图表 → 报告”,框架自动建议拆为 todo |
| 持久化 | CHECKPOINTER(MongoDBSaver)持久化 Agent state,跨重启保留任务进度 |
项目中harness架构的核心能力(二):安全的和动态路由的文件系统
项目实现:通过 CompositeBackend 实现三层路由文件系统。
路由分流效果:
| 路径前缀 | 后端 | 生命周期 | 隔离粒度 |
|---|---|---|---|
| /AGENTS.md | OpenSandbox | 沙箱生命周期 | 全局一份(只读) |
| /memories/{user_id}/ | StoreBackend → MongoDB | 跨会话永久 | 按 user_id 隔离 |
| /persisted-skills/ | StoreBackend → MongoDB | 跨会话永久 | 按 namespace 组织 |
| /skills/ | OpenSandbox | 沙箱生命周期 | 每次沙箱重建 |
| /data/、/analysis/ | OpenSandbox | 沙箱生命周期 | 临时工作区 |
backend = lambda rt: CompositeBackend(
default=sandbox_backend, # OpenSandbox(临时文件、代码执行)
routes={
"/memories/": StoreBackend( # 用户偏好 → MongoDB 持久化
runtime=rt,
namespace=lambda rt: (getattr(rt.runtime.context, 'user_id', 'default_user'),),
),
"/persisted-skills/": StoreBackend( # 持久化技能 → MongoDB
runtime=rt,
namespace=lambda rt: SKILLS_STORE_NAMESPACE,
),
},
)
项目中harness架构的核心能力(三): Subagent的任务委派
主 Agent 拥有 task 工具,每次调用创建全新 Agent 实例,独立上下文,执行完返回单个报告。支持并行执行和特殊化配置。
项目实现:
子 Agent 配置加载:loader.py
YAML 配置文件 (configs/*.yaml)
│ load_subagent_configs() ← YAML → dict 列表
│ resolve_subagent_tools() ← 工具名子串匹配 → 可调用对象
│ _validate_subagent_config() ← 必填字段校验
▼
两个子 Agent 对比:
| procurement-analyst | procurement-order | |
|---|---|---|
| YAML | procurement_analyst.yaml | procurement_order.yaml |
| description | 含触发关键词(”分析”、”对比”、”报告”…) | 含触发关键词(”下单”、”创建订单”、”修改”…) |
| 工具数 | 8 个(6 MCP + web_search + generate_visualization) | 5 个(3 MCP + web_search + request_order_info) |
| 技能 scope | /skills/procurement/ | /skills/order/ |
| 中间件 | Summarization + 调用限制(50/200) | 调用限制(20/50) |
| interrupt_on | 无 | order_create: approve/reject, order_update: approve/reject |
| context 隔离 | 独立上下文,analyst 长篇工具结果不污染主 Agent | 独立上下文,订单操作不干扰分析对话 |
委派模板(AGENTS.md:74-121):
主 Agent 的 AGENTS.md 为每个子 Agent 定义了结构化的 task 调用模板,确保 user_id/username/偏好 正确传递到子 Agent。
项目中harness架构的核心能力(四):Context Management — 上下文管理
四种策略——Input context(启动时加载)、Compression(自动 offload + summarization)、Isolation(子 Agent 隔离)、Long-term memory(跨线程持久化)。
项目实现:
a. Input Context
| 输入 | 来源 | 何时加载 |
|---|---|---|
| system_prompt | prompts.py | Agent 创建时 |
| AGENTS.md | 本地 → 沙箱 /AGENTS.md(main_agent.py:122-124) | Agent 创建时,memory 参数 |
| 用户偏好 | /memories/{user_id}/preferences.md(StoreBackend) | 每轮对话前 read_file |
| 技能 frontmatter | 各 SKILL.md 的 YAML frontmatter(渐进式披露) | Agent 启动时扫描 |
| 用户上下文 | ContextInjectionMiddleware 注入 SystemMessage | 每轮 before_agent |
| 子Agent报告路径/订单确认 | 委派结果 | 收到返回后 |
b. Compression
| 机制 | 实现 | 配置 |
|---|---|---|
| 自动 Offloading | DeepAgents 内置,工具结果 > 20k tokens → 写入文件,上下文只保留路径+前10行预览 | 框架默认 |
| 自动 Summarization | SummarizationMiddleware,上下文 85% 窗口 → 自动压缩历史为摘要,完整历史保存到 /conversation_history/ | tools_summarization.py |
| 手动 compact_conversation | SummarizationToolMiddleware 注册的工具,Agent 在收到子Agent长篇报告/对话超6轮/用户频繁切换话题时主动调用 | tools_summarization.py:40-42, model=deepseek-v4-flash |
c. Isolation(子 Agent 上下文隔离)
analyst 执行 5 步分析流程时会产生大量中间结果(MCP 返回的供应商/零部件 JSON、Python 脚本输出、图表生成参数等),全部隔离在子 Agent 自己的上下文窗口中。主 Agent 只收到最终的结构化报告。
d. Long-term Memory
| 存储 | 后端 | 内容 |
|---|---|---|
| 用户偏好 | StoreBackend → MongoDB | /memories/{user_id}/preferences.md |
| 持久化技能 | StoreBackend → MongoDB | /persisted-skills/ 下技能文件 |
| 对话状态 | MongoDBSaver | Checkpoint(HITL 中断恢复、跨重启对话) |
| 对话历史 | MongoDB(history.py) | 会话列表/消息历史 |
项目中harness架构的核心能力(五):Skills — 渐进式技能系统(自我进化)
遵循 Agent Skills 标准,每个技能是含 SKILL.md 的目录,支持渐进式披露——启动时只读 frontmatter,需要时再加载完整内容。
项目实现:
Skills自我进化(4 阶段)
flowchart TD
%% 样式定义
classDef stage1 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
classDef stage2 fill:#e0f2f1,stroke:#00695c,stroke-width:2px;
classDef stage3 fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
classDef stage4 fill:#ede7f6,stroke:#5e35b1,stroke-width:2px;
%% 阶段 1
subgraph S1 ["阶段 1: 同步到沙箱"]
direction TB
Task1["<b>SkillsSyncMiddleware</b> (增量)<br>+ <b>_seed_files()</b> (首次)<br>路径: src/skills/ → /skills/{scope}/"]:::stage1
end
%% 阶段 2
subgraph S2 ["阶段 2: 渐进式发现"]
direction TB
Task2["<b>目录扫描</b><br>ls /skills/procurement/<br>解析 SKILL.md Frontmatter"]:::stage2
Task3["<b>按需加载</b><br>read_file() (仅需要时加载)"]:::stage2
end
%% 阶段 3
subgraph S3 ["阶段 3: 运行时创建/下载"]
direction TB
Task4["<b>skill-management</b><br>execute(download_skill.py)"]:::stage3
Task5["<b>部署与验证</b><br>解压到 /skills/main/ → 测试验证"]:::stage3
end
%% 阶段 4
subgraph S4 ["阶段 4: 分配与持久化"]
direction TB
Task6["<b>分配</b><br>assign_skill(skill_name, agent_name)<br>复制到 /skills/{scope}/"]:::stage4
Task7["<b>持久化</b><br>store.aput() (StoreBackend)"]:::stage4
Task8["<b>清理与恢复</b><br>UserSkillsRestoreMiddleware & 清理压缩包"]:::stage4
end
%% 流程连线
Task1 --> Task2
Task2 --> Task3
Task3 --> Task4
Task4 --> Task5
Task5 --> Task6
Task6 --> Task7
Task7 --> Task8
基于SkillsSyncMiddleware发现链
yaml system_prompt: "ls /skills/procurement/ 扫描可用技能"
→ 返回: chart_params.md, procurement-analysis/, supplier-price-urls/, web-scraper/
→ read_file("procurement-analysis/SKILL.md") 加载操作手册
→ 按流程激活各技能
项目中harness架构的核心能力(六): Human-in-the-Loop — 人工介入
通过 interrupt_on 参数选择性地在工具调用前暂停,等待人工审批或修改。
项目实现:双层中断体系。
第1层: 数据补充 (tool-internal interrupt)
request_order_info 工具内部调用 interrupt()
├─ 触发时机: Schema校验发现必填字段缺失
├─ 恢复格式: {"supplement": "自由文本补充信息"}
└─ 循环: 校验→补充→解析→校验...直到完整
第2层: 最终审批 (interrupt_on 配置)
order_create / order_update 的 interrupt_on 配置
├─ 触发时机: 数据完整,准备执行
├─ 恢复格式: {"decisions": [{"type": "approve"|"reject"}]}
└─ 特性: approve → 执行 / reject → 取消
代码位置:
| 组件 | 位置 | 职责 |
|---|---|---|
| interrupt_on 配置 | procurement_order.yaml:30-34 | 声明哪些工具需审批 |
| request_order_info | hitl_tools.py | 数据补充中断工具 |
| SSE 中断检测 | chat.py | stream_mode=[“messages”,”values”],检测 values 中的 interrupts |
| 前端恢复端点 | chat.py POST /chat/{thread_id}/resume | 接收 ResumeRequest → Command(resume=…) |
| 前端中断横幅 | InterruptBanner.vue | 两种模式:数据补充输入框 vs HITL 审批卡片 |
两层中断互不干扰的原因:request_order_info 不在 interrupt_on 列表中,不会被 HITL 中间件拦截。两者是顺序关系——先补齐数据,再审批执行。
项目中harness架构的核心能力(七):Memory — 用户偏好记忆管理
项目实现:以用户作用域来管理Memory
记忆层次
┌─ 用户层(读写,跨会话持久化)──────────────────────┐
│ /memories/{user_id}/preferences.md │
│ 来源: StoreBackend → MongoDB │
│ 内容: preferred_output, preferred_chart_type, │
│ preferred_currency, preferred_language, │
│ recent_suppliers, recent_queries │
│ 加载: 每轮对话前 Agent 主动 read_file │
│ 更新: Agent 手动 edit_file(偏好变更) │
│ + MemoryUpdateMiddleware 自动维护 │
│ (recent_suppliers + recent_queries) │
└───────────────────────────────────────────────────┘
记忆更新流程
【手动路径】用户说"以后都用饼图"
→ Agent edit_file /memories/{user_id}/preferences.md
→ 更新 preferred_chart_type: pie
【自动路径】对话结束后
→ MemoryUpdateMiddleware.aafter_agent()
→ LLM 提取 {suppliers: [...], query: "..."}
→ store.aput() 写入 StoreBackend
→ 跨会话保留
项目的最大安全问题: 基于OpenSandbox来解决
OpenSandbox 就是这个项目的唯一安全边界。所有 Agent 行为——运行 Python 脚本、执行 Shell 命令、读写文件、下载技能、爬取网页——全部发生在这个容器内部。OpenSandbox 的配置决定了这道墙有多厚。
综合风险评估
| 风险 | 等级 | 影响 | 当前状态 |
|---|---|---|---|
| 沙箱无网络出口限制 | 高 | 数据外传、内网探测、云凭证窃取 | network_policy 解决 |
| 无 URL 白名单 (download_skill) | 中 | 从恶意服务器下载 | network_policy 解决 |
| skill_name 路径穿越 | 中 | assign_skill 访问任意沙箱路径 | 文件操作 |
| user_id 隔离弱 | 中 | 多用户数据可被跨用户访问 | 文件操作 |
| AGENTS.md 无技术只读 | 低 | Agent 可能修改全局准则 | 防止文件修改 |
| 无边载配额/进程限制 | 低 | 沙箱资源耗尽 | docker容器可以随时增加 |
| download_sandbox_file 无敏感文件过滤 | 低 | 沙箱文件可外传到本地 | 无限制 |
OpenSandbox 在这里扮演的角色:它是最后的兜底线。即使Skills全部失守——恶意技能被下载、解压、恶意代码、文件操作——第 5 阶段执行时,恶意代码仍然被关在容器里。容器的网络策略决定了恶意代码能造成多大的外部影响。
Sandbox 做边界定义:墙内 vs 墙外
┌──────────────────────────────────────────────────────┐
│ 宿主机 (Windows) │
│ │
│ FastAPI(Agent服务器) MongoDB MCP Server │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ OpenSandbox 容器 (Linux) │ │
│ │ │ │
│ │ /skills/ /data/ /analysis/ │ │
│ │ /memories/ /AGENTS.md /workspace/ │ │
│ │ │ │
│ │ Python,Go,Java,Nodejs 环境 │ │
│ │ execute("任意命令") → 在这里运行 │ │
│ │ web-scraper → 从这里发出 HTTP 请求 │ │
│ │ 技能脚本 → 在这里被调用 │ │
│ │ │ │
│ │ 网络出口 ──────────────────────────────→ ??? │ │
│ └────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘
OpenSandbox 防护了三件事:
| 防护 | 机制 |
|---|---|
| 宿主机文件系统不可见 | 容器内 /skills/、/data/ 都是容器自己的文件系统,与宿主机 E:\my_project\ 物理隔离 |
| 进程隔离 | execute() 启动的进程在容器内运行,无法直接操作宿主机进程 |
| 资源限制 | cpu: “2”、memory: “4Gi” 防止容器耗尽宿主机资源 |