- 人工智能
- AI Agent
- Agent 框架
- DeepSeek
【免费下载链接】deepseek-harness
DeepSeek Harness: Everything is a Plugin.
本篇技术指南围绕 DeepSeek Harness 仓库内一份已归档的架构提案笔记(.agents/notes/proposed/architecture/2026-06-16-typed-event-schemas.md)展开,剖析其核心议题:harness 以“合并可扩展映射”(merge-extensible map)模式建模核心词表,但该模式只在编译期存在,运行时类型被完全擦除——持久化边界与插件边界因此缺乏结构校验。文章将完整继承笔记中的问题分析、爆炸半径实测、三种备选方案、延期决策与验收标准,并结合仓库源码(会话事件类型、JSONL 持久化、工具 Schema DSL 等)逐层印证。读完本文,你将理解该模式为何成为仓库的“通用扩展模式”、其运行时空白的具体代价,以及为什么“用 Zod 校验事件”在结构上注定是一次全仓库级词表重构而非持久化实现细节。
一、背景:什么是 merge-extensible-map 模式
DeepSeek Harness 将核心词表——内容块(content blocks)、消息来源(message sources)、结束原因(finish reasons)、回合触发(turn triggers)、回合结束原因(turn-end reasons)与会话事件(session events)——统一建模为merge-extensible map:即一个 TypeScriptinterface(如SessionEventMap、ContentBlockMap),插件通过**声明合并(declaration merging)**向其中追加键,公开联合类型则由Map[keyof Map]派生而来。这是整个仓库的通用扩展模式,docs/architecture.md 明确记载:“The same merge-extensible-map pattern is used forMessageSource,FinishReason,TurnTrigger, andTurnEndReason”。
该模式同时支撑着两个关键的仓库惯例:
defineTool的InferArgsDSL:从编译期 Schema 规格推导出零转型(zero-cast)的execute参数类型,见 packages/core/tools/src/schema.ts 中export type InferArgs<S> = InferProperties<S, []>;assertNever穷尽性检查惯例:AGENTS.md中明确要求“封闭联合以assertNever收尾,可扩展联合落入文档化的default分支”(见 AGENTS.md)。
笔记点名了六张核心映射表(约 370 行核心类型代码):
| 映射表 | 所在包 | 仓库位置 |
|---|---|---|
ContentBlockMap | dsh-llm | packages/llm/llm/src/types.ts |
MessageSourceMap | dsh-llm | packages/llm/llm/src/types.ts |
FinishReasonMap | dsh-llm | packages/llm/llm/src/types.ts |
TurnTriggerMap | dsh-session | 同属会话事件类型定义域 |
TurnEndReasonMap | dsh-session | packages/core/session/src/types.ts |
SessionEventMap | dsh-session | packages/core/session/src/types.ts |
以SessionEventMap为例,其注释明确自述:“The merge-extensible, append-only source of truth for an agent interaction”,事件携带连续序列号、以无损 JSON 存储,持久化可原样落盘整个规范日志:
export interface SessionEventMap { 'turn/start': { turn: number } 'turn/end': { turn: number; reason: TurnEndReason } 'step/start': { turn: number; step: number } 'step/end': { turn: number; step: number } 'user/message': UserMessage 'assistant/chunk': { turn: number; step: number; chunk: StreamChunk } // ... 更多事件类型 }而派生联合的写法是export type SessionEventType = keyof SessionEventMap与export type ContentBlock = ContentBlockMap[ContentBlockType](见 packages/llm/llm/src/types.ts)。FinishReasonMap则允许各 LLM 适配器扩展供应商专属的结束原因(stop、tool-calls、max-tokens、aborted、error等)。
二、问题本质:编译期模式在运行时留下的空白
merge-extensible-map只存在于编译期。类型在运行时被完全擦除:仓库中不存在任何 Schema 对象,无法用它校验入站值、解析不可信输入或在运行时枚举词表。
会话持久化契约(见已实现笔记 .agents/notes/implemented/architecture/2026-06-14-session-persistence.md)由此暴露两个具体后果:
持久化把
event.data当作不透明 JSON。JSONL/SQLite 后端对每个事件原样JSON.stringify/JSON.parse,唯一的运行时防线是isJsonValue——它只做往返可序列化性检查(拒绝 BigInt、函数、循环引用、非有限数值等),而非结构校验。其实现位于 packages/core/session/src/json.ts,配套测试 packages/core/session/tests/json.spec.ts 覆盖了-0、NaN、BigInt、稀疏数组、伪造原型对象等边界。后果是:一个“损坏但仍是合法 JSON”的事件数据(字段类型错误、字段缺失)会静默往返,直到某个消费方的switch遇到它才被发现——甚至永远不被发现。插件新增变体没有运行时契约。插件通过声明合并向
SessionEventMap追加新键,只能为自己的代码拿到编译期类型;但没有任何机制校验它产出的值是否真的符合其声明的形状——在生产者端、持久化边界或重新加载时都没有。
三、为什么这不是一次“持久化改动”
很容易把“用 Zod 做序列化”误读为dsh-session-persistence-jsonl/src/format.ts的局部改动。笔记给出了一个决定性的结构理由:插件无法对 Zod Schema 做声明合并。声明合并是 TypeScript 编译期机制,而 Zod Schema 是运行时值。要用 Zod 校验事件,就必须引入运行时注册表(runtime registry)——每个产生事件的包向其中注册自己的 Schema(如ctx.sessionEvents.register('compaction/marker', z.object({…}))),每个消费方再从注册表读取。这个注册表(而非持久化后端)将成为词表的单一事实来源,取代可合并扩展的 interface。
因此真实提案是:全仓库范围内,用运行时 Schema 注册表替换编译期的 merge-extensible-map 模式。这是一次核心词表重构,而非持久化实现细节。
四、爆炸半径(实测数据)
笔记对迁移事件/词表 API 到运行时 Schema 的影响面做了实测,至少触及:
- 六张 merge-extensible 映射表(约 370 行核心类型),即上文表格所列;
- 约 10 处
declare module增强点,分布于dsh-agent、dsh-agent-loop、dsh-shell、dsh-llm、dsh-session、dsh-session-persistence、dsh-system-prompt、dsh-tools——每一处都要从声明合并改为运行时register()调用; - 事件生产者:循环(loop)中 16 处
session.append(...)调用点,形状不变,但会在边界处新增校验; - 约 7 个基于这些联合类型做
switch的消费方:deriveMessages及其包内不变式伴生模块(dsh-session)、BlockAssembler(dsh-llm)、两个 LLM 适配器(dsh-llm-deepseek、dsh-llm-pi-ai)、工具 Schema 层(dsh-tools); assertNever惯例需要重新思考:已文档化的 lint 规则“封闭联合以assertNever结尾 vs 可扩展联合落入 fall-through”在运行时变体面前失效——运行时变体在静态层面并不穷尽。这一点在 docs/subsystems/session.md 中有对应表述:由于SessionEventMap可合并扩展,对SessionEvent的switch禁止使用assertNever,插件新增的变体是合法的未知值,应处理已知 case 后在default中放行;defineTool的InferArgsDSL(dsh-tools):它从编译期 Schema 规格推导零转型的execute参数类型,是当前方案的代表性成果;- 文档:
docs/architecture.md(该模式被描述为基础性的)、dev-mode invariants 笔记(.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md)以及所有引用该模式的 Agent Note。
结论明确:这是全仓库范围的词表重构,不是持久化实现细节。
五、三种备选方案
方案 A:维持现状——merge-extensible 类型 + 持久化边界的isJsonValue
保留编译期模式。持久化维持“不透明 JSON + 可序列化性守卫”。插件通过声明合并扩展;事件形状的正确性由生产者负责,并在编译期由 TypeScript 强制。包内不变式伴生模块在启用时校验选定的跨记录关系,但不提供通用的运行时形状 Schema。
- 优点:零变动;插件扩展只是一行
interface增强,具备完整类型推断,无运行时注册仪式;不新增运行时依赖;defineToolDSL 与assertNever穷尽性检查照常工作。 - 缺点:持久化边界与插件边界都没有运行时结构校验;一个畸形但仍是 JSON 的数据会被延迟发现。
方案 B:仅对封闭头部/元数据形状做 schemastery 校验(事件保持不透明)
只收紧那些本就封闭、且已有手写类型守卫的形状——例如 JSONL 的HeaderLine守卫(isHeaderLine)——改用schemastery(仓库现有 Schema 库,已被每个插件的static Config使用)声明式描述。可合并扩展的事件联合保持不变。
仓库证据支持“schemastery 是仓库既定选择”这一前提:
@deepseek-ai/schemastery以 workspace 依赖形式被引入,见 apps/cli/package.json;- 插件配置与设置卡片均用其声明 Schema,见 docs/cookbook/adding-a-settings-card.md 中
import z from '@deepseek-ai/schemastery'的用法; - docs/config-catalog.md 说明配置目录由运行时 schemastery Schema 与声明类型交叉核对生成。
目标守卫isHeaderLine位于 packages/session/session-persistence-jsonl/src/format.ts,是典型的手写逐字段类型守卫(校验type === 'session'、version/id/createdAt/delegationDepth的数字与安全整数约束、-0拒绝、origin/agentPreset的可选字段形状等)。
- 优点:改动小、契合现有惯例(schemastery 而非新库);将封闭形状上的手写守卫替换为声明式 Schema;无需核心重构。
- 缺点:不解决事件数据校验;只有固定的元数据记录得到改善。
方案 C:全词表运行时 Schema 注册表(Zod 或 schemastery)
用运行时注册表替换 merge-extensible 映射表:生产者向注册表贡献 Schema,持久化与消费路径据此校验。
- 优点:持久化边界与插件边界获得真正的运行时校验;单一事实来源;可支撑通用工具链(自动生成文档、模糊测试、线格式检查)。
- 缺点:就是前述全部爆炸半径;且Zod 目前不是直接依赖(只是
@earendil-works/pi-ai的传递依赖),而仓库选定的 Schema 库是 schemastery——广泛采用 Zod 本身就是一次依赖决策;声明合并的易用性(一行插件扩展、完整推断)被“运行时注册 + 手工类型接线”取代;assertNever静态穷尽性保证被削弱(运行时变体在静态层面不穷尽)。
三种方案的横向对比:
| 维度 | A 现状 | B 封闭头部 schemastery | C 全词表注册表 |
|---|---|---|---|
| 运行时结构校验 | 无 | 仅头部/元数据 | 事件数据 + 插件边界 |
| 改动规模 | 零 | 小 | 全仓库词表重构 |
| 新依赖 | 无 | 无(沿用 schemastery) | Zod 需成为直接依赖(二选一) |
| 插件扩展方式 | 一行声明合并 | 不变 | 运行时register()+ 手工类型接线 |
assertNever穷尽性 | 保持 | 保持 | 削弱 |
六、提案与验收标准
笔记的正式提案是:延期(Defer)。
- 如果确实需要持久化边界的运行时校验,方案 B(对封闭头部与元数据形状使用 schemastery)是在现有惯例内“相称的一步”;
- 方案 C属于架构决策,必须通过其独立的实现 Agent Note 推进,并包含 Zod 与 schemastery 之间的选型。
验收标准两条:
- 方案 C 只能通过其自身的实现 Agent Note 推进,绝不允许作为持久化的副作用顺带实施;
- 若采用方案 B,封闭的头部/元数据形状(JSONL 的
isHeaderLine守卫及其同类)应改用 schemastery 校验,取代手写守卫,merge-extensible 映射表保持不变。
七、风险
- 延期的代价:事件
data在持久化边界仍无结构校验——畸形但仍是 JSON 的数据会被消费方的switch延迟发现。这是现状成本,属于有意接受。 - 若未来采用方案 C:易用性损失是真实的——一行声明合并变成“运行时注册 + 手工类型接线”,且
assertNever静态穷尽性保证被削弱。
八、悬而未决的问题
笔记留下三个开放问题,供后续实现笔记回答:
- 库选型:若采用注册表,用仓库内已有且作为配置 Schema 库的schemastery,还是生态更丰富但目前仅是传递依赖的Zod?同时引入两个 Schema 库本身就是成本。
- 混合方案是否可行:能否保留编译期推断(让
defineTool与插件 DX 存活),同时为每个变体增加可选的运行时 Schema,仅在持久化/线边界校验,而非每次进程内append都校验? ctx.invariants服务是否已覆盖足够的运行时形状缺口:启用时,是否只有真正不可信输入(例如重载被外部修改的日志)才需要边界校验?
九、对开发者的启示与后续线索
对当前仓库的使用者与扩展者,这份笔记的实操价值体现在两点:
当下如何扩展事件词表(方案 A 语境):插件继续通过声明合并向SessionEventMap追加键,docs/architecture.md的“Where new behavior goes”表格给出对应原则——“Add durable session state → extendSessionEventMap; render and replay from the log”(docs/architecture.md)。消费方对可扩展联合的switch必须落入default而非assertNever(docs/subsystems/session.md)。
关注后续演进的落点:本提案为proposed状态,其姊妹篇已实现笔记 .agents/notes/implemented/architecture/2026-06-14-session-persistence.md 展示了持久化边界的现状设计(SESSION_FORMAT_VERSION = 0、SessionHeader元数据出日志、JSONL 与 SQLite 双后端契约,见 packages/core/session/src/types.ts)。若方案 B 被采纳,读者可直接跟踪 packages/session/session-persistence-jsonl/src/format.ts 的守卫演进;若方案 C 成行,则应关注上文“爆炸半径”一节列出的六张映射表与约 10 处增强点如何被register()调用取代。仓库中另有已实现的词表相关笔记(如 fail-closed 会话事件词表,见 packages/core/session/src/types.ts 注释中引用的.agents/notes/implemented/simplification/2026-08-25-fail-closed-session-event-vocabulary.md),可作为理解词表演进路线的延伸阅读入口。
本文所有源码位置均可直接在仓库中核验:类型定义见 packages/core/session/src/types.ts 与 packages/llm/llm/src/types.ts,运行时守卫见 packages/core/session/src/json.ts 与 packages/session/session-persistence-jsonl/src/format.ts,工具 Schema DSL 见 packages/core/tools/src/schema.ts,扩展惯例见 docs/architecture.md 与 AGENTS.md。
- 人工智能
- AI Agent
- Agent 框架
- DeepSeek
【免费下载链接】deepseek-harness
DeepSeek Harness: Everything is a Plugin.
相关推荐
DeepSeek Harness 工具参数 schema DSL:从否决 Schemastery 到统一 JSON 值词汇的设计决策
DeepSeek Harness 工具参数 schema DSL:从否决 Schemastery 到统一 JSON 值词汇的设计决策 本文围绕 DeepSeek
人工智能AI AgentAgent 框架DeepSeekDeepSeek Harness 会话日志不可变性与开发模式不变式:运行时所有权边界的源码级解析
DeepSeek Harness 会话日志不可变性与开发模式不变式:运行时所有权边界的源码级解析 导读 DeepSeek Harness(dsh)将"一切皆插件
人工智能AI AgentAgent 框架DeepSeekDeepSeek Harness 插件怎么接收配置:cordis.yml 配置、Schemastery Schema 校验与默认值
DeepSeek Harness 插件怎么接收配置:cordis.yml 配置、Schemastery Schema 校验与默认值 在 DeepSeek Har
人工智能AI AgentAgent 框架DeepSeek
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考