第一部分:Agent Harness 的基本要素
1.1 什么是 Harness
模型只会产出文本。把文本变成“读文件、改代码、跑命令、再看结果”的闭环,需要一层外壳:提供工具、管理上下文、提供执行环境、驱动循环。Claude Code 的文档直接把自己定义为“agentic harness”,Claude 是里面的模型;Codex 的架构同样把模型调用、工具执行、沙箱和审批拆成独立层。Harness 的价值在于模型之外的部分:同一个模型换一个 harness,能力边界和安全边界都会变。
1.2 核心循环:Agent Loop
两家的循环结构一致:一个 session 是一段绑定工作目录的对话;用户一次输入开启一个 turn;turn 内部反复“模型请求 → 工具调用 → 结果回填”直到模型不再调工具。Claude Code 把这叫 agentic loop,强调用户可随时打断或排队追加输入;Codex 在 core 里用 thread / turn / item 三级建模,turn 可挂起、可取消,item 是 turn 内的最小记录单元。循环本身很薄,复杂度都在它挂接的扩展点上。
1.3 工具系统:Tool
工具让模型能行动。内置工具大体分五类:文件读写、搜索、命令执行、网络、编排(子代理、提问、计划)。工具 schema 随每次请求进入提示词,因此工具数量直接消耗上下文;Claude Code 用 MCP tool search 把外部工具 schema 延迟到用到时再加载。Codex 把 shell 执行统一成 unified exec,支持长驻进程和分段读取输出。MCP 是两家共同采纳的外部工具协议,stdio 和 HTTP 两种接法,可按服务器粒度开关单个工具。
1.4 指令与记忆的分层:Instruction
项目约定通过 Markdown 文件注入:Claude Code 读 CLAUDE.md,Codex 读 AGENTS.md。发现规则相同:全局用户级一份,仓库根到当前目录逐层拼接,广的在前、具体的在后,不互相覆盖。Codex 对拼接总量设字节上限。Claude Code 另有 auto memory(模型自己写的笔记,每次加载索引前 200 行)和 rules(按路径触发的碎片指令)。Skills 是按需加载的指令包:启动时只把名字和描述放进提示词,被调用时才展开全文。这一层的设计目标是把“总在上下文里”的东西压到最小。
1.5 权限与沙箱:两层分离(Sandbox & Permission)
两家都把“要不要问人”和“物理上能不能做”分成两层。
审批层: Claude Code 有六种 permission mode(default、acceptEdits、plan、auto、dontAsk、bypassPermissions)加 allow/ask/deny 规则,deny 优先、首个命中生效;Codex 有 approval_policy(untrusted、on-request、never、granular)加 execpolicy 规则引擎,规则给命令前缀打 allow / prompt / forbidden,取最严格者。
沙箱层: read-only、workspace-write、danger-full-access 三档在两家几乎同名;macOS 用 Seatbelt,Linux 用 bubblewrap 加 seccomp,写根目录里的 .git 等路径仍只读,网络默认关闭。沙箱拦下的命令会升级为一次审批请求。
两家都在引入模型审阅者替代人工点头:Claude Code 的 auto 模式用分类器,Codex 的 auto_review 用 guardian 策略,失败一律 fail closed。
1.6 子代理:SubAgent
子代理是一个独立上下文窗口里的模型实例,带自己的系统提示、工具集和权限,完成后只把摘要交回主对话。Claude Code 用 Markdown frontmatter 定义(tools、model、permissionMode、hooks、skills、memory、isolation),Codex 用 TOML(developer_instructions、model、sandbox_mode、mcp_servers);两家都内置 explorer / worker 类角色。关键区分是 fresh 与 fork:fresh 子代理不继承对话历史,fork 继承全部。并发数和嵌套深度都有上限,子代理的审批请求回流到主会话。设计目的是隔离噪音上下文和限制工具面,不是并行本身。
第二部分:DeepSeek Harness 不同的地方
2.1 Everything is a plugin
Claude Code 和 Codex 都是“固定内核加扩展点”:hooks、plugins、MCP 挂在一个不可替换的核心外面。dsh 没有特权核心,agent loop、工具注册表、会话日志、模型适配器本身都是插件,由 profile → bundle → patch 三层配置组装成一棵树,按行替换、热重载。这里的“插件”是运行时组合单元,不是 skills 加 hooks 的分发包。扩展 dsh 的方式是在旁边再挂一个插件,而不是改核心;每个注册都是可撤销的 effect,插件卸载时随之回退。这条立场能成立,依赖的是下面的 Cordis。
2.2 Cordis
Cordis 是一个 TypeScript 插件元框架:只管插件的装载、卸载和依赖关系,业务能力全部由插件通过服务和事件提供。一句话模型是“可热插拔的依赖容器 + 生命周期状态机 + 类型化事件总线”,解决的核心问题是当一个能力出现、消失或被替换时,所有依赖它的代码怎样自动、干净地跟着起停。
2.3 Cordis 的发源历史
作者是 Shigma(石逸凡)。Cordis 最早是 2019 年开发聊天机器人框架 Koishi 时沉淀出来的插件内核;Koishi 于 2020 年 1 月发布首个正式版,Cordis 于 2022 年被抽出独立命名、以 MIT 开源,Koishi 随后重构为建立在 Cordis 之上。名字取自拉丁语 cor(心)的属格,意为“心脏的”。Koishi 需要在一个长驻进程里反复装卸几十个第三方插件而不重启,这种“插件必须能干净地来去”的压力,是 Cordis 一切设计的源头。
2.4 Cordis 的论文核心
2026 年的论文《A Programming Paradigm for Spatiotemporal Composability》(arXiv 2608.25512,Shi、Zhang、Cui)给 Cordis 补了形式化基础,dsh 的 README 直接引用它。论文把动态组合拆成两个正交维度:时间可组合性,一个组件被移除时其副作用能完全回退;空间可组合性,组件能声明依赖并由运行时反应式地管理激活与停用。前者靠“可逆效应”实现:每次对上下文的变换都带一个由运行时持有的逆操作。后者靠“反应式余效应”实现:每次上下文变化都对照组件的依赖规格判定是否激活。Cordis 是参考实现,分两层:核心库负责效应追踪和余效应解析,声明式 loader 负责配置协调和热更新。
GitHub · cordiverse/cordis:Meta-Framework of Spatiotemporal Composability
2.5 Cordis 的核心概念
Plugin。 函数、类或带 apply 的对象,附带可选元数据:inject 声明需要的服务,provide 声明提供的服务,Config 给出校验 schema。插件体只负责“装上”,不写卸载逻辑。注册表按函数对象身份去重,同一插件装多次只有一条 Runtime 记录。
Context。 插件拿到的 ctx 不是全局对象,而是从父上下文派生、带着身份的子对象,同一个 ctx.on() 在不同插件里记到不同的账上。它是 Proxy:读 ctx.llm 走服务解析,未声明 inject 就报错。三个派生方法都不改父上下文:extend 给分支贴数据,isolate 给某个服务名开平行作用域,intercept 给某个服务叠一层配置。
Service。 挂在 ctx.<名字> 上的具名能力,通过 ctx.provide 或继承 Service 基类注册。注册本身是 effect,拥有它的插件卸载时自动注销。存储按“名字到作用域标签”索引而非按名字,这是 isolate 能让同名服务并存的原因。dsh 在此之上加了 Service Definition、Provider、Consumer 三角色纪律。
Fiber 与 Runtime。 ctx.plugin(P) 返回一个 Fiber。Plugin 是定义,Runtime 是注册表里每个插件一条的簿记,Fiber 才是有状态、有资源、能被卸载的实体:持有自己的 ctx、校验后的配置、依赖快照和 disposer 清单,状态在 PENDING、LOADING、ACTIVE、FAILED、UNLOADING、DISPOSED 之间迁移。子 fiber 的创建注册为父 fiber 的 effect,fiber 构成一棵树,父卸载则子必卸载。
Effect。 ctx.effect(fn) 立刻执行 fn,收下它返回的 disposer,fiber 卸载时逆序执行;手动调用与自动卸载谁先到谁执行,只执行一次。ctx.on、ctx.provide、ctx.plugin 全部建立在 effect 之上,因此“卸载后不留垃圾”是结构性保证。这对应论文的可逆效应。已卸载的 fiber 再注册 effect 直接抛错。
Events。 同一张监听表,五种分发模式:emit 广播不等待,parallel 并发等全部,serial 顺序等到第一个非空返回,bail 是其同步版,waterfall 是环绕中间件,监听器拿到 next,不调用即短路。Events 让插件在不知道对方存在、也不互相依赖的前提下观察和拦截彼此的行为,并且框架自己的生命周期也走同一套机制,所以任何环节都能被普通插件扩展或替换。
inject 与 epoch。 插件不排启动顺序,只声明依赖,拓扑由框架动态算。每个 fiber 把依赖状态编码成字符串 epoch,内容是各依赖提供方 fiber 的 uid 拼接,任一缺失即 INACTIVE。epoch 变化就卸载或重载;把提供方身份而不只是存在性纳入版本号,因此换掉一个 provider 时所有消费者自动重跑。这对应论文的反应式余效应,也是热重载和按 session 重组 agent 的机关。
Loader 与 Entry。 Loader 把 cordis.yml 的每一行变成一个 Entry:稳定 id、包名 name、config、inject、disabled。挂载时按名字 import 出插件再调 ctx.plugin 生成 fiber;配置变化、禁用、HMR 都是换 fiber 而 Entry 不变。
第三部分:DeepSeek Harness 执行
3.1 一次 loop 流程
用户输入 "run the tests"
│
├─ 入口:headless-runner / Web gateway RPC
│ agent.followup(userMessage)
│ └ send(msg, 'next-turn', wakeup=true)
│ inbox.splice → emit agent/inbox/inserted
│ wakeDriver(): idle → running,emit agent/status running,kick()
│
├─ kick(): while (await turn()) {}
│
└─ turn() ───────────────────────────────────────────────────────────── 一个 turn
session.append turn/start
┌─ loop(每圈一个 step)
│ preStep(target)
│ inbox.claim(target) → emit agent/inbox/claimed
│ ctx.systemPrompt.assemble → waterfall system-prompt/assemble ← 各插件注册的 section
│ runtimeContext.project → 上下文段
│ waterfall agent/pre-step {messages, turn, step, signal} ← compaction、plan-mode、hooks…
│ → reject:turn 结束,reason blocked
│ → enter(messages)
│ session.append step/start;每条 message → user/message
│ step(assembly)
│ buildRequest
│ seedConfig(provider/model/effort/maxTokens)
│ waterfall agent/request → proposedConfig ← agent-presets 改模型
│ ctx.llm.prepareCall(config) → 绑定 adapter,解析精确模型默认值
│ canonicalHeader(system, tools, config) → session.append request/header(首次或变化时)
│ stream = ctx.llm.stream(request)
│ waterfall llm/stream → adapterStream → adapter.stream(HTTP) ← checkpoint、title、validate
│ for await chunk:session.append assistant/chunk;BlockAssembler.push
│ finish
│ error/aborted → waterfall agent/request-error ← llm-retry 重试 / compaction 压缩
│ retry → 回到 buildRequest;否则 throw LlmError
│ ok → createAssistantMessage(blocks) → session.append assistant/message {usage, sourceEventSeqs}
│ max-tokens → 返回 {max-tokens}
│ 无 tool-call block → 返回 {completed}
│ 有 tool-call block → executeToolCalls(...)
│ session.append step/end
│ 自然停止且 next-step 收件箱空 → serial agent/turn-stopping ← hooks 的 Stop 检查点
│ 否则 target = 'next-step',回到 loop 顶部(工具结果已在日志里,下一步模型看得到)
└─
session.append turn/end {reason}
inbox 空 → turn() 返回 false → kick 退出 → setPhase(idle) → emit agent/status idle
→ agent.whenIdle() resolve;headless 打印,Web 通过 session/event 已经推完
第四部分:总结
4.1 优势
- Cordis 是个非常好的创新点,可插拔的插件机制能够确保整个系统是可组合的、可逆的。
- 通过 Event 机制对所有的系统组件进行解耦,Provider 和 Consumer 不需要互相了解实现。
- Plugin 机制扩展性极强,一种 Service 定义可以实现 N 种 Impl。
4.2 劣势
- 插件之间大量的自定义事件也给开发带来了很多心智负担。
- 但上层接口对具体实现约束弱,若 Effect 的 disposal 实现不够完备,时间可组合性就没法保证。
- 大量插件之间的依赖关系才是构成 Harness 运行的关键,在这点上其实没有太多创新。
- Cordis 核心目前还不够稳定,有很多 breaking changes 会导致插件失效。