User:
deepseek harness 出来了,帮我研究下
ChatGPT
我把 DeepSeek 官方刚放出的 DeepSeek Harness(DSH) 系统性看了一遍。代码仓库是 deepseek-ai/deepseek-harness。截至 2026 年 8 月 14 日,官方定位仍是 Developer Preview,但已经可以直接通过 npx @deepseek-ai/dsh web 跑起来,GitHub 仓库已经是完整工程而不是 Demo。官方给它的定义非常明确:
我的核心判断是:
DeepSeek Harness 真正值得研究的不是“又一个 Coding Agent”,而是它开始把 Harness 本身定义成一种独立于模型的 Agent OS / Runtime。
一、先看 DeepSeek Harness 到底是什么
传统 Coding Agent 大概是:
LLM
│
┌───────▼───────┐
│ Agent Loop │
├───────────────┤
│ Bash / File │
│ Search / MCP │
│ Context │
│ Permission │
└───────────────┘
而 DeepSeek Harness 的思路变成:
Model
│
▼
Agent Interface
│
┌───────────▼───────────┐
│ Cordis Kernel │
│ Plugin / Service / │
│ Event Bus │
└───────────┬───────────┘
│
┌──────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
Agent Loop Context Tools
Plugin Plugin Plugin
│ │ │
▼ ▼ ▼
Session Skills Bash / FS
Plugin Plugin Plugin
│ │
▼ ▼
Storage Sandbox SubAgent
Plugin Plugin Plugin
Workflow / Schedule / Goal / UI / LSP /
Web / Compaction / Memory / Permission ...
全部都是 Plugin
官方的原话就是 Everything is a Plugin:模型、工具、技能、会话、沙箱、存储、循环、调度、UI 都由插件构成。Cordis 核心只负责插件加载、卸载、依赖关系和事件机制,不拥有 Agent 的具体能力。(深度求索)
这个设计非常关键。
因为它意味着:
Agent Loop 本身都不是神圣不可修改的。
你甚至可以:
DeepSeek model
+
Claude Code subagent
+
remote E2B sandbox
+
custom memory
+
custom tool policy
+
custom agent loop
+
custom UI
拼成一个新的 Agent。
二、它的核心架构其实只有三个词
我觉得理解 DSH,只需要理解三个东西:
1. Service
表示“能力”。
例如:
ctx.llm
ctx.tools
ctx.sessions
ctx.fs
ctx.subprocess
ctx.shell
ctx.subagents
ctx.goals
ctx.jobs
ctx.skills
最漂亮的一点是,一个能力通常会拆成:
Service Definition
↓
Service Provider
↓
Consumer
例如文件系统:
FS Service
│
├── Local FS
├── E2B FS
└── Remote FS
│
▼
file tools
Shell、Subprocess、Sandbox、Web、LLM、SubAgent 都采用类似设计。(GitHub)
这其实是一种非常标准化的 Capability Seam(能力接缝)。
它解决了 Agent 工程里一个长期问题:
Agent 到底应该依赖“具体工具”,还是依赖“能力接口”?
DSH 选择后者。
三、第二个核心:Event
DSH 不是简单:
LLM → Tool → LLM
它内部实际上是一条事件流水线。
官方给出的 turn flow 大致是:
turn/start
↓
claim input
↓
assemble:
system prompt
context
tools
↓
agent/pre-step
↓
step/start
↓
LLM Request
agent/request
↓
llm/stream
↓
assistant/message
↓
tool/call
↓
tools/pre-execute
│
├── permission
├── guard
├── policy
└── hook
↓
tools/execute
↓
tools/post-execute
↓
tool/result
↓
step/end
↓
next step / turn end
(GitHub)
这意味着大量功能根本不用修改 Agent Loop。
例如:
我要做安全检查
→ 监听 tools/pre-execute
我要加入记忆
→ agent/pre-step 注入
我要记录行为
→ session/event
我要改模型请求
→ agent/request
我要决定是否继续推理
→ agent/turn-stopping
这和现在大量 Agent Framework 的区别很明显:
不是不断往 Agent 类里面加 feature,而是在 Agent 生命周期上挂能力。
四、我认为整个项目最值得抄的,其实是 Session Log
这个设计我非常喜欢。
DeepSeek Harness 有一个:
append-only Session Event Log
而且他们定了一个极强的工程约束:
Model-visible means logged.
即:
凡是模型看见过的东西,都必须能够从 Session Log 重建。
包括:
- system prompt
- user message
- assistant message
- reasoning/chunk
- context injection
- tool call
- tool result
- subagent
- plan
- permission
- session state
Session 恢复、fork、回放、trajectory、telemetry、检索,都从同一个事件流衍生。(深度求索)
可以理解为:
Session Event Log
│
┌───────────────┼─────────────────┐
▼ ▼ ▼
Agent Context Trajectory Replay
│
▼
Resume
│
▼
Fork
│
▼
Retrieval
这已经不太像“聊天记录”。
更像:
Agent 的 Event Sourcing。
五、这点和你的记忆系统非常值得结合
这里我觉得特别有意思。
你现在的记忆体系里有:
L0 原始记忆
L1 摘要/原子记忆
L2 主题记忆
L3 核心记忆
DeepSeek Harness 的 Session Event Log,本质上非常适合对应:
Agent L0 Event Memory
比如:
user/query
context/injected
memory/retrieved
tool/call
tool/result
agent/decision
assistant/response
workflow/step
permission/decision
全部进入 append-only Event Log。
然后再异步生成:
Event Log
│
┌──────┴──────┐
▼ ▼
Session projection Memory pipeline
│
┌─────────┼─────────┐
▼ ▼ ▼
L1 L2 L3
但这里有一个非常重要的区别:
DSH Session ≠ 真正的长期用户记忆。
DSH 官方已经有 session-query,支持 session corpus、lineage、关系、semantic filtering、SQLite FTS,也有 compaction;但这更偏向 Agent trajectory / session retrieval,并不是完整的用户长期画像、事实冲突治理、隐私治理、跨数据源长期记忆系统。(GitHub)
所以我反而认为:
DSH 给你的启示不是“拿 DSH 做记忆系统”,而是让你的 Memory System 成为 Harness 的一级 Capability。
例如:
ctx.memory
│
├── defaultContext()
├── retrieve()
├── remember()
├── forget()
├── consolidate()
├── authorize()
└── provenance()
这样会非常漂亮。
六、DeepSeek Harness 有四种 Agent 模式
官方这次其实不只发布了一个 Coding Agent,而是四种 preset。
Standard
完整 Coding Agent:
File
Shell
Web
Skills
Plan
Goal
SubAgent
Workflow
...
PTC
这是我觉得第二值得研究的东西。
不是:
LLM
→ tool
→ LLM
→ tool
→ LLM
→ tool
而是让模型生成 TypeScript:
const files = await tools.search(...)
const results = await Promise.all(
files.map(x => tools.read(x))
)
...
然后由 Code Mode Runtime 执行。
即:
LLM
│
▼
Generate Program
│
▼
TypeScript Runtime
│
├── tool
├── tool
├── tool
└── tool
│
▼
LLM
官方称 PTC 模式通过 Code Mode SDK 让模型用一段 TypeScript 编排多步工具操作。(深度求索)
这实际上是在减少:
LLM ↔ Harness round trip。
我认为未来 Agent 很可能会同时存在:
Function Calling
+
Code Calling
+
Workflow Calling
三种执行方式。
七、Minimal Mode 更有研究价值
DeepSeek V4-Pro 最新 Code Agent benchmark 使用的就是 DeepSeek Harness 极简模式。(DeepSeek API 文档)
Minimal 模式只有:
persistent bash
+
str_replace_editor
Python SDK 的官方示例进一步确认:
- 只有两个 model-facing tools
- compaction 关闭
- 没有 Skills
- 没有 task tools
- 没有 workspace prompt
- 没有复杂 Harness identity
- JSONL Session Log
- persistent shell
(GitHub)
这背后其实有非常重要的研究思想:
Benchmark 模型能力时,尽量削弱 Harness 的影响。
否则:
Benchmark Score
=
Model能力
+
Prompt engineering
+
Tool engineering
+
Memory
+
Context compression
+
Agent Loop
+
Workflow
你根本不知道提升来自哪里。
DeepSeek 等于在明确区分:
Model Benchmark Harness
vs
Production Harness
这点非常值得学习。
八、第四种 Creative Mode 更有意思
Creative Mode 的作用不是写代码。
而是:
让 Agent 修改 Agent。
官方描述是:
inspect runtime
→ experiment plugins
→ compose plugins
→ create new agent preset
仓库中甚至已经包含:
extensions/
Agent runtime self-modification
也就是 Agent 可以:
查看当前有哪些 plugin
↓
设计一个新 plugin
↓
加载到内存
↓
运行测试
↓
调整
↓
生成新的 Agent preset
(深度求索)
这其实已经开始接近:
Self-Evolving Agent Runtime。
我认为这是 DeepSeek Harness 最有想象力的方向之一。
九、它和 Claude Code / Codex 的核心差别
Claude Code 今天其实已经非常强:
CLAUDE.md
Skills
Hooks
Plugins
MCP
Subagents
Permission
Agent SDK
而 Claude Agent SDK 直接提供 Claude Code 同款 agent loop、tools 和 context management。(Claude Platform Docs)
Codex 也已经有:
CLI
App
Skills
Subagents
MCP
Automations
Sandbox
Approvals
并且强调 OS 级 sandbox + approval。(OpenAI)
但三者设计哲学不一样:
| Claude Code | Codex | DeepSeek Harness | |
|---|---|---|---|
| 目标 | Coding Agent 产品 | Coding Agent 平台/产品 | Agent Runtime |
| Agent Loop | 内建 | 内建 | 可替换 Plugin |
| Tools | Hooks/MCP/SDK | MCP/tools | Plugin Service |
| Session | 产品控制 | 产品控制 | 可替换 Event Store |
| Sandbox | 内建权限体系 | OS Sandbox | Provider seam |
| SubAgent | 原生 | 原生 | Provider abstraction |
| UI | 产品 UI | 产品 UI | Plugin |
| Model | Claude | OpenAI | Provider-neutral |
| Self modification | 较弱 | 较弱 | 明确设计目标 |
| Code orchestration | Agent loop | Agent loop | PTC / Code Mode |
这里最有意思的一件事是:
DSH 甚至能把 Claude Code 和 Codex 当成 SubAgent Provider。
官方仓库已经实现:
parent DSH agent
│
└── ctx.subagents
│
┌───────┴────────┐
▼ ▼
Claude Code Codex
而不是必须由 DeepSeek 模型自己做所有事情。(GitHub)
所以它的竞争层次实际上变成了:
Claude Code
Codex
↑
Agent Products
-----------------
DeepSeek Harness
↑
Agent Runtime / OS
至少从架构意图上,它想站在更下面一层。
十、我认为 DSH 最值得抄的 7 个设计
如果让我给这个项目排优先级,我会重点研究:
① Append-only Agent Event Log
Everything model sees → Event
Agent 全生命周期可重放、可追踪、可 fork。
② Capability Seam
不要写:
Agent → BaiduMapTool
而是:
Agent
↓
Navigation Capability
↓
Provider
├─ 百度
├─ 高德
└─ Google
③ Agent 生命周期事件化
pre-step
request
pre-tool
execute
post-tool
turn-stop
Memory / Privacy / Policy / Personalization 都可以插进去。
④ Agent Scope
不同 Agent 可以拥有不同:
Tool
Context
Model
Permission
Memory
Skill
而不是全局一套工具。
⑤ Harness Profile
实际上就是:
Agent = Plugin Composition
比如你的语音助手未来完全可以:
Driving Agent
Health Agent
Finance Agent
Shopping Agent
不是四套代码,而是四个 preset。
⑥ Code Mode / PTC
把:
LLM → tool → LLM → tool
变成:
LLM → Program → tools × N
对高频工具场景尤其值得研究。
⑦ Model 和 Harness 分开评测
这对你们做 Agent 能力评测尤其重要:
Model Score
Harness Score
Memory Score
End-to-End Score
必须拆开。
否则你永远不知道提升究竟来自模型还是系统。
十一、但目前不要直接拿来做生产底座
这一点要特别注意。
官方自己明确写着:
Developer Preview,未来会有 compatibility-breaking changes。
(GitHub)
而且从仓库可以看到很多能力仍在高速变化,包括:
- self-modification
- compaction
- session format
- plugin API
- UI
- sandbox
- workflow
甚至 Session 格式目前已经专门设计了版本升级和未知 Event 的兼容机制,说明团队自己也预期架构还会快速演进。(GitHub)
另一个值得注意的是安全边界。
“Everything is a Plugin”意味着扩展能力非常强,但同时:
Plugin
=
Code
+
Tools
+
Context
+
Events
+
Potential Authority
所以第三方 Plugin 的 capability isolation、签名、权限、sandbox、secret access 最终会变成 DSH 能否进入企业生产环境的关键问题。
目前官方已经把 telemetry 改成默认关闭、显式 opt-in,说明他们对数据安全边界也在快速补工程能力。(GitHub)
十二、我的结论
如果只用一句话评价:
DeepSeek Harness 最重要的意义,是把“Agent Engineering”的竞争焦点,从 Prompt / Tool Calling 提升到了 Harness Architecture。
以前大家研究:
哪个 Model 更强?
后来研究:
哪个 Agent 更强?
下一阶段很可能研究:
Model
+
Harness
+
Memory
+
Tools
+
Environment
+
Feedback
哪个整体系统更强。
而 DeepSeek 这次非常明确地把:
Model
和:
Harness
拆成了两个可以独立演进的产品层。
对构建语音助手的人来说,我尤其建议深入看三个部分:
core/session + session-query—— 对记忆系统最有价值core/agent + agent-loop + event system—— 对语音助手主 Agent 架构最有价值capability seam + preset + scope—— 对多 Agent / 多场景开放体系最有价值
其次再看:
Code Mode / PTC
SubAgent
Workflow
Self Modification
Compaction
Sandbox
DeepSeek Harness 官方目前甚至已经有 4.49 万 GitHub Stars,而且仍处于 Developer Preview,社区增长非常快。(GitHub)
如果按你之前研究 MemOS 的方式来继续,我下一步更建议直接做一件事:把 DeepSeek Harness 源码中的 session / context / compaction / session-query / subagent / plugin 六个模块逐层拆开,画出完整源码级架构图,同时专门分析它和你现在 L0-L3 记忆架构怎么融合。 这部分会比停留在产品介绍层更有价值。
User:
你说DeepSeek Harness 核心架构其实只有三个词 :Service 、Event 、Session Log 。详细展开讲一下Service、Event 和Session Log
ChatGPT:
对,这三个词如果真正吃透,DeepSeek Harness 的架构基本就通了。
我建议不要只把它们理解成三个技术组件,而是理解成三个正交维度:
Service = 能力面
Event = 协作面
Session Log = 事实面
它们分别回答三个完全不同的问题:
Service:
“系统能做什么?”
Event:
“什么时候,谁可以介入?”
Session Log:
“刚才到底发生了什么?”
DeepSeek Harness 的巧妙之处就在于:能力、控制流、事实历史没有混在一个 Agent 类里。 官方架构也是以 Cordis 的 service、typed event、session event log 来组织整个系统。(GitHub)
一、Service:定义“能力是什么”,但不绑定“能力怎么实现”
先看最核心的一句话:
Service 是 Agent Runtime 中稳定的“能力接口”。
比如 Agent 需要:
调用模型
文件读写
运行 shell
查询 session
调用 subagent
执行工具
传统做法可能直接写:
Agent
├── OpenAIClient
├── LocalFileSystem
├── BashExecutor
├── SQLiteSessionStore
└── ClaudeSubAgent
这其实意味着:
Agent
↓
依赖具体实现
而 DeepSeek Harness 更像:
Agent
↓
Service Interface
↓
Provider
例如:
FS Service
│
┌────────────┼────────────┐
▼ ▼ ▼
LocalFS SandboxFS RemoteFS
Agent 本身只需要:
ctx.fs
它不关心下面到底是谁。
这就是 Capability Seam。
官方架构中已经明确列出许多核心 ctx service,例如:
ctx.sessions
ctx.systemPrompt
ctx.tools
ctx.agents
ctx.agentLoop
ctx.llm
而具体实现是由插件挂进去的。(GitHub)
1. Service 解决的第一个问题:解耦
举个特别直观的例子。
假设你做:
Memory Retrieval
一种写法是:
def agent():
memories = redis.search(...)
那以后你想换:
Redis
→ Milvus
→ 本地端侧索引
→ 云端 Memory Service
Agent 都得改。
如果采用 Service:
Agent
│
▼
ctx.memoryQuery
│
├── Redis Provider
├── Milvus Provider
├── Local Provider
└── Cloud Provider
Agent 根本不用知道。
所以:
Service 是“依赖抽象”。
二、为什么不是直接用一个巨大的 AgentManager?
因为 DeepSeek Harness 的设计哲学实际上是:
Agent 本身尽量薄
而系统能力分散在:
Service A
Service B
Service C
Service D
...
Agent Loop 只是这些能力的消费者。
可以想象成:
Agent Loop
│
┌────────────┼─────────────┐
│ │ │
▼ ▼ ▼
ctx.llm ctx.tools ctx.sessions
│ │ │
▼ ▼ ▼
Model Tool Registry Session
因此 Agent Loop 并不是:
“整个 Agent 系统”
它更像:
“驱动这些 Service 工作的调度器”
DeepSeek 官方甚至明确强调默认 agent-loop 只是一个具体 loop 插件,本身也是可以替换的。(GitHub)
三、Service 最重要的不是“接口”,而是“可替换性”
例如:
ctx.subagents
背后完全可能是:
DeepSeek Harness SubAgent
Claude Code
Codex
Remote Agent
对于上层来说:
spawn(...)
语义不变。
这就是:
WHAT
和
HOW
分离
四、套到你的记忆系统里,Service 应该是什么?
这是最值得借鉴的一点。
你现在容易形成:
Memory System
│
├─ 默认加载
├─ PCS
├─ Memory DB
├─ 权限
├─ L1
├─ L2
└─ L3
如果借 DSH 的思想,我反而建议拆:
ctx.memoryQuery
“查什么记忆”
ctx.memoryStore
“如何存记忆”
ctx.memoryPolicy
“能不能使用”
ctx.memoryConsolidation
“如何形成长期记忆”
ctx.memoryContext
“给 Agent 看哪些记忆”
ctx.provenance
“这条记忆从哪里来”
这样:
Memory
不再是一个巨大的模块。
它是一组能力。
五、所以 Service 可以一句话概括
Service = 系统能力的名词。
比如:
LLM
Tool
Session
Memory
Filesystem
Search
SubAgent
都是“名词”。
它告诉系统:
我这里有这么一种能力。
但它不回答:
什么时候调用?
这个问题交给第二个东西:
Event。
六、Event:定义“什么时候允许谁介入”
如果 Service 是名词,
那么 Event 更像:
动词 + 时间点。
例如:
agent/pre-step
agent/request
tools/pre-execute
tools/execute
tools/post-execute
agent/turn-stopping
它们本质上表示:
“系统现在执行到这里了。”
于是其他插件可以说:
我关心这个时刻。
七、没有 Event 的 Agent 通常长这样
传统 Agent:
def run():
context = build_context()
context = memory.inject(context)
if need_compact(context):
context = compact(context)
response = llm(context)
for tool_call in response.tools:
if permission.check(tool_call):
result = tool.execute(tool_call)
save_session()
问题是什么?
所有逻辑全耦合在:
run()
里面。
以后新增:
审计
隐私
限流
Memory
Telemetry
Safety
Rerank
就会变成:
def run():
...
memory()
policy()
privacy()
telemetry()
audit()
safety()
...
最后出现一个:
God Agent Loop。
八、Event 把这个 Agent Loop“切开”
DeepSeek Harness 会把执行过程切成很多生命周期节点:
turn/start
│
▼
claim input
│
▼
agent/pre-step
│
▼
step/start
│
▼
assemble context
│
▼
agent/request
│
▼
LLM
│
▼
tool/call
│
▼
tools/pre-execute
│
▼
tools/execute
│
▼
tools/post-execute
│
▼
step/end
│
▼
turn/end
官方现在就是这么描述 turn/step 生命周期的。一个 step 基本对应一次模型请求及其工具调用,一个 turn 可以包含多个 step。(GitHub)
于是插件不用修改 Agent Loop。
它只需要监听:
某个 Event
九、例如 Memory 插件
你完全可以写:
agent/pre-step
│
▼
Memory Plugin
│
├─ 获取 L3 Hot Memory
├─ 判断是否触发隐式 recall
└─ 注入 Context
Agent Loop 不需要知道:
Memory 是什么。
十、Permission 插件
可以挂:
tools/pre-execute
例如:
Agent:
“删除用户文件”
│
▼
tools/pre-execute
│
▼
Permission Plugin
│
├── allow
└── deny
官方 extension cookbook 也把 permission gate 放在 tools/pre-execute 这一类拦截点上。(GitHub)
十一、Telemetry 甚至更简单
它可能只需要:
session/event
然后:
observe
即可。
不干预执行。
十二、这里有一个特别关键的概念:Event 不只是“通知”
DeepSeek Harness 里的 Event 大致有两种性质。
第一种:通知型
例如:
“发生了这件事”
监听者只能观察。
类似:
assistant/chunk
agent/status
session/event
第二种:拦截/流水线型
例如:
agent/pre-step
agent/request
tools/pre-execute
这些是 waterfall。
官方定义是:
listener A
↓ next()
listener B
↓ next()
listener C
↓
default behavior
某个 listener 可以:
修改输入
拒绝执行
短路
或者继续 next()
官方明确把 agent/pre-step、agent/request、llm/stream 以及三个 tools 生命周期事件定义成 waterfall 类型。(GitHub)
这个机制其实就是:
可插拔 Middleware。
十三、Event 最重要的价值:控制反转
正常写法:
Agent
↓
主动调用
Memory
↓
主动调用
Permission
↓
主动调用
Telemetry
依赖方向:
Agent → Everything
Event 化以后:
Agent Lifecycle
│
emits events
│
┌───────────────┼──────────────┐
▼ ▼ ▼
Memory Policy Telemetry
也就是:
Agent 不知道插件是谁,插件自己决定什么时候介入。
这是 Inversion of Control。
十四、这个设计为什么特别适合 Agent?
因为 Agent 的逻辑会飞速膨胀。
今天只有:
LLM
Tool
明天就是:
LLM
Tools
Memory
RAG
Safety
Permission
SubAgent
Workflow
Compaction
Personalization
Audit
Telemetry
Sandbox
Human-in-the-loop
如果每加一个能力都修改 Agent Loop:
Agent Loop
一定会越来越不可维护。
Event 相当于预先定义了:
Agent 生命周期上的插槽。
十五、所以 Event 一句话概括
Event = 系统能力的“插入时机”。
Service 回答:
我能干什么?
Event 回答:
我什么时候干?
但还有第三个问题:
刚才到底干了什么?
这就是 Session Log。
十六、Session Log:不是聊天历史,而是 Agent 的“事实账本”
这是 DeepSeek Harness 里我认为最重要的设计。
官方定义得非常明确:
Session 是 Agent 整个交互历史的 append-only source of truth,LLM message history 是从日志派生出来的,而不是单独存储。(GitHub)
注意这个词:
Source of Truth
它不是:
为了 UI 展示的日志
也不是:
debug log
而是:
整个 Agent 状态的事实来源。
十七、Session Log 和普通 Chat History 有本质差别
普通 Chat History:
User: xxx
Assistant: xxx
User: xxx
Assistant: xxx
只记录:
说了什么。
Session Log 记录:
turn/start
user/message
step/start
assistant/chunk
assistant/chunk
assistant/message
tool/call
tool/result
step/end
step/start
assistant/message
turn/end
所以它描述的是:
Agent 的完整经历。
而不仅仅是对话。
十八、可以把 Session Log 理解成银行账本
这个比喻非常贴切。
银行账户通常不会只保存:
当前余额 = 1000
而是保存:
+100
-50
+500
-200
...
然后:
Transaction Log
↓
Projection
↓
Current Balance
DSH 也是:
Session Event Log
↓
Projection
↓
Current Model Context
这实际上就是软件架构里的:
Event Sourcing。
十九、为什么一定要 append-only?
因为如果直接修改历史:
原对话
A
B
C
D
Compaction 后:
Summary
D
那你已经不知道:
A/B/C 原来是什么。
但 DSH 不这么干。
而是:
Raw Log
1 A
2 B
3 C
4 D
5 Summary
然后让:
Summary
声明:
我替代 1~3。
于是模型当前看到:
Summary
D
但系统仍然知道:
A
B
C
D
Summary
二十、这就是 Raw Log 和 Surface 的区别
非常值得你记住:
Session Log
│
immutable truth
│
▼
Surface
│
effective view
│
▼
deriveMessages()
│
▼
LLM
官方明确说 Session 上面维护了一层有序的 Surface projection,LLM message history 就是从这里高效派生;raw log 则保留完整历史。(GitHub)
也就是说:
事实
≠
当前视图
这点极其重要。
二十一、为什么 Agent 特别需要这个?
因为 Agent 经常:
总结
压缩
工具调用
失败重试
Fork
Resume
SubAgent
恢复任务
如果只保存:
当前 Context
很多事情没法解释。
例如用户问:
为什么你刚才删除了这个文件?
如果只有当前 Context,可能已经没有证据。
Session Log 则可以查:
user/message
↓
assistant/message
↓
tool/call
↓
permission
↓
tool/result
整个链条。
二十二、这就是官方强调的原则:Model-visible means logged
这是 DSH Session 设计里最关键的一条规则。
意思不是:
系统里所有东西都要写日志
而是:
凡是模型实际看见并参与决策的信息,都必须能够从 Session Log 重新构造出来。
官方 architecture 文档明确要求,任何进入模型请求的可见输入都必须可由日志重建,并有 runtime invariant 检查。(GitHub)
例如:
Memory Plugin
查到:
用户喜欢无糖咖啡
如果只是:
临时 append 到 prompt
然后没记录下来,就会出现一个问题:
未来 Replay:
为什么模型推荐了无糖?
不知道。
所以正确做法应该是:
Memory DB
↓
Recall
↓
Memory Snapshot
↓
写入 Session
↓
模型看到
而不是:
Memory DB
↓
直接偷偷塞给 Model
二十三、这里还有一个特别容易搞混的地方
DeepSeek Harness 里其实有两种 Event。
A. Live Event
例如:
agent/pre-step
tools/pre-execute
agent/request
这些属于 Cordis Events。
它们的目标是:
控制当前运行过程。
它们不一定需要永久保存。
B. Session Event
例如:
turn/start
user/message
assistant/message
tool/call
tool/result
turn/end
这些是:
SessionEventMap
目标是:
记录已经发生的事实。
Session Event 本身不是一堆独立的 Cordis event 类型;它们作为持久化记录追加到 Session Log,然后统一通过 session/event 广播给观察者。官方源码说明也专门区分了这两种事件体系。(GitHub)
这个区别非常重要。
可以简单记成:
Live Event
=
现在怎么办?
Session Event
=
刚才发生了什么?
二十四、所以其实是三个“平面”
现在你就可以得到一个非常清楚的架构:
┌──────────────────────────────────────┐
│ Capability Plane │
│ │
│ SERVICE │
│ │
│ LLM / Tool / Memory / FS / Search │
│ Session / SubAgent / Sandbox │
│ │
│ “我能做什么?” │
└──────────────────────────────────────┘
▲
│
│ use
│
┌──────────────────────────────────────┐
│ Control Plane │
│ │
│ EVENT │
│ │
│ pre-step / request / pre-execute ... │
│ │
│ “我什么时候做?” │
└──────────────────────────────────────┘
│
│ produces facts
▼
┌──────────────────────────────────────┐
│ Evidence Plane │
│ │
│ SESSION LOG │
│ │
│ turn/user/assistant/tool/result/... │
│ │
│ “到底发生了什么?” │
└──────────────────────────────────────┘
我觉得这是理解 DSH 最好的心智模型。
二十五、用一个“订咖啡”的完整例子串起来
用户说:
帮我点杯咖啡。
第一步:Service
系统已有:
ctx.memoryQuery
ctx.tools
ctx.llm
ctx.sessions
这些只是声明:
我有这些能力。
第二步:Event
Agent 开始运行:
turn/start
然后:
agent/pre-step
Memory Plugin 监听到了:
agent/pre-step
于是调用:
ctx.memoryQuery
得到:
用户喜欢冰美式
无糖
常用地址 = 公司
然后把这些信息注入。
注意:
Event
触发了:
Service
即:
Event → Service
第三步:Session Log
这些模型可见内容进入 Session:
Seq 101
turn/start
Seq 102
user/message
“帮我点杯咖啡”
Seq 103
user/message
[memory context]
用户偏好:冰美式,无糖
常用地址:公司
第四步:LLM
模型现在看到:
User
+
Memory
+
System Prompt
+
Tools
于是决定:
OrderCoffee(
type="iced americano",
sugar=0,
address="company"
)
第五步:Tool Event
进入:
tools/pre-execute
Permission Plugin 可以:
检查支付授权
然后:
tools/execute
调用:
ctx.tools
第六步:继续写 Session Log
记录:
Seq 104
assistant/message
Seq 105
tool/call
OrderCoffee(...)
Seq 106
tool/result
success
Seq 107
assistant/message
“已经帮你下单。”
Seq 108
turn/end
于是最后形成:
Service
↓
提供能力
Event
↓
编排能力
Session Log
↓
记录结果
完整闭环:
┌─────────────┐
│ Service │
│ 能力池 │
└──────▲──────┘
│ call
│
┌──────┴──────┐
│ Event │
│ 生命周期编排 │
└──────┬──────┘
│ happens
▼
┌─────────────┐
│ Session Log │
│ 事实账本 │
└─────────────┘
二十六、这三个东西为什么一定要分开?
假设不分开。
你可能写:
Memory Plugin
=
负责查 Memory
+
决定何时查
+
修改 Prompt
+
记录日志
+
持久化
+
权限
这个模块慢慢会变成:
Mini Agent Framework
非常难维护。
DSH 的方式是:
Service
MemoryQuery
只负责:
怎么查询。
Event
agent/pre-step
负责:
什么时候查询。
Session Log
负责:
最终查到了什么、给模型看了什么。
三个职责非常干净。
二十八、还有一个更深层的理解
如果继续抽象,其实三者对应的是计算机系统里三种经典结构:
| DSH | 传统系统概念 | 核心问题 |
|---|---|---|
| Service | API / Device / Capability | 能做什么 |
| Event | Interrupt / Middleware / Hook | 何时介入 |
| Session Log | WAL / Event Sourcing / Journal | 发生了什么 |
所以 DeepSeek Harness 有一点像一个微型操作系统:
Operating System
Device / System Call
↓
Service
Interrupt / Scheduler
↓
Event
Journal / WAL
↓
Session Log
这也是为什么我前面会说它更像:
Agent Runtime / Agent OS
而不是传统意义上的 Agent Framework。
二十九、最后给你一个最简记忆口诀
以后看 DeepSeek Harness 源码,你可以一直问这三个问题:
看到一个 Service
问:
它提供的是什么能力?谁在 Provider?谁在 Consumer?
看到一个 Event
问:
它发生在 Agent 生命周期哪个时刻?它只是观察,还是能拦截/修改?
看到一个 Session Event
问:
为什么这个事实值得永久记录?它是否影响模型看到的历史?能否 Replay?
只要按这三个问题去读源码,很多几十个 package 就不会显得乱了。
最本质的关系就是:
Event
“什么时候行动”
│
▼
Service ──────────→ Capability
“调用什么能力”
│
▼
实际执行结果
│
▼
Session Log
“把事实记下来”
│
▼
Replay / Resume
Audit / Memory
Context / Fork
而且这里还有一个非常漂亮的反馈环:
Session Log
↓
derive context
↓
Event
↓
Service
↓
产生新事实
↓
Session Log
这其实就是 DeepSeek Harness 的“飞轮”。
如果再往下研究,我认为下一层最值得讲的是:**Service + Event + Session Log 到底怎样通过 Cordis 的 Context / Plugin / Effect / Scope 四个机制具体实现出来。**这会从“架构思想”进入真正的源码机制层。
User:
有几个问题:
- Service 本质还是一个接口,我定义接口的时候,就按能力分类,准备不同接口不也能实现同样的效果吗?
- 具体举例展开讲讲Event 最重要的价值:控制反转,对event “Agent 不知道插件是谁,插件自己决定什么时候介入。”这句话不太理解
- Summary声明我替代1-3,真是这么做的吗?举个具体的例子。
- Session 上面维护了一层有序的 Surface projection,LLM message history 就是从这里高效派生。具体是怎么派生的?怎么决定哪些内容放到history里?这一步派生有时延开销吗?
- Event优点想claude code里面的hook的概念,是不是一样?
ChatGPT:
你这 5 个问题非常关键,而且里面有两个地方我想先修正我前面为了方便理解做的简化:
第一,Service 本质上确实就是接口抽象 + 运行时服务注册/依赖注入,它没有“神奇的新理论”。
第二,“Summary 替代 1-3”这个说法概念上对,但源码实现更严谨:真正执行 replace 的不是 compaction/summary 事件,而是紧接着新增的一条 user/message。
下面逐个展开。
1. Service 本质还是接口,我自己按能力定义不同接口,不也能实现吗?
完全可以。
甚至我会说:
如果你的系统已经有良好的 Interface + DI + Provider 抽象,那么 DSH 的 Service 这部分并没有本质创新。
比如你完全可以自己写:
interface MemoryQueryService {
search(query: string): Promise<Memory[]>
}
interface FileSystemService {
read(path: string): Promise<string>
}
interface LLMService {
generate(req: Request): Promise<Response>
}
然后:
Agent
├── MemoryQueryService
├── FileSystemService
└── LLMService
再通过 Spring / Guice / NestJS / 手工 DI 注入:
MemoryQueryService
│
├─ RedisMemoryProvider
├─ LocalMemoryProvider
└─ CloudMemoryProvider
这已经实现了 DSH Service 80% 的核心思想。
DSH 自己也明确把一个 capability seam 定义成三种角色:
Service Definition
+
Service Provider
+
Consumer
也就是接口、实现方和使用方。(GitHub)
那 DSH 为什么还特意搞 Service?
区别主要不在“接口”,而在于它把接口进一步放进了一个Runtime Service Registry。
普通接口是:
编译期抽象
DSH Service 更接近:
编译期抽象
+
运行时注册
+
依赖注入
+
Plugin 生命周期
+
Scope
+
运行时替换
例如普通代码:
class Agent {
constructor(
private fs: FileSystem,
private memory: MemoryService
) {}
}
这里依赖关系在 Agent 构造时基本定了。
DSH 更接近:
Cordis Context
ctx.fs
ctx.llm
ctx.sessions
ctx.tools
ctx.subagents
...
插件启动时把 Provider 挂进 Context:
Plugin A
↓
register ctx.fs
Plugin B
↓
register ctx.llm
Plugin C
↓
consume ctx.fs
而整个运行中的 DSH 本身是一棵可组合、可 patch 的插件树;不同 profile 可以换掉 Provider。(GitHub)
所以真正的区别可以这样看
你自己定义接口
interface FileSystem
│
▼
LocalFS
Agent → FileSystem
已经解决:
代码依赖具体实现的问题。
DSH Service
Service Definition
│
ctx.fs
│
┌──────────────┼──────────────┐
▼ ▼ ▼
LocalFS E2B FS Remote FS
▲ ▲ ▲
└──────────────┼──────────────┘
│
Runtime Config
进一步解决:
运行中的 Agent 到底装哪一种能力的问题。
所以我对 Service 的评价其实是
它的价值不是:
“发明了接口。”
而是:
把接口/Provider/Consumer 提升成 Harness 的统一组装协议。
这有点像:
Java interface
和:
Spring Bean + Dependency Injection Container
的区别。
你完全可以不用 Spring,自己写接口。
但当系统有:
100 个能力
50 个 Provider
20 种 Agent Profile
不同 Scope
不同生命周期
统一 Service Container 就开始有价值。
所以对于你们自己的语音助手,我不会因为 DSH 就重构所有接口。
如果已经有:
MemoryService
CalendarService
ContactService
SearchService
ToolService
完全可以保留。
DSH 真正更值得学的是 Event 和 Session Log。
2. Event 为什么叫“控制反转”?Agent 不知道 Plugin 是谁是什么意思?
这句话我前面说得不够严谨。
更准确的表述应该是:
Agent Loop 知道“这里存在一个扩展点”,但不知道“有哪些插件会监听这个扩展点、它们会做什么”。
不是说 Agent 完全不知道 Event。
先看没有 Event 的写法
比如你现在有一个 Agent:
async function runAgent(query) {
const memory = await memoryService.retrieve(query)
const context = await contextService.build(memory)
const response = await llm.generate(context)
for (const tool of response.toolCalls) {
await permissionService.check(tool)
await auditService.record(tool)
const result = await toolService.execute(tool)
await telemetryService.record(result)
}
return response
}
注意依赖关系:
Agent Loop
│
┌────────────────┼─────────────────┐
▼ ▼ ▼
Memory Permission Audit
│
▼
Context
│
▼
LLM
│
▼
Tool
│
▼
Telemetry
Agent Loop 必须知道:
Memory 存在
Permission 存在
Audit 存在
Telemetry 存在
如果明天再增加:
Privacy
Safety
Experiment
RateLimit
Tracing
Personalization
你就得改:
runAgent()
于是 Agent Loop 越来越胖。
Event 模型反过来了
Agent Loop 只定义几个生命节点:
async function runAgent() {
messages = await emit("agent/pre-step", messages)
request = await emit("agent/request", request)
response = await llm(request)
toolCall = await emit("tools/pre-execute", toolCall)
result = await execute(toolCall)
result = await emit("tools/post-execute", result)
}
这里 Agent Loop 只知道:
“工具执行之前,我留一个插槽。”
它不知道:
谁会用这个插槽。
Permission Plugin 自己注册
比如:
ctx.on("tools/pre-execute", async (call, next) => {
if (!hasPermission(call)) {
throw new PermissionDenied()
}
return next(call)
})
Safety Plugin 也注册:
ctx.on("tools/pre-execute", async (call, next) => {
checkSafety(call)
return next(call)
})
Audit Plugin:
ctx.on("tools/pre-execute", async (call, next) => {
audit(call)
return next(call)
})
于是运行时:
Agent Loop
│
│ “tools/pre-execute”
▼
┌───────────────────────────┐
│ Event Pipeline │
│ │
│ Permission Plugin │
│ ↓ │
│ Safety Plugin │
│ ↓ │
│ Audit Plugin │
│ ↓ │
│ next() │
└───────────────────────────┘
│
▼
execute tool
DSH 当前把 agent/pre-step、agent/request、tools/pre-execute、tools/execute、tools/post-execute 等定义成 waterfall middleware:监听器可以修改、短路、恢复或者继续 next()。(GitHub)
“控制反转”到底反转了什么?
原来:
Agent
↓
我要调用 Permission
↓
我要调用 Safety
↓
我要调用 Audit
控制者是 Agent。
变成 Event:
Agent
↓
“我现在到 Tool PreExecute 了”
Permission:
“这个时刻我需要介入。”
Safety:
“这个时刻我也要介入。”
Audit:
“我也介入。”
变成:
Agent 只公布生命周期
Plugin 自己挂到生命周期
所以叫:
Inversion of Control。
一个特别适合你的 Memory 例子
假设今天主 Agent 代码是:
Agent Loop
后来你想加入:
默认记忆加载
传统方法:
async run(query) {
const memory = await memory.loadDefault(query)
...
}
Agent 被 Memory 污染了。
Event 方法:
Agent Loop
│
▼
agent/pre-step
Memory Plugin:
on("agent/pre-step", async (messages, next) => {
const memories = memoryHotCache.get(...)
return next(
injectMemory(messages, memories)
)
})
以后你把 Memory 系统整个删掉:
卸载 Memory Plugin
Agent Loop 一行代码不改。
这才是最重要的价值。
但这里有一个边界
Plugin 不能凭空决定任意时间介入。
Agent Loop 必须事先提供:
agent/pre-step
agent/request
tools/pre-execute
...
这些 Extension Point。
DSH 官方甚至直接说:
“事件就是扩展点”。(GitHub)
所以更准确地说:
Agent 决定有哪些插槽;Plugin 决定自己挂哪个插槽。
这句话最准确。
3. Summary 真的是“声明替代 1-3”吗?
这里需要精确纠正一下。
概念上是。
但源码里不是:
compaction/summary
surfaceOp = replace
实际上:
compaction/summary
是一个 log-only event。
真正进行 Surface Replace 的,是它之后新增的一条:
user/message
官方 compaction 文档明确写了这个流程。(GitHub)
完整流程是:
compaction/start
↓
生成 summary
↓
compaction/summary
(log-only)
↓
user/message
content = summary
surfaceOp = replace(...)
↓
compaction/end
举一个真实结构的例子
假设 Raw Session Log:
Seq 0 turn/start
Seq 1 user/message
"帮我分析一下这个项目"
surfaceOp = append
Seq 2 step/start
Seq 3 assistant/message
"我先看看目录..."
surfaceOp = append
Seq 4 tool/result
"src/..."
surfaceOp = append
Seq 5 assistant/message
"从目录看,这个项目..."
surfaceOp = append
Seq 6 step/end
Seq 7 turn/end
注意:
Raw Log 是:
0 1 2 3 4 5 6 7
但 Surface 只包含 message-producing event:
Surface
[1, 3, 4, 5]
因为:
turn/start
step/start
step/end
turn/end
不会进入 LLM history。当前只有 user/message、assistant/message、tool/result 三类 Surface Event。(GitHub)
现在发生 Compaction
系统决定把:
Surface [1,3,4,5]
压成:
用户要求分析项目。Agent 查看了项目目录并得出初步架构分析……
先 append:
Seq 8
compaction/start
然后:
Seq 9
compaction/summary
summary:
"用户要求分析项目……"
shadowedSeqs:
[1,3,4,5]
shadowedRange:
start=1
end=5
但 Seq 9 不进入 Surface。
然后再 append:
Seq 10
user/message
content:
"用户要求分析项目。Agent 查看了项目目录……"
surfaceOp:
{
op: "replace",
start: 1,
end: 5
}
sourceEventSeqs:
[1,3,4,5]
最后:
Seq 11
compaction/end
官方的 SurfaceOp 类型真的就是:
type SurfaceOp =
| "append"
| {
op: "replace"
start: number
end: number
}
而 replacement event 的 sourceEventSeqs 必须覆盖被 shadow 的 Surface nodes。(GitHub)
此时 Raw Log 还是:
0
1 user/message
2
3 assistant/message
4 tool/result
5 assistant/message
6
7
8 compaction/start
9 compaction/summary
10 user/message(summary replacement)
11 compaction/end
一个都没删。
但是 Surface 从:
[1,3,4,5]
变成:
[10]
模型之后看到:
User:
“用户要求分析项目。Agent 查看了项目目录……”
而不是再看到 1、3、4、5。
官方明确说明:shadowed events 仍然保留在 raw log,因此 replay 仍然是确定性的。(GitHub)
更妙的一点:start/end 不是简单数字区间
这点非常容易忽略。
假设经过一次 Replace 后:
Surface:
[100, 30, 35]
这是合法的。
因为 100 可能是后来 append 的 summary,但被插回了原来较早的位置。
所以:
replace(start=100,end=35)
也可能成立。
它表达的是:
Surface 顺序中的某一段
而不是:
seq 100 ~ seq 35
这样的数值区间。
官方源码也专门强调 shadowedRange 是 surface-position span,而 shadowedSeqs 才是实际被替换节点的权威列表。(GitHub)
这说明他们这个设计考虑得挺细。
4. Surface 怎么派生 LLM history?
其实这一步比想象中简单。
核心逻辑就是:
Raw Log
↓
SurfaceManager
↓
surface.nodes
↓
deriveEventMessage()
↓
Message[]
第一步:先维护 Surface Nodes
每次 append Surface Event 都声明:
append
或者:
replace
例如:
Raw Log
0 turn/start
1 user/message append
2 step/start
3 assistant/message append
4 tool/result append
5 assistant/chunk
6 assistant/message append
SurfaceManager 得到:
nodes = [1,3,4,6]
turn/start、step/start、assistant/chunk 不在其中。
第二步:deriveMessages() 遍历 Surface Nodes
源码核心逻辑基本就是:
for (const seq of nodes) {
const event = log[seq]
const msg = deriveEventMessage(event)
if (msg)
history.push(msg)
}
当前源码就是这样的,只不过做了缓存优化。(GitHub)
第三步:不同 Event 转不同 Message
当前规则是:
user/message
变成:
role = user
content = 原 content
assistant/message
变成:
role = assistant
content = 原 content
provider/model 信息
tool/result
比较特殊。
它被映射成:
role = user
content:
tool-result block
这是 LLM Provider 对工具结果的消息表达形式。(GitHub)
哪些东西不会进 History?
例如:
turn/start
turn/end
step/start
step/end
assistant/chunk
usage
request/context
compaction/start
compaction/summary
compaction/end
todo/write
hook/invoked
...
这些都是:
Log-only Event
不进入 Surface,自然也就不进入 deriveMessages()。官方文档明确说只有三类 Surface Event:user/message、assistant/message、tool/result。(GitHub)
所以这里不是每轮做:
LLM 判断:
“这个 Event 值不值得放进历史?”
不是。
它是确定性规则:
Event Type
+
SurfaceOp
决定。
Memory Context 怎么进去?
如果 Memory Plugin 注入:
用户偏好:
冰美式、无糖
最终它会形成一个:
user/message
source = memory/context
surfaceOp = append
于是自然进入 Surface。
官方现在也规定 injected context 仍然投影成 user-role message,只不过其 typed source 可以说明它不是用户本人输入。(GitHub)
那派生 History 有时延开销吗?
有,但正常情况下非常小,不是我们前面讨论 TTFT 时真正值得担心的部分。
原因是它做了增量缓存。
源码里维护:
private derived: Message[] = []
private derivedNodes = 0
private derivedGeneration = 0
每次调用:
deriveMessages()
先看:
Surface 有没有发生 replace?
如果没有:
旧 nodes:
[1,3,4,6]
新 nodes:
[1,3,4,6,10]
只派生:
10
即:
O(new nodes)
而不是重新扫描所有 history。官方源码注释明确写的是“each surface node is projected exactly once”;正常 append 情况调用成本是 O(new nodes)。(GitHub)
什么情况下会重建?
发生:
surface replace
例如 Compaction。
SurfaceManager 有:
replaceGeneration
一旦 replace:
generation++
如果:
generation !== derivedGeneration
源码会:
this.derived = []
this.derivedNodes = 0
然后重新派生当前 Surface。(GitHub)
所以时延性质是:
正常:
历史已有 1000 条
新增 1 条
derive:
只处理 1 条
非常轻。
Compaction 后:
Surface 发生 rewrite
重新遍历当前 Surface
但注意:
Compaction 本来就是为了把 Surface 变小。
所以通常也不会特别重。
真正影响 TTFT 的不是 deriveMessages()
我会这样排:
deriveMessages()
↓
很小
Context Retrieval
↓
较大
Memory Search
↓
较大
Compaction LLM Call
↓
很大
LLM Prefill
↓
最大头之一
所以你前面担心 TTFT 是对的,但 Surface → Message[] 这一步不是主要矛盾。
5. Event 和 Claude Code Hook 是不是一样?
理念非常像,可以把 Claude Code Hooks 看成 Event/Hook 架构的一种产品化实现。
Claude Code 官方现在的定义就是:
Hooks 是在 Claude Code 生命周期特定点自动执行的用户定义命令、HTTP endpoint 或 LLM prompt。(Claude Platform Docs)
比如:
SessionStart
UserPromptSubmit
PreToolUse
PostToolUse
Stop
SubagentStart
SubagentStop
PreCompact
PostCompact
这跟 DSH:
agent/pre-step
agent/request
tools/pre-execute
tools/execute
tools/post-execute
agent/turn-stopping
思想几乎是一脉相承的。
比如工具权限
Claude Code:
Claude wants Bash
↓
PreToolUse Hook
↓
检查 command
↓
allow / block
Claude 官方示例甚至就是检查 Bash 命令,如果匹配危险操作则 block。(Claude Platform Docs)
DSH:
Model tool call
↓
tools/pre-execute
↓
Permission Plugin
↓
next / reject
非常相似。
但二者有一个层级区别
我会把它们理解成:
Claude Code Hook
=
“产品提供出来的扩展点”
而:
DSH Event
=
“Runtime 内部统一的控制机制”
Claude Code Hook 更多是:
Claude Code
↓
公开一些 hook 给外部用户
你不能通过 Hook 去重新定义 Claude Code 整个内部架构。
而 DSH 中 Event 本身就是微内核的基本组成机制之一。
不仅外部扩展用 Event,
内部组件之间也用:
agent/*
tools/*
fs/*
session/*
这套事件机制协作。
官方将 DSH 的 extension points 明确定义为 Cordis typed events,并区分 waterfall、serial、parallel、emit 四种调度语义。(GitHub)
所以可以理解成这个关系
Lifecycle Extension
┌──────────────┐
│ Event / Hook │
└───────┬──────┘
│
┌─────────────┴─────────────┐
▼ ▼
Claude Code Hook DSH Event
面向产品扩展 Runtime 原生机制
Shell / HTTP / Typed TS Listener
Prompt / Agent
外部扩展居多 内外统一
固定产品生命周期 Harness 微内核生命周期
我给你一个最后的总结
你这 5 个问题下来,其实可以更准确地重新评价 DSH 三个核心东西:
Service
不是新东西。
本质:
Interface
+
Provider
+
Runtime DI
+
Lifecycle
+
Scope
如果你们已有成熟服务框架,这部分借鉴意义一般。
Event
很值得学。
关键不是“事件通知”。
而是:
把 Agent Loop 切成稳定 Extension Points
然后让:
Memory
Privacy
Permission
Safety
Telemetry
Compaction
在不修改 Agent Loop 的情况下插进去。
对你们语音助手这种会不断增加系统能力的产品,这个价值很大。
Session Log + Surface
我认为最值得学。
它把:
真实发生过什么
和:
模型当前应该看到什么
彻底解耦:
Raw Session Log
│
│ immutable
▼
完整事实
Raw Log
│
▼
Surface Projection
│
▼
当前有效 Context
│
▼
deriveMessages()
│
▼
LLM History
尤其是:
append-only truth
+
replaceable surface
+
sourceEventSeqs provenance
这套东西我觉得和你们的 L0 原始记忆 + 当前上下文 + 长期记忆非常契合。
如果让我现在从 DSH 里只选两个设计带回你们系统,我会选:
① Agent Lifecycle Event Bus
② Raw Event Log → Surface → LLM Context 三层分离
Service 反而是第三位。
Claude Code 读源码校验以上结论
结论:ChatGPT 讲得基本正确,错误主要集中在细节措辞和引用规范上
我先声明一件重要的事:我在核对前凭印象怀疑的几处「明显捏造」,逐一去代码库查证后全部被推翻了。四种模式、Minimal 的能力清单、tool/result 映射成 user role、把 Claude Code/Codex 当 SubAgent 后端——这些 ChatGPT 都说对了。下面只列真正站不住的部分。
一、确实错的
1. reasoning/chunk 这个事件不存在(gpt学习.md:286)
真实类型是 assistant/chunk。完整事件词表在 packages/core/session/src/known-event-types.ts:19-64(该文件由 gen-persistence-catalog.ts 自动生成,是权威清单),44 个类型里没有 reasoning/chunk。推理内容是 assistant/chunk 里的一种 chunk 子类型,不是独立事件。
2. 把 permission 列为「模型看见过的东西」,混淆了两件事(:292)
ChatGPT 在「Model-visible means logged」标题下列出 permission。日志里确实有 approval/asked / approval/decided / approval/policy,但这三个是审计记录,不进 Surface。packages/core/session/src/surface.ts:96-113 的 deriveEventMessage 只认三种类型,其余一律 return null。方向搞反了:不是「模型看见所以记录」,而是「记录了但模型看不见」。这个原则是单向的——模型可见 ⟹ 已记录,反之不成立。
3. 「Event 大致有两种性质」低估了实际维度(:1572)
- 分发模式有五种,不是两种:
emit/parallel/serial/bail/waterfall(vendor/cordis/src/events.ts:32)。ChatGPT 归纳的「通知型 / 拦截型」把 serial 和 bail 漏了,而agent/turn-stopping恰好是 serial——它能拦截但没有next()(docs/architecture.zh.md:88明确点出这个区别)。 - 事件域有三个,不是两个:会话事件、Agent 事件(
agent/*)、能力事件(fs/*/tools/*/telemetry/*)(docs/architecture.zh.md:59-61)。ChatGPT 的 Live / Session 二分法把能力事件整个漏掉了,而那正是「不 import 主循环就能挂策略」的关键一层。
4. 架构图把 Cordis 画在 Agent Interface 下面(:40-46)
图里 Agent Interface 在 Cordis Kernel 之上。实际相反:Cordis 是底座,packages/core/agent 是长在它上面的插件。ChatGPT 自己在正文里说对了(「Agent Loop 本身都不是神圣不可修改的」),只是图画反了。
5. 引用了一篇归档笔记当权威(:2725)
引用 [4] 指向 .agents/notes/archived/process/2026-07-04-persistence-log-catalog.md。文件确实存在,但 AGENTS.md:122 明确规定:归档笔记已冻结,永远不要编辑它、也不要把它当作当前权威。恰好 ChatGPT 关于 Surface / deriveMessages 的多处论述都挂在这个 [4] 上——结论碰巧对了,但取证路径是仓库明令禁止的。当前权威应该用自动生成的 known-event-types.ts 和 surface.ts 源码。
6. 「4.49 万 GitHub Stars」无法核实(:996)
这属于外部事实,不在代码库范围内,我无从验证。考虑到仓库还处于 Developer Preview(README.zh.md:11),这个数字建议你自己去 GitHub 确认再引用。
7. 一处引用串行导致的乱码
「Agent = Mode(深度求索)境、调用工具、维持状态、执行任务」是脚注插入把句子撕断产生的,不是官方表述。
二、说对了、而且值得肯定的
我原本怀疑是编造的,核实后确认无误:
- 四种 preset 真实存在,且中文名与 ChatGPT 的说法完全一致:
apps/cli/config/agent-presets/下standard(标准模式)、code(PTC 模式)、minimal(极简模式)、cordis(创造模式)。「PTC」确实是官方用词。 - Minimal 的能力清单几乎逐条命中:
minimal/agent.cordis.yml里只有持久bash+str_replace_editor;complete: true让 persona 成为完整系统提示词,后续 listener 加不进任何文字;includeRuntimeContext: false关掉运行时快照;整个文件没有 compaction 行(文件头注释直接写明 “Context compaction is absent”)。ChatGPT 的每一条都对得上。 tool/result映射成role: 'user'是对的。packages/llm/llm/src/message.ts:152-156:ToolResultMessage extends Message且readonly role: 'user',content 是单个tool-resultblock。我此前认为这是「把 provider 线格式和内部模型搞混」,是我错了。DSH 内部就是 user role 承载 tool-result block,到 DeepSeek 线格式时才由serialize.ts:133转成role: 'tool'。- Claude Code / Codex 可作 SubAgent 后端:
packages/subagent/subagent-claude-code、subagent-codex真实存在,且cordis/agent.cordis.yml:191-207有对应的 tool 行(默认disabled: true,注释说明产品级 provider 是 host 平面单例)。 - 创造模式确有自我修改能力:
cordispreset 挂了tool-cordis,文件头注释直接写「cordis_mountevaluates model-written JavaScript against the live runtime … Treat a session on this preset as shell access」。ChatGPT 说的「Self-Evolving Agent Runtime」方向是有实据的。 - 第三轮的两次自我纠正都准确:Service 并非新概念(本质是接口 + 可替换性 + 生命周期);以及 compaction 的
replace实际由紧随其后的user/message承载,compaction/summary只是日志记录。后者尤其难得,很多人会想当然认为是 summary 自己执行替换。 - Surface 机制整体正确:append-only、
SurfaceOp形状、只有三种 Surface 事件、derived/derivedNodes/derivedGeneration增量缓存、追加是 O(新节点) 而 replace 触发全量重建、shadowedRange是 surface 位置区间而shadowedSeqs才是权威列表。 - **Claude Code Hooks 是「产品扩展点」而 DSH Events 是「运行时原生机制」**这个层级区分,抓得很准。
三、漏掉的、会影响你判断的部分
- fail-closed 纪律是这套 harness 最见功力的地方,ChatGPT 完全没提:approval policy 为
'never'时在任何 waterfall 之前就短路;没有 approval responder 就返回unavailable(拒绝,而非放行);guard 是单调的,跑在 pre-execute 的 allow 之后,只能拒不能批——也就是说前面的插件放行了,后面的 guard 依然能否决。 - compaction 先做确定性裁剪,再花 LLM 调用:
compaction-tool-result-pruner按字符阈值先砍(cordispreset 里配的是thresholdChars: 8192/headChars: 4096/tailChars: 1024),ChatGPT 直接跳到摘要。 packages/spill整个没提:它管的是「单个工具输出过大」,触发点在tools/post-execute;compaction 管的是「历史总长」,触发点在agent/pre-step。两条正交的轴,ChatGPT 只讲了一条。- prompt caching 是被动的:没有
cache_control/ breakpoint API,纯靠请求前缀字节稳定。这解释了为什么 plan mode 要刻意保持工具目录不变(cordis/agent.cordis.yml:106:「The tool catalog stays the same across modes for request-cache stability」)——这是个很反直觉的设计约束。 - 注入上下文的主路径是在
agent/pre-stepwaterfall 里直接改写decision.messages,不是agent.inject()。后者是给外部调用方的被动预设(next-step、不唤醒)。
四、这套 harness 的核心亮点
1. 微内核选得够狠。 Cordis 只管插件加载/卸载/依赖/事件,不拥有任何 Agent 能力。主循环 packages/core/agent-loop/src/agent.ts 全部只有 496 行,且它自己也是个可替换插件。219 个包收敛到 496 行核心,这个比例说明抽象是真的立住了。
2. 「注册即 effect」让一切可逆。 每个贡献都走 ctx.effect() / ctx.on(),registry 的 register() 返回 disposer,逆序释放,嵌套 effect 形成带标签的诊断树。热插拔不是补丁,是地基属性。
3. epoch 把依赖变化压成一次字符串比较。 形如 :3:7:12 的依赖指纹,加载 / 卸载 / 热替换三种情况用同一个比较处理完。
4. 「模型可见 ⟺ 已记录」是有运行时断言的硬约束,不是文档口号。凡进入模型请求的东西都必须能从日志重建,于是 resume / fork / 回放 / trajectory / 检索全部从同一个事件流派生,不需要各自维护状态。
5. Surface 投影解决了 append-only 与上下文压缩的矛盾。 原始日志永不改写,压缩通过 shadow 屏蔽 surface 节点。既拿到审计完整性,又拿到可控的模型上下文——这是我认为最值得直接搬走的设计。
6. 相位状态机只留三态。 idle | maintenance | running,turn 和 step 只是 running 分支里的两个数字,真正的结构全在日志里。状态机不膨胀,因为它拒绝承担记忆职责。
7. 能力接缝强制三件套。 Service Definition / Provider / Consumer 三个角色齐全才算一条接缝,绝不只有一个角色(AGENTS.md:109)。所以扩展插件永远依赖定义、不依赖具体实现,sandbox 能在 Landlock / bwrap / seatbelt / windows-acl 之间换而上层无感。
8. fail-closed 贯彻到底。 前面已列。这是把「安全」做成结构性质而非检查清单的做法。
一句话总结:ChatGPT 对这套 harness 的架构判断是可靠的,值得你继续参考;需要修正的是事件词表细节(assistant/chunk、五种分发模式、三个事件域)、permission 的可见性方向、以及不要沿用它对归档笔记的引用。真正的缺口在 fail-closed 纪律、spill 与 compaction 的正交关系、以及被动 prompt caching 这三块——恰好都是工程上最难还原的部分。

196

被折叠的 条评论
为什么被折叠?



