Deepseek Harness 的学习记录

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

User:

deepseek harness 出来了,帮我研究下

ChatGPT

我把 DeepSeek 官方刚放出的 DeepSeek Harness(DSH) 系统性看了一遍。代码仓库是 deepseek-ai/deepseek-harness。截至 2026 年 8 月 14 日,官方定位仍是 Developer Preview,但已经可以直接通过 npx @deepseek-ai/dsh web 跑起来,GitHub 仓库已经是完整工程而不是 Demo。官方给它的定义非常明确:

**Agent = Mode(深度求索)境、调用工具、维持状态、执行任务。(深度求索)

我的核心判断是:

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 CodeCodexDeepSeek Harness
目标Coding Agent 产品Coding Agent 平台/产品Agent Runtime
Agent Loop内建内建可替换 Plugin
ToolsHooks/MCP/SDKMCP/toolsPlugin Service
Session产品控制产品控制可替换 Event Store
Sandbox内建权限体系OS SandboxProvider seam
SubAgent原生原生Provider abstraction
UI产品 UI产品 UIPlugin
ModelClaudeOpenAIProvider-neutral
Self modification较弱较弱明确设计目标
Code orchestrationAgent loopAgent loopPTC / 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

拆成了两个可以独立演进的产品层。

对构建语音助手的人来说,我尤其建议深入看三个部分:

  1. core/session + session-query —— 对记忆系统最有价值
  2. core/agent + agent-loop + event system —— 对语音助手主 Agent 架构最有价值
  3. 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-stepagent/requestllm/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传统系统概念核心问题
ServiceAPI / Device / Capability能做什么
EventInterrupt / Middleware / Hook何时介入
Session LogWAL / 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:

有几个问题:

  1. Service 本质还是一个接口,我定义接口的时候,就按能力分类,准备不同接口不也能实现同样的效果吗?
  2. 具体举例展开讲讲Event 最重要的价值:控制反转,对event “Agent 不知道插件是谁,插件自己决定什么时候介入。”这句话不太理解
  3. Summary声明我替代1-3,真是这么做的吗?举个具体的例子。
  4. Session 上面维护了一层有序的 Surface projection,LLM message history 就是从这里高效派生。具体是怎么派生的?怎么决定哪些内容放到history里?这一步派生有时延开销吗?
  5. 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-stepagent/requesttools/pre-executetools/executetools/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/messageassistant/messagetool/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

这样的数值区间。

官方源码也专门强调 shadowedRangesurface-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/startstep/startassistant/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/messageassistant/messagetool/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

(Claude Platform Docs)

这跟 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,但这三个是审计记录,不进 Surfacepackages/core/session/src/surface.ts:96-113deriveEventMessage 只认三种类型,其余一律 return null。方向搞反了:不是「模型看见所以记录」,而是「记录了但模型看不见」。这个原则是单向的——模型可见 ⟹ 已记录,反之不成立。

3. 「Event 大致有两种性质」低估了实际维度:1572

  • 分发模式有五种,不是两种:emit / parallel / serial / bail / waterfallvendor/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 InterfaceCordis 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.tssurface.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(标准模式)、codePTC 模式)、minimal(极简模式)、cordis创造模式)。「PTC」确实是官方用词。
  • Minimal 的能力清单几乎逐条命中minimal/agent.cordis.yml 里只有持久 bash + str_replace_editorcomplete: true 让 persona 成为完整系统提示词,后续 listener 加不进任何文字;includeRuntimeContext: false 关掉运行时快照;整个文件没有 compaction 行(文件头注释直接写明 “Context compaction is absent”)。ChatGPT 的每一条都对得上。
  • tool/result 映射成 role: 'user' 是对的packages/llm/llm/src/message.ts:152-156ToolResultMessage extends Messagereadonly role: 'user',content 是单个 tool-result block。我此前认为这是「把 provider 线格式和内部模型搞混」,是我错了。DSH 内部就是 user role 承载 tool-result block,到 DeepSeek 线格式时才由 serialize.ts:133 转成 role: 'tool'
  • Claude Code / Codex 可作 SubAgent 后端packages/subagent/subagent-claude-codesubagent-codex 真实存在,且 cordis/agent.cordis.yml:191-207 有对应的 tool 行(默认 disabled: true,注释说明产品级 provider 是 host 平面单例)。
  • 创造模式确有自我修改能力cordis preset 挂了 tool-cordis,文件头注释直接写「cordis_mount evaluates 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 按字符阈值先砍(cordis preset 里配的是 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-step waterfall 里直接改写 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 这三块——恰好都是工程上最难还原的部分。

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值