我把 OpenChiip Harness 源码读完——这是一套"单进程装下整个数字员工"的 Agent 运行时(我把故事酿成酒完整版原唱) ypxx.net

从三层意图路由到 DAG 编排,从事件溯源到 NAT 穿透,拆开看它的工程取舍

现在做 Agent 框架的人不少,但大多数是"搭微服务":LLM 一个服务、向量库一个服务、工具网关一个服务,再用一堆消息队列串起来。OpenChiip Harness 走了另一条路——把对话、技能、工具、记忆、多智能体、资源监控全部塞进同一个 Python 进程,再配一个开箱即用的 React 后台。这篇文章我把它的核心代码翻了一遍,聊清楚它到底是怎么做到的。

OpenChiip Harness 自带的 Web/桌面管理后台(截图来自仓库 README)

01

先搞清楚:它到底是个什么

README 里一句话定位很准——可自托管的智能体运行与编排框架。它同时提供三种身份:

● 本地 Agent 服务:一条 openchiip-harness serve 起来,FastAPI + Web UI 就有了。

● 平台节点:加个 --platform wss://...,进程主动向外建 WebSocket,穿透 NAT,不需要公网 IP、不需要端口映射,平台就能把任务派下来。

● 桌面应用:Tauri 2.0(Rust)包一层,Windows/macOS/Linux 都有安装包,还有 PyInstaller 单文件 exe。

一句话技术栈

后端 Python 3.10+ / FastAPI / SQLite / WebSocket;前端 React 18 + TypeScript + Vite + Tailwind + Zustand + TanStack Query;桌面壳 Tauri 2.0 (Rust);LLM 兼容百度文心、OpenAI 以及任意 OpenAI 兼容接口。

02

整体架构:六层盒子,从浏览器一路压到 SQLite

整个项目的分层非常克制。我按 agent_runtime/main.py 里 AgentApp.__init__ 的组装顺序画了一张图:

前端层 · React 18 + Vite + Tailwind

ChatViewConversation Node 注册表16 个管理后台页面WebSocket 实时

接口层 · FastAPI

12 个 REST 路由/ws 实时通道SSE 流式对话Swagger /docs

组装层 · AgentApp

CapabilityRegistryPromptAssemblerPluginManagerSubagentManagerCompactionProvider

智能循环 · Agent Loop

IntentRouterSkillManager(DAG)LLM Function CallingOrchestrator 编排

可靠性三件套

SessionLog 事件溯源ToolPipeline 守卫管道Checkpoint 崩溃恢复

持久层 · SQLite

会话/执行日志Session Events记忆(RAG)Token 计量检查点

这里最值得说的不是"用了什么框架",而是它的组装思路:AgentApp 是一个上帝类,启动时把 DB、LLM 客户端、记忆、工具、DAG 执行器、技能管理、元技能、命令、路由器、诊断器、调度器、A2A、能力注册表、插件、提示词组装器、压缩器、子 Agent 管理器、编排器全部 new 一遍——单进程、单实例、靠 lifespan 管理生命周期。代码里还专门留了一段注释吐槽历史教训:早期版本另建了一份 AgentApp,导致平台侧看到模式反复跳变,所以现在强制全进程只能有一个实例。

03

一条消息的旅程:5 道关卡才轮到 LLM

这是我觉得最能体现工程功力的部分。看 process_message_stream,用户每发一句话,不是直接丢给大模型,而是要闯过下面这 5 关:

1Command 前置拦截

前缀检测 + fast-LLM 解析(约 100 tokens),命中即走控制指令

2快捷路径

时间/日期等纯本地处理,0 Token

3编排器入口

fast 模型评估复杂度;简单任务返回 None,复杂任务进入 DAG 拆解

4三层意图路由

规则 → 缓存 → LLM;命中 Skill 走 DAG,未命中走 Function Calling

5日志审计

Memory.record + execution_log,Token 计量,事件溯源

为什么要设计成这样?因为不是每句话都值得花 Token。「现在几点」这种问题走规则 0 成本搞定;已经回答过的相似问题走缓存;真正复杂的任务才上升到 LLM。这种"确定性优先"的思路,贯穿了整个项目。

04

三层意图路由:能不喊 LLM 就不喊

core/intent_router.py 写得很直白,注释里就一句"确定性优先"。它的层级关系是这样的:

这里有几个细节很值得玩味:

● 规则命中会主动清缓存——避免老缓存把新规则盖住;

● LLM 分类用的是 fast 模型(不是主模型),提示词要求输出严格 JSON,只要 ~50 tokens;

● 缓存只留最近 1000 条(list(keys)[-1000:]),防止无限膨胀;

● 命中 Skill 且置信度超过阈值,走确定性 DAG 执行;没命中才落到 LLM Function Calling 多轮对话。

设计哲学

"规则 → 缓存 → LLM"不是性能优化,而是成本与稳定性的权衡:能确定性回答的就别让模型即兴发挥,模型只在真正开放的问题上出手。

05

五原语能力治理:别再给每种能力写一套注册逻辑

很多 Agent 项目做着做着就长这样:技能一个注册表、工具一个注册表、命令一个注册表、远程 Agent 又一个注册表,API 各写各的,生命周期各管各的。OpenChiip Harness 用一个 CapabilityRegistry 把五样东西拉平:

它们全部实现同一个接口:describe() 给出描述符、invoke() 执行、health_check() 体检。注册表还带 before_register / after_register / before_invoke / after_invoke 四个生命周期钩子——审计、限流、审批全部可以挂在这里。

内置了 7 个元技能:env.check / file.find / file.grep / file.list / file.read / file.write / shell.exec。注意它们都标了 zero_llm=true——纯本地执行,不花一分钱 Token。

