ontology-harness 实现原理学习笔记
一句话:本仓库是站在 DeerFlow 之上的 FastAPI 执行层(Harness)。DeerFlow 负责 Agent 运行时、checkpoint、沙箱工具与 MCP 装载;Harness 把「一轮对话 / 一次主动任务」变成可对客户端推送或轮询的 Run + Message + Blocks,并用自研中间件做本体编排、记忆注入、技能白名单、主动任务与产品协议。
本体推理不在本仓库:通过 .mcp.json 连接平台 MCP copy_ontology。当前 config.yaml 编排面为 none(先跑通对话/技能,MCP 健康项可标 disabled)。
1. 产品定位(设计文档口径)
文档 docs/1030/agent-harness-1030-版本规范.md · Harness 保持纯执行器:
- 调度、接收范围、频控留在 BFF;Harness 补系统触发的执行能力
- 无用户消息也能创建并跑完一次 run:
POST /runs/proactive - 终态后把结果交回 BFF:
GET /runs/{id}/result - 主动内容写入用户主会话:
POST /threads/messages - 对话 agent 写 DeerMem,问候读同一桶,并向 BFF 提供记忆读接口
- 用户对话现有协议保持可运行:SSE、快照、
cancel/retry/continue/answer
读文档注意 版本规范正文里仍残留早期数字(如「五枚举」「mes-patrol」「池 6/2/4」)。以当前源码为准:services/proactive.py 的 TASKS 是 6 个 task_type;执行器名是 anomaly-detection / brief-generation / greeting_first / greeting_daily;池容量是 user 12 · batch 4 · per_user 8(见 agent/executors.py 的 POOL_WORKERS)。
2. 整体架构图(DeerFlow vs Harness)
图 A · 能力边界:左侧 Harness 产品化,右侧 DeerFlow 运行时。
flowchart LR
subgraph C["调用方"]
BFF["BFF / H5"]
end
subgraph H["Harness"]
API["controllers"]
SVC["Run / Proactive"]
MW["中间件 + Blocks"]
DB["harness 表"]
end
subgraph D["DeerFlow"]
CL["Client x5"]
G["Agent 图"]
CP["checkpoint"]
SB["沙箱 / MCP"]
end
subgraph X["外部"]
ONTO["copy_ontology"]
MEM["DeerMem"]
end
BFF --> API --> SVC --> CL --> G
G --> CP
G --> SB --> ONTO
SVC --> MW --> DB
MW -.-> MEM
G -.-> MEM
DeerFlow 给你什么
- 可 checkpoint 的 Agent 回合与工具循环
- 沙箱、技能目录约定、内置工具(如
present_files) - MCP 插件装载框架
- Memory 桶存储(写侧仍走其 MemoryMiddleware)
Harness 补什么
- 产品化 Run / Thread / Blocks / 补发协议
- 三池并发与
queued(单会话仍不排队) - 本体编排面、技能白名单、工具组裁剪
- 记忆注入快照、主动执行器、军师业务契约
3. DeerFlow vs 自研:边界怎么切
| 层级 | 谁做 | 落到哪里 |
|---|---|---|
| LLM Agent / LangGraph 图、工具循环 | DeerFlow | DeerFlowClient、agent 配置目录、checkpointer |
| 对话状态真相(可恢复中断) | DeerFlow | checkpoint(Postgres);harness 不另存执行状态 |
| 沙箱文件工具、bash、Tavily | DeerFlow | config.yaml 里 deerflow.* / tools |
| MCP 协议装载 | DeerFlow + Harness 合同校验 | Harness agent/mcp_setup/loader.py 只放行 copy_ontology |
| Run / 会话消息 / 交互 / 分享表 | Harness | store/models/ + Alembic(与 DeerFlow alembic_version 隔离) |
| 对外 HTTP | Harness | controllers/ |
| 流式事件 → UI Blocks | 混合 | services/agent.py + assembler / 中间件 custom 事件 |
| 系统提示词、技能清单拼装 | Harness 覆盖 | SystemPromptMiddleware + SkillsMiddleware |
| 记忆注入(读侧) | Harness | MemoryContextMiddleware:写成 checkpoint 里的 HumanMessage 快照(前缀缓存);须 memory.injection_enabled: false |
| 本体编排 | Harness | agent/copy_ontology.py + agent/middlewares/copy_ontology_* |
| 主动任务 | Harness | services/proactive.py + 四个执行器;不挂 copy_ontology、不写记忆 |
4. 一次 Run:创建 → 调度 → 执行 → 落盘 / 事件
核心文件:controllers/run.py → services/run.py(对话)/ services/proactive.py(主动)→ services/agent.py(不走公开 DeerFlowClient.stream,直接调 agent 的 stream / run_once)→ 中间件栈 → DeerFlow checkpointer。
图 B1 · 对话 Run:创建与排队(单会话不排队;忙则 409)。
sequenceDiagram
autonumber
participant BFF as BFF
participant API as API
participant RS as RunService
participant Pool as user池
participant DB as DB
BFF->>API: POST /runs
API->>RS: create
alt 会话忙
RS-->>BFF: 409 Busy
else waiting_user
RS->>RS: 当作回答续跑
else 空闲
RS->>DB: 插入 queued
RS->>Pool: submit
RS-->>BFF: 202 Run
end
图 B2 · 对话 Run:执行、流式事件与落盘(事件可丢,快照为准)。
sequenceDiagram
autonumber
participant Pool as user池
participant RS as RunService
participant AS as Agent
participant DF as DeerFlow
participant DB as DB
participant BFF as BFF
Pool->>RS: _execute
RS->>DB: running
RS->>AS: stream
AS->>DF: agent.stream
DF->>DF: checkpoint
loop 流式事件
DF-->>AS: token/custom
AS-->>RS: AgentEvent
RS->>DB: events+Blocks
RS-->>BFF: SSE
end
alt 提问 interrupt
RS->>DB: waiting_user
else 正常结束
RS->>DB: completed
end
Note over BFF,DB: 断线改拉 GET /threads/id
要点 AGENTS.md:DeerFlow checkpoint 是对话真相;harness 表只存 Run、事件日志、会话、给用户看的消息、交互、分享。一个 thread_id 同一时刻只有一个非终态 Run,不排队。跨会话并发靠三池;池满 proactive 留在 queued,对话撞忙会话是 409。
状态机(对话)
queued → running → completed | failed | cancelled
⇅
waiting_user (等用户回答提问;对外/服务层常用此名)
先写结束事件,再把行标终态 —— 客户端读到终态时日志一定完整。
一个库只有一个 harness 写进程;启动 recover() 把残留 queued/running 标失败(worker_lost)。
5. 三池 + task_type → agent 分流(以代码为准)
历史上曾是「单一 ThreadPoolExecutor(max_workers=max_concurrent_runs)」。现在:
RunService持有 user 池(对话)ProactiveService持有 batch + per_user 两池harness.max_concurrent_runs已不再读取- 启动断言
database.pool_size >= 24(三池 worker 之和;每个 worker 跑图各占一条 checkpointer 连接)
图 C · 三池分流:对外用 task_type,池按执行器名选择。
flowchart TB
U["POST /runs 对话"] --> CU["chat-agent"]
CU --> PU["user池 x12"]
P["POST /runs/proactive"] --> T{"task_type"}
T -->|quality / production / inventory| AD["anomaly-detection"]
T -->|company_brief| BG["brief-generation"]
T -->|greeting_first / daily| GR["greeting-*"]
AD --> PB["batch池 x4"]
BG --> PB
GR --> PP["per_user池 x8"]
task_type | 粒度 | 执行器 design_name | DeerFlow agent_name | 池 |
|---|---|---|---|---|
| (用户对话,无此字段) | 会话 | — | chat-agent | user · 12 |
quality_anomaly | 批次 | anomaly-detection | anomaly-detection | batch · 4 |
production_anomaly | 批次 | anomaly-detection | anomaly-detection | batch · 4 |
inventory_anomaly | 批次 | anomaly-detection | anomaly-detection | batch · 4 |
company_brief | 批次 | brief-generation | brief-generation | batch · 4 |
greeting_first | 用户 | greeting_first | greeting-first | per_user · 8 |
greeting_daily | 用户 | greeting_daily | greeting-daily | per_user · 8 |
# agent/executors.py(摘录)
POOL_WORKERS = {POOL_USER: 12, POOL_BATCH: 4, POOL_PER_USER: 8}
def normalize_agent_name(name: str) -> str:
"""只在构造客户端、找提示词文件时用;task_type 不经过它。"""
return re.sub(r"[^A-Za-z0-9-]", "-", name)
EXECUTORS = {
"anomaly-detection": Executor(..., POOL_BATCH),
"brief-generation": Executor(..., POOL_BATCH),
"greeting_first": Executor(..., POOL_PER_USER), # → greeting-first
"greeting_daily": Executor(..., POOL_PER_USER), # → greeting-daily
}
# services/proactive.py
TASKS = {
"quality_anomaly": Task(EXECUTORS["anomaly-detection"], KIND_ANOMALY),
"production_anomaly": Task(EXECUTORS["anomaly-detection"], KIND_ANOMALY),
"inventory_anomaly": Task(EXECUTORS["anomaly-detection"], KIND_ANOMALY),
"company_brief": Task(EXECUTORS["brief-generation"], KIND_BRIEF),
"greeting_first": Task(EXECUTORS["greeting_first"], KIND_GREETING),
"greeting_daily": Task(EXECUTORS["greeting_daily"], KIND_GREETING),
}
映射要点 对外路由字段是 task_type;池映射键是执行器内部名。创建 proactive Run 时已完成 task_type → Executor,再 self._pools[task.executor.pool].submit(...)。同键不同 task_type → 409;非法 task_type → 422。用户级必须带 user_id,批次不能带。
6. 本体能力:copy_ontology(自研编排 + 平台 MCP)
本体推理引擎不在 harness。三种编排面(harness.copy_ontology_orchestration):
| 编排面 | 中间件思路 | 适用 |
|---|---|---|
platform_runtime | Strategist 上下文循环 + CopyOntologyEvents | 平台侧主导检索/编排 |
scoped_tools | Budget + ScopedOntology + Events + Provenance | 本轮动态挂本体工具,有预算 |
none(当前 config) | 不连 MCP;Provenance 兜底;health 可标 mcp.disabled | 先跑通对话/技能 |
none / platform_runtime 会拿掉 DeerFlow 内置 ask_clarification(它不在 tools 池,只能在 copy_ontology.py 显式处理)。展示组件由中间件校验平台返回后直接发 custom 事件;files 例外走 present_files + PresentFilesMiddleware。
对话中间件大致顺序:SystemPrompt → Skills? → MemoryContext → ReferenceContext → LocalToolGroups? → ToolCallLog → 编排面相关 → PresentFiles …
7. 记忆:DeerFlow 存桶,Harness 管注入与 HTTP
- 写:仅
chat-agent通过 DeerFlowMemoryMiddleware(桶名默认chat-agent)。 - 读/注入:自研
MemoryContextMiddleware把get_context写成 checkpoint 里带harness_user_memory标记的 HumanMessage 快照(插在当前用户消息前),用 revision 做前缀缓存——facts 不变就不重读,避免把记忆塞进系统提示词导致整段前缀失效。 - 必须关 DeerFlow 自带注入(
memory.injection_enabled: false)。 - 问候在 compose 阶段读同一桶;BFF 经记忆 HTTP 读/覆盖画像与兴趣称呼。
8. 技能与工具面:配置白名单压过 DeerFlow 默认「全开」
五个 agent(1 对话 + 4 执行器)。启动时:
- 补写/校验各 agent 的
agent_dir(name)/config.yaml(执行器强制memory_enabled=false、mcp_plugins: [])。 agent/skills.py扫skills/public/*/SKILL.md,按config.yaml白名单校验;未知技能告警跳过。- 本地工具全进程一个池;对话用
LocalToolGroupsMiddleware藏未选中组;执行器用ToolsMiddleware。 - 技能隔离现用
prompt(保留 local sandbox host bash);接入 aio 沙箱后宜改回sandbox。
| agent | skills(当前) | tool_groups |
|---|---|---|
chat-agent | deep-research(已移除 markdown-render;成品约定直接 HTML) | null(全部) |
anomaly-detection | mes-quality-analysis、mes-production-analysis、html-card | file:read/write + bash |
brief-generation | html-card | file:read/write + bash |
greeting-first | 无 | [] |
greeting-daily | 无 | [web] |
9. 主动执行器
主动 Run:trigger_kind=proactive,无会话、无触发消息、不写事件、不进 harness_messages;按全局唯一 idempotency_key 去重;非流式 run_once;结果只在 completed 写 result_payload。
图 D · 主动 Run:创建 → 分池执行 → 取结果 → 可选写入主会话。
flowchart TD
A["BFF POST /proactive"] --> B{"校验参数"}
B -->|非法| E1["422"]
B -->|同键冲突| E2["409"]
B -->|同键命中| H["202 hit"]
B -->|新键| Q["queued 入库"]
Q --> S["按池 submit"]
S --> R["running + compose"]
R --> I["run_once"]
I --> F{"终态"}
F -->|成功| OK["completed + payload"]
F -->|失败| FL["failed"]
F -->|取消| CA["cancelled"]
OK --> G["GET /result"]
G --> M["可选写主会话"]
M --> N["空闲 append / 忙则下轮补写"]
今日相关 greeting_first 必须带 data: {name, junshi_name, interest_tags},落进 input_payload;其它任务带了 data 也会丢掉。Run 归属统一用 harness_runs.user_id(已删除 subject_user_id)。批次任务 user_id 为空。
# 结果信封经 GET /runs/{id}/result
# schema_version 按 kind 选;异常块可带 summary/metadata;
# 周报优先读沙箱固定路径 brief.html,不读模型正文。
# DeerFlow thread 用合成键 proactive-{run_id.hex},不建 harness_threads 行。
10. 客户端协议:Message + Blocks
- 事件流可丢,以线程快照为准(
docs/agent-ui.md)。 - 交互走 LangGraph
interrupt()+ harness interaction +POST /runs/{id}/answer。 - 块类型含 text / html / reasoning / 组件块等;未知 type 客户端应跳过。
- 近期协议增强:思考增量、本地工具卡、files 插在文本后、用户消息引用、游标分页、主动
message_kind。
11. 今日(2026-10-29)代码要点 · 原理向
| # | 变更 | 实现思路 |
|---|---|---|
| 1 | 首次登录欢迎语按用户资料生成 | POST /runs/proactive 增加 GreetingFirstData;材料进 input_payload,compose 拼进用户消息 |
| 2 | 删除 subject_user_id | 迁移 0006/0007;对话与用户级 proactive 统一写 user_id;响应直接读该列 |
| 3 | 对话成品统一 HTML | chat-agent 提示词约定直接出 HTML;配置去掉 markdown-render 技能 |
| 4 | 问候工具面收紧 | greeting-first 的 tool_groups: [];日常问候保留 web |
| 5 | 产量异常任务 | production_anomaly 与质量/库存共用 anomaly-detection;技能含 mes-production-analysis |
| 6 | 联网检索尽快收敛 | chat-agent 提示词侧约束检索步数/收敛,不改运行时协议 |
| 7 | 问候文案产品化 | greeting-first.md / greeting-daily.md 与计划文档同步 |
12. 近期功能(按原理,不按 commit)
| 功能 | 实现思路 | 边界 |
|---|---|---|
| 1030 合入 main | 目录迁到 agent/controllers;只留 copy_ontology;uv/pyproject | 混合 |
| 编排面 none | 关 MCP;隐藏 ask_clarification | Harness |
| 提示词本地 / Langfuse | LANGFUSE_PROMPT_LOCAL 等环境开关;一 agent 一文件 | Harness |
| 记忆前缀缓存 / 覆盖 memory | revision 快照;HTTP 写画像给 App | Harness |
| 兴趣与称呼接口 | 给 H5 写 harness 记忆 | HTTP |
| 引用 / 游标分页 / 自动标题 | 协议与查询层;标题并行主模型 | Harness |
| 思考增量 + 本地工具卡 | 扩展事件类型 | 混合 |
| 工具调用日志 | 最外层 ToolCallLog | Harness |
| 部署门禁 / slim 镜像 / JSON 日志 | DB·模型·Tavily 预检;Loki 友好 | 运维 |
13. 建议阅读顺序
AGENTS.md(权威约定)docs/1030/agent-harness-1030-版本规范.md(对照源码纠偏过期表)config.yaml:tools / memory / agents / orchestrationagent/executors.py→services/proactive.py→services/run.pyagent/copy_ontology.py→agent/middlewares/*services/agent.py(与 DeerFlow 粘合最深)controllers/run.py/memory.py/thread.pydocs/agent-ui.md、docs/chat.md、docs/1030/greeting_first.md
14. 设计文档地图(仓库内)
1030 版本规范 · 目录
- 军师 APP 1030 · Agent Harness 版本规范
- 修订记录
- 1. 目标与非目标
- 2. 已收口的结构裁决
- 3. 标识(含 task_type)
- 4. 数据模型
- 5. 运行时(含三池)
- 6. 接口
- 7. 记忆数据流
- 8. proactive 错误码
- 9. 验收标准
- 10. 仍开放
设计骨架里与并发相关的裁决
- 现状曾是单一进程级 FIFO 线程池
- 改造:三池;submit 时按执行器所属池分流
- 对外路由字段:
task_type(源码现为六值) - 池映射键:内部 agent / executor 名;
task_type→agent在创建时完成 - 另见:问候规范、strategist-runtime-v2、.cursor/plans 下今日计划文档
学习时怎么判断「该不该改 DeerFlow」:若问题是 checkpoint / 工具循环 / 沙箱生命周期——优先查 DeerFlow 升级与 services/agent.py 粘合;若问题是 Blocks 形态、编排策略、军师业务、记忆注入内容、并发排队——改 Harness 中间件与 controllers,不要把业务逻辑 fork 进 DeerFlow。