Stephen 技术博客

AI和数据平台的工程实践

← 返回文章列表
META-HARNESS · 核心设计

Meta-Harness 核心 7 设计

把 N 个 agent 框架(Claude Code / Codex / Pi / OpenAI Agents)压成"只需实现 run_turn、吐标准事件"的薄适配器

图 · Meta-Harness 内部:Runner → ExecutorAdapter → Executor 事件流,以及子进程隔离、native/sdk 双形态与横切统一层

下面 7 条是 Omnigent 区别于普通 agent 框架的核心设计决策。每条都给出具体机制代码位置

1统一的 Executor 契约

整个 meta-harness 的基石 —— 一个抽象接口,归一化所有异构后端。

omnigent/inner/executor.py:496
class Executor:
    async def run_turn(
        messages: list[Message],   # 异构 JSON dict,带 "role"
        tools: list[ToolSpec],     # JSON-Schema 工具定义
        system_prompt: str,
        config: ExecutorConfig,
    ) -> AsyncIterator[ExecutorEvent]

无论底层是 Claude SDK、codex CLI 还是 OpenAI Agents:

  • 输入统一归一化成 (messages, tools, system_prompt, config)
  • 输出统一归一化成一条 ExecutorEvent 事件流
为什么关键:上层(Session / Runner)只认这条事件流,完全不知道底下是哪家 SDK。这就是 "swap or combine harnesses" 成立的根本。

2归一化的事件词汇表

后端再异构,都得把原生对象翻译成这套固定事件 —— 所有 harness 必须"说同一种话"。

omnigent/inner/executor.py:96-239
事件含义
TextChunk(text)流式文本增量
ReasoningChunk(delta, event_type)思考过程(Claude reasoning / o1)
ToolCallRequest(name, args, metadata={call_id})模型要调工具
ToolCallComplete(name, status, result, call_id)工具执行完(由 Session 发出)
TurnComplete(response, usage, continue_turn)回合结束 + token 用量
ExecutorError(message, retryable)出错(区分可重试 / 永久)
不变量:每次 run_turn 必须以恰好一个 TurnCompleteExecutorError 结尾;call_id 在 request / complete 之间镜像,用来配对。Claude SDK 把 AssistantMessage/ResultMessage 翻成这些,codex 把它的协议翻成这些 —— 翻译都发生在各自 executor 内部,Session 层零感知。

3能力声明(协商差异,不抹平)

后端能力天差地别,用一组布尔标志让上层据此决定控制流。

omnigent/inner/executor.py:519
supports_streaming()               # 是否流式
handles_tools_internally()         # 自己跑工具循环?(Claude SDK=True)
supports_live_message_queue()      # 能否回合中途插话
supports_tool_boundary_interrupt() # 能否在工具边界打断
max_context_tokens()               # 上下文窗口
为什么关键:既统一接口,又不强行抹平能力差异。例如 native harness 声明 supports_live_message_queue=True 支持中途 steering,不支持的后端就降级处理。

4子进程隔离 — 每对话一个 harness 进程

工程上最硬核的一条。契约:一个对话 = 一个 harness 子进程,独立 Unix socket,懒启动。

omnigent/runtime/harnesses/process_manager.py · _runner.py
父进程 ──spawn──> python -m omnigent.runtime.harnesses._runner
                   --harness codex --socket conv-<id>.sock --parent-pid N
                 子进程内:import 模块 → create_app() → uvicorn 绑 UDS
父进程 ──httpx over UDS──> 子进程

为什么不在进程内跑?

  • 状态隔离:每对话独立 executor / SDK client,互不串
  • 崩溃隔离:一个 harness 崩了只死一个对话
  • 干净回收:进程退出即彻底清理,无内存泄漏
  • native CLI 1:1 对应:每个终端 TUI session 配一个子进程

配套生命周期管理

父死子死:prctl(PDEATHSIG) + 看门狗线程每秒探活 空闲 30min 回收 reaper 启动孤儿清扫:lsof 找占 socket 的 pid → SIGTERM → SIGKILL /model 改模型 → 检测 env 变化 → 重启子进程