06

复杂任务怎么办?拆解 → 执行 → 审计 → 结算 → 检查点

单轮对话能搞定的事,走上面那条路就够了。但用户说"帮我调研三家竞品、对比定价、生成一份报告"这种多步任务怎么办?orchestration/ 目录下就是答案。

1评估复杂度

fast 模型判断是否多步/多工具;IntentRouter 高置信度直接跳过

2任务拆解

LLM 输出子任务清单 + depends_on 依赖图(DAG)

3按依赖调度

get_ready_subtasks():只执行依赖已完成的子任务

4独立审计

每个子任务执行完由 Auditor 验真,不通过重试(最多 2 次)

5检查点

每个子任务结束即把整份计划落 SQLite,崩溃后从最近点继续

6结算汇总

Settlement 汇总各子任务产出,拼装最终回复,清检查点

几个值得抄的工程细节:

● 先花小钱评估复杂度——fast 模型判断要不要拆,简单任务直接返回 None 走普通流程,不为"写个 hello world"付出拆解成本;

● 子任务要求"新鲜上下文"——拆解提示词里明确写了"每个子任务必须可以在新鲜上下文中独立执行",避免长上下文污染;

● 依赖图调度——get_ready_subtasks() 每次只返回依赖已完成的节点,天然支持并行;

● 执行和审计分开——执行器干完活,审计员再验一遍,不过就重试,最多 2 次;

● 每个子任务落一次检查点——整份计划序列化进 SQLite,进程崩了重启能从最近一个完成点继续,不用重头再来。

07

可靠性三件套:事件溯源、守卫管道、Token 熔断

SessionLog:append-only 的事件日志

传统做法把对话存成消息列表,想做"回放""分叉""崩溃恢复"就抓瞎。OpenChiip 选择事件溯源:每个 Turn/Step/工具调用都作为一条事件追加进 SQLite,LLM 的对话历史是从事件派生出来的。这意味着:同一份日志既能回放全过程,又能在任意节点 fork 出一条新会话。代码里把这条写成了 ADR-001(架构决策记录)。

ToolPipeline:工具调用不是直接 dispatch

工具执行被包成一条管道:pre-hook(可拒绝)→ 执行 → post-hook(处理结果)。自带默认 30 秒超时、最多 5 个并行的信号量、输出裁到 4000 字符、调用日志。审批流、沙箱检查、凭据清洗都通过挂 pre-hook 实现——单个钩子崩了也不会炸掉主流程(异常被隔离)。

TokenMeter:月度预算一到就熔断

TokenMeter 记录每次调用消耗,配置 monthly_cap 和 warning_threshold,超了直接抛 BudgetExceededError,对话入口会接住并提示"本月 Token 预算已用尽"。对一个要长期跑在服务器上的数字员工来说,这是保命设计。

此外还有 CompactionProvider:历史超过 75% 阈值就自动压缩,把长对话压成摘要再喂给模型,不然上下文窗口迟早爆。

08

不止自己干:多 Agent 与外部 CLI

SubagentManager 注册了四种子 Agent Provider:

也就是说,主 Agent 自己不会写代码时,可以把活派给本机装着的 Codex / Claude Code,收结果回来。服务重启后还会从 tools.db 的安装记录里自动把这些外部 CLI Provider 恢复注册。

跨主机的协作走 A2A:每个 Agent 有一张 AgentCard(名片),通过 RegistryClient 发现对端,A2ACaller 发起调用。平台模式下,这些节点之间还能互相指派任务。

09

双模式运行:一条命令切换"独立"和"打工"

README 里强调了三种工作模式,本质上是同一个进程的不同开关:

● 本地模式:只起 FastAPI + Web UI,不连平台;

● 平台协同模式:本地服务照跑,同时主动出站连平台,双向同步;

● 接单模式:run_mode=order,拒绝新对话请求,只接平台派下来的项目任务——相当于这个数字员工"上班了,别闲聊"。

NAT 穿透的巧思

大多数 Agent 平台要节点暴露公网 IP 或配端口映射,OpenChiip 让节点主动向外建 WebSocket,平台只需要有一个固定域名。家里的 NAS、公司内网的工位机、云上的小主机,都能零配置接进来。

10

工程细节里的诚意

翻代码时几个让我停下来多看两眼的地方:

● MCP 工具探测是真跑命令的——装个工具不是看 PATH 里有没有就完事,而是真执行 --version,还识别 Windows Store 那种假 exe;

● 提示词是组装出来的——PromptAssembler 用"0/10/20/25"的优先级注册段落,身份、工具描述、技能描述、元技能描述各自独立,插件可以再塞段落;

● 系统提示词本地缓存优先——后端挂了也能离线跑,缓存文件读不到才用兜底提示词;

● API 设计得很整齐——12 个路由模块、35 个端点,/api/v1/... 前缀统一,FastAPI 自动生成 Swagger;

● License 有点特别——AIGCGPL-1.0(人工智能生成代码通用公共许可证),因为这个仓库的代码本身就是 AI 生成的,作者在许可证里把这件事写明白了。

11

收尾:这套设计适合谁

如果你只是想周末跑个聊天 Demo,LangChain + Gradio 够了。但如果你想要一个能长期跑在自己工位上、能接平台派活、崩了能恢复、花超了能熔断、还能把 Codex/Claude Code 当下属使唤的数字员工底座,OpenChiip Harness 这套"单进程 + 确定性优先 + 事件溯源"的设计,是值得认真读一遍源码的。

它的核心主张其实就一句话:把不确定性留给 LLM,把确定性全部工程化。规则、缓存、DAG、检查点、钩子、预算——这些都是确定性的部分;只在真正开放的对话和拆解上,才让模型出手。