DeepSeek Harness 事件词表的运行时模式抉择:merge-extensible-map 与 Zod/schemastery 方案权衡
2026/9/20 18:02:40 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 框架
  • DeepSeek

【免费下载链接】deepseek-harness

DeepSeek Harness: Everything is a Plugin.

项目地址:https://gitcode.com/gh_mirrors/de/deepseek-harness
点击查看免费下载

本篇技术指南围绕 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(如SessionEventMapContentBlockMap),插件通过**声明合并(declaration merging)**向其中追加键,公开联合类型则由Map[keyof Map]派生而来。这是整个仓库的通用扩展模式,docs/architecture.md 明确记载:“The same merge-extensible-map pattern is used forMessageSource,FinishReason,TurnTrigger, andTurnEndReason”。

该模式同时支撑着两个关键的仓库惯例:

  • defineToolInferArgsDSL:从编译期 Schema 规格推导出零转型(zero-cast)的execute参数类型,见 packages/core/tools/src/schema.ts 中export type InferArgs<S> = InferProperties<S, []>
  • assertNever穷尽性检查惯例AGENTS.md中明确要求“封闭联合以assertNever收尾,可扩展联合落入文档化的default分支”(见 AGENTS.md)。

笔记点名了六张核心映射表(约 370 行核心类型代码):

映射表所在包仓库位置
ContentBlockMapdsh-llmpackages/llm/llm/src/types.ts
MessageSourceMapdsh-llmpackages/llm/llm/src/types.ts
FinishReasonMapdsh-llmpackages/llm/llm/src/types.ts
TurnTriggerMapdsh-session同属会话事件类型定义域
TurnEndReasonMapdsh-sessionpackages/core/session/src/types.ts
SessionEventMapdsh-sessionpackages/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 SessionEventMapexport type ContentBlock = ContentBlockMap[ContentBlockType](见 packages/llm/llm/src/types.ts)。FinishReasonMap则允许各 LLM 适配器扩展供应商专属的结束原因(stoptool-callsmax-tokensabortederror等)。

二、问题本质:编译期模式在运行时留下的空白

merge-extensible-map只存在于编译期。类型在运行时被完全擦除:仓库中不存在任何 Schema 对象,无法用它校验入站值、解析不可信输入或在运行时枚举词表。

会话持久化契约(见已实现笔记 .agents/notes/implemented/architecture/2026-06-14-session-persistence.md)由此暴露两个具体后果:

  1. 持久化把event.data当作不透明 JSON。JSONL/SQLite 后端对每个事件原样JSON.stringify/JSON.parse,唯一的运行时防线是isJsonValue——它只做往返可序列化性检查(拒绝 BigInt、函数、循环引用、非有限数值等),而非结构校验。其实现位于 packages/core/session/src/json.ts,配套测试 packages/core/session/tests/json.spec.ts 覆盖了-0NaNBigInt、稀疏数组、伪造原型对象等边界。后果是:一个“损坏但仍是合法 JSON”的事件数据(字段类型错误、字段缺失)会静默往返,直到某个消费方的switch遇到它才被发现——甚至永远不被发现。

  2. 插件新增变体没有运行时契约。插件通过声明合并向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-agentdsh-agent-loopdsh-shelldsh-llmdsh-sessiondsh-session-persistencedsh-system-promptdsh-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可合并扩展,对SessionEventswitch禁止使用assertNever,插件新增的变体是合法的未知值,应处理已知 case 后在default中放行;
  • defineToolInferArgsDSL(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 封闭头部 schemasteryC 全词表注册表
运行时结构校验仅头部/元数据事件数据 + 插件边界
改动规模全仓库词表重构
新依赖无(沿用 schemastery)Zod 需成为直接依赖(二选一)
插件扩展方式一行声明合并不变运行时register()+ 手工类型接线
assertNever穷尽性保持保持削弱

六、提案与验收标准

笔记的正式提案是:延期(Defer)

  • 如果确实需要持久化边界的运行时校验,方案 B(对封闭头部与元数据形状使用 schemastery)是在现有惯例内“相称的一步”;
  • 方案 C属于架构决策,必须通过其独立的实现 Agent Note 推进,并包含 Zod 与 schemastery 之间的选型。

验收标准两条:

  1. 方案 C 只能通过其自身的实现 Agent Note 推进,绝不允许作为持久化的副作用顺带实施
  2. 若采用方案 B,封闭的头部/元数据形状(JSONL 的isHeaderLine守卫及其同类)应改用 schemastery 校验,取代手写守卫,merge-extensible 映射表保持不变

七、风险

  • 延期的代价:事件data在持久化边界仍无结构校验——畸形但仍是 JSON 的数据会被消费方的switch延迟发现。这是现状成本,属于有意接受。
  • 若未来采用方案 C:易用性损失是真实的——一行声明合并变成“运行时注册 + 手工类型接线”,且assertNever静态穷尽性保证被削弱。

八、悬而未决的问题

笔记留下三个开放问题,供后续实现笔记回答:

  1. 库选型:若采用注册表,用仓库内已有且作为配置 Schema 库的schemastery,还是生态更丰富但目前仅是传递依赖的Zod?同时引入两个 Schema 库本身就是成本。
  2. 混合方案是否可行:能否保留编译期推断(让defineTool与插件 DX 存活),同时为每个变体增加可选的运行时 Schema,仅在持久化/线边界校验,而非每次进程内append都校验?
  3. 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 = 0SessionHeader元数据出日志、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.

项目地址:https://gitcode.com/gh_mirrors/de/deepseek-harness
点击查看免费下载

相关推荐

上一篇:WebUploader内存优化终极指南:大文件处理中的Blob对象管理策略
下一篇:3秒定位大型程序集:dnSpy类型搜索终极提速指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询