5ExecutorAdapter — 把任意 Executor 包成统一 REST 服务

所有 harness 的 create_app() 长一个样,只有 executor_factory 不同。

omnigent/runtime/harnesses/_executor_adapter.py
def create_app() -> FastAPI:
    adapter = ExecutorAdapter(executor_factory=_build_xxx_executor)
    return adapter.build()     # 四种 harness 只有 factory 不同

Adapter 暴露统一 REST 协议(单个 POST /v1/sessions/{id}/events 端点,用 type 区分): message / interrupt / tool_result / approval / policy_verdict。 它把 HTTP 请求翻成 run_turn() 调用,再把 ExecutorEvent 翻成 SSE 事件。

关键桥接 — 装在 executor 上的 3 个稳定回调(所有 harness 共用)

executor._tool_executor       = self._stable_tool_executor       # 工具分发桥
executor._policy_evaluator    = self._stable_policy_evaluator    # 策略往返桥
executor._elicitation_handler = self._stable_elicitation_handler # 审批回调

这三个桥就是横切能力(设计 7)接入每个 harness 的入口。

6native vs sdk 两种 harness 形态

同一个抽象下两种截然不同的接入方式 —— 这是 Omnigent 一个很聪明的设计。

SDK harness
claude-sdk / codex / pi / openai-agents
native harness
claude-native / codex-native
模型怎么跑子进程内启动 SDKCLI/TUI 已在终端跑着,不启动
run_turn 做什么驱动 SDK,流式产事件tmux send-keys 注入消息,立即返回
输出从哪来SDK 流式transcript forwarder 读 Claude 的 JSONL / Codex 的 rollout
用途后台 / 编程式UI 里实时围观甚至接管
native 形态通过 *_native_bridge.py + *_native_app_server.py + hooks 桥接真实终端 —— 这就是为什么你能"看着 Codex 干活并随时接管"。

7横切统一层 — 工具 / 策略 / 多 agent / 成本

抽象不只在接口:这些能力在 server / runner 端统一注入,与底层框架无关,对所有 harness 一致生效。

能力机制位置
统一工具ToolManager 单一注册表:sys_* 内置、MCP、子 agent、用户 Python 函数,都继承 Tool ABC,以 OpenAI schema 喂给任意 harness;调用经 _stable_tool_executor → ctx.dispatch_tool → server 执行tools/manager.py
统一策略PolicyEngine 在 server 端,harness 只是瘦中继。4 个评估点(输入 / 工具调用 / 工具结果 / 输出)。native CLI 用同步 hook(Pre/PostToolUse)拦截,翻成同一套 server 格式runtime/policies/engine.py · native_policy_hook.py
统一多 agentsys_session_send / create 创建子会话(runner task),子会话可用与父会话不同的 harness。Claude 编排器派活给 Codex 子 agent,异步,经 inbox drain 收结果tools/builtins/spawn.py
统一成本/状态token 用量归一化进 PolicyEngine.usage(input/output/cache/cost);AdvisorVerdict 作为 conversation label 持久化;所有 harness 读写同一份 ConversationStore 历史cost_plan.py · stores/

一句话总结

meta-harness 的核心 = 归一化的 Executor.run_turn() → ExecutorEvent 契约(① ② ③) + 每对话子进程隔离(④)+ 统一的 Adapter / REST 包装(⑤) + native / sdk 双形态接入(⑥)+ 工具 / 策略 / 多 agent / 成本横切统一(⑦)。

底层 agent 框架被压缩成"只需实现 run_turn、吐标准事件"的薄适配器,其余一切(工具、策略、编排、成本、跨设备、协作)都由 Omnigent 统一层提供 —— 这就是"把 N 个 agent 框架变成可互换零件"的工程实现。

Meta-Harness 核心 7 设计 · 基于代码仓库 main 分支梳理 · 配图 docs/meta-harness-design.svg · 总览 architecture.html