三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

DeepSeek Harness (dsh) 项目深度分析报告

DeepSeek Harness (dsh) 项目深度分析报告

DeepSeek Harness (dsh) 项目深度分析报告

分析日期: 2026-08-13
仓库路径: ~/PycharmProjects/deepseek-harness
版本: 0.1.0-rc.5


目录

  1. 项目定位与总览
  2. 仓库布局与工程体系
  3. 核心架构:Cordis 全插件框架
  4. Core 包脊柱(8 个子包)
  5. Agent 循环引擎
  6. 会话系统:事件溯源架构
  7. 能力缝隙(Capability Seam)设计模式
  8. LLM 能力族
  9. Shell / FS / Web / Subagent 能力族
  10. Typert 类型图系统
  11. SDK / ACP 传输层
  12. 会话持久化双后端
  13. 测试与质量体系
  14. 防御性编程模式
  15. 架构评估与关键发现

1. 项目定位与总览

DeepSeek Harness(dsh)是一个基于 vendored Cordis 框架的插件式 Agent 运行时外壳。核心理念是"一切皆插件"——模型适配器、工具注册表、会话日志、Agent 循环本身都是可从配置替换的插件。

维度 事实
包名 @deepseek-ai/dsh-root (monorepo root)
版本 0.1.0-rc.5 (pre-release, 无外部消费者)
包管理 pnpm@11.7.0 workspaces
Node 引擎 ^22.19.0 || >=24.0.0
模块系统 ESM ("type": "module") 全仓库
TypeScript strict: true, noImplicitAny, noUncheckedIndexedAccess, exactOptionalPropertyTypes
Lint oxlint 1.76.0
测试 vitest ^4.1.8, 逐文件 100% 覆盖门禁
构建 tsc (lib/types) + tsdown (runtime bundle)

项目处于 pre-release 阶段,明确声明"基础优先于兼容性"——重命名、重新打包、丢弃旧格式均可自由进行,不需要兼容 shim。


2. 仓库布局与工程体系

2.1 顶层目录

vendor/      vendored Cordis 源码(pin 指定 SHA)
packages/    @deepseek-ai/dsh-<pkg> 工作区(52 个分组, ~200+ 子包)
python/      Python SDK 和捆绑运行时
native/      node-addon-landlock-run (Linux 沙箱)
examples/    可运行的 cordis.yml 示例(agent-spine, CLI, ACP, JSON-RPC)
docs/        架构文档、生成目录、postmortem、cookbook
.agents/     Agent 工作流和 Agent Notes (notes/)
scripts/     仓库门控和生成器脚本
website/     VitePress 文档站点
apps/        CLI 和 Web 应用入口

2.2 包分组总览(52 组)

项目按 packages/<group>/<pkg>/ 两层组织,包名统一为 @deepseek-ai/dsh-<pkg>。主要分组:

分组 角色 子包数 发布预期
core/ 产品 API 脊柱 8 Product — stable API
llm/ LLM 能力族 10 Product
shell/ Bash 能力族 12 Product
fs/ 文件系统能力族 7 Product
subagent/ 子代理能力族 16 Product
session/ 持久会话数据平面 16 Product
web/ Web 搜索/抓取 6 Product
client/ Web-GUI 浏览器端 44 Product
host/ Web-GUI 宿主端 8 Product
sdk/ 进程外运行时 SDK 3 Product
api/ BFF 和 Typert 网关 2 Product
typert/ 类型图系统 4 Product
interaction/ 人机协作平面 5 Product
sandbox/ 进程限制缝 4 Product
hooks/ Claude Code/Codex hook 桥 3 Product
其他 工具/支持/基础设施 ~30+ Product / Support

2.3 TypeScript 项目布局

采用 host/client 双聚合体设计,因为 Cordis Context 在相同键下合并时 host 和 client 侧无法在同一个 TS 程序中共存:

配置文件 角色
tsconfig.base.json 基础配置 + ~230 条 paths 映射
tsconfig.base.client.json Client 编译器形态 (JSX, DOM lib)
tsconfig.json Solution 文件 (references host + client)
tsconfig.host.json Host 聚合 (~180 项目引用)
tsconfig.client.json Client 聚合 (~45 项目引用)

关键 tsconfig 设置:target: es2024, module: esnext, moduleResolution: bundler, composite: true, incremental: true。所有 paths 映射指向 src(源码面),构建产物在 lib/

2.4 关键脚本

pnpm run test           # vitest 单元测试
pnpm run test:coverage  # CI 覆盖门禁:逐文件 100%
pnpm run test:e2e       # 真实 API 测试(无 key 自跳过)
pnpm run test:snapshot  # 无 key 快照测试
pnpm run typecheck      # 先构建 host 面再检查
pnpm run lint           # oxlint
pnpm run build          # tsc + tsdown
pnpm run hygiene        # knip + publint + 约束 + 消费者检查
pnpm run doc-sync       # 文档门控(verify-md-links, verify-doc-budgets 等)
pnpm dsh --profile headless "task"  # 源码启动

3. 核心架构:Cordis 全插件框架

3.1 设计哲学

整个产品基于 vendored Cordis 插件框架。没有特权核心——每个功能都通过挂载插件实现:

  1. 插件向共享 Context 贡献服务、类型化事件和可逆 effect
  2. 注册即 effectctx.effect() / ctx.on() 返回 disposer,fiber 释放时自动撤回
  3. Profile + Bundle 组合:运行中的 dsh 是一个从有序层组合的插件树

3.2 Profile / Bundle 组合模型

概念 说明
Profile Harness home 中的命名组合,列出堆叠的 bundle,持有 out-of-tree 插件和 cordis.patch.yml
Bundle Cordis 配置行 + 挂载代码的分发格式,在 package.jsondsh 字段下声明
层应用顺序 profile 列出的 bundle → profile 的 cordis.patch.yml → home 级别 → --patch 覆盖

cordis.yml 是 YAML 数组,每个条目声明一个插件:

- id: <unique-id>name: '<package-name>'    # Service 类的 default export 或函数插件的 named exportconfig:key: value

3.3 两种插件导出形式

形式 导出 适用
Service 插件 default export extends Service 有状态的服务(AgentLoop, ToolRuntime 等)
函数插件 named export name/inject/Config/apply 无状态的功能插件(checkpoint-policy 等)

混合两种形式会导致 Loader 丢弃函数插件的命名空间(有 postmortem 记录此缺陷)。

3.4 事件系统

事件分为三种语义:

类型 行为 示例
emit 发射即忘,监听器同步执行 session/created, agent/status, tools/result
serial 串行执行,无 next() agent/turn-stopping
waterfall 瀑布链,监听器必须 next() 委托 agent/pre-step, agent/request, llm/stream, tools/pre-execute, tools/execute, tools/post-execute

3.5 Scope 作用域系统

dsh-scope 包提供不透明的 scope 身份标签,实现两层路由:

  • 事件沿链向上流动(子 → 父):父作用域监听器接收所有后代的事件
  • 注册视图沿链向下继承(父 → 子):父层注册的工具/section/variable 对子层可见
  • 近遮蔽远:同名注册项,最近的作用域胜出

这使得一个 preset 的 standing composition 能观察其下所有 agent,同时允许子 agent 覆盖特定配置。

3.6 Turn 流程

一个完整的交互周期:

turn/start → agent/pre-step(waterfall) → step/start → agent/request(waterfall)→ llm/stream(waterfall) → assistant/chunk* → assistant/message→ tool/call* → tools/pre-execute(waterfall) → tools/execute(waterfall)→ tools/post-execute(waterfall) → tool/result* → step/end→ agent/turn-stopping(serial) → turn/end
  • step = 一次模型请求 + 其工具调用
  • turn = 零或多个 step,消费一批 admitted input

4. Core 包脊柱(8 个子包)

4.1 包总览

ctx key 职责
core/scope 无 (library) 作用域注册原语
core/session ctx.sessions 只追加事件日志和内存存储
core/system-prompt ctx.systemPrompt Prompt section 和 tool-schema 组装
core/tools ctx.tools 作用域工具注册表和受保护执行管道
core/agent ctx.agents Agent 接口、注册表和事件词汇表
core/agent-loop ctx.agentLoop 默认 agent 驱动循环
core/agent-default-model ctx.agentDefaultModel 默认模型选择
core/agent-tool-presentation 无 (函数插件) 工具展示模式选择

4.2 dsh-session — 事件源会话日志

核心类: Session(纯类)+ SessionStore(Service, ctx.sessions

文件: packages/core/session/src/index.ts

export class Session {readonly header: SessionHeaderget id(): SessionIdget events(): readonly SessionEvent[]get seq(): numberget surface(): SessionSurfaceappend<T extends SessionEventType>(type: T, data: SessionEventMap[T], ...opts: []): SessionEvent<T>deriveMessages(): Message[]    // 增量缓存投影requestHeader(): EpochHeader | undefined
}

关键设计:

  • 事件溯源 (event-sourced):append-only 日志是唯一真相源,deriveMessages() 从 surface 投影 LLM 消息历史(增量缓存)
  • 深度冻结:所有事件数据在接收时 deepFreeze
  • Forkfork(source, boundary?, childSessionId?) 从稳定前缀创建子会话
  • 不变量:"模型可见即已记录"——任何到达模型请求的内容必须从日志可重构

4.3 dsh-system-prompt — Prompt 组装注册表

核心类: SystemPrompt (Service, ctx.systemPrompt)

文件: packages/core/system-prompt/src/index.ts

分层注册机制:

  • PromptLayer 实现 ScopeLayer,包含 sections, contexts, toolProviders, variables
  • assemble(context) 合并全局 + 作用域 provider,分离 tool parameters,应用规范排序,运行 system-prompt/assemble waterfall
  • {{variable}} 严格插值:未知/未定义引用直接抛错
  • complete section:标记为 complete: true 的 section 在 waterfall 后恢复为唯一 prompt section

4.4 dsh-tools — 工具注册表和执行管道

核心类: ToolRuntime (Service, ctx.tools)

文件: packages/core/tools/src/index.ts

工具执行管道三阶段:

  1. prepare — 参数物化 + tools/pre-execute waterfall (allow/deny/ask) + monotonic guards
  2. dispatchtools/execute waterfall (around-dispatch) + 工具 body
  3. finalizetools/post-execute waterfall (accept/block/replace) + finalizeContent + tools/result 通知

关键特性:

  • 三种 presentation mode: native(默认), code(只发送 run_code + SDK,模型直接调用其他工具被拒绝为 UNKNOWN_TOOL), both
  • 并发分类: isConcurrencySafe 区分 parallel vs exclusive
  • 取消语义: caller signal + wrapper signal 融合,ABORTED vs ABORTED_BEFORE_DISPATCH
  • 作用域级工具过滤: restrict({ allow, deny })

4.5 dsh-agent — Agent 接口和注册表

核心类: AgentRegistry (Service, ctx.agents)

文件: packages/core/agent/src/index.ts

关键设计:

  • AsyncLocalStorage initiator 作用域:同进程因果归属,不是授权
  • enter() + announce() 分离:先注册后通知,支持回滚安全
  • 回滚保护事务:setup 失败/commit 失败/owner disposal 回滚整个作用域,不发布任何 id

5. Agent 循环引擎

5.1 ReactLoopAgent — 核心驱动

文件: packages/core/agent-loop/src/agent.ts

Phase 状态机:

type Phase =| { kind: 'idle'; lastTurn: number }| { kind: 'maintenance'; abort: AbortController; lastTurn: number; wakeRequested: boolean }| { kind: 'running'; abort: AbortController; turn: number; step: number; wakeRequested: boolean }

驱动循环:kick()turn()preStep()step()

方法 职责
kick() 驱动入口,循环调用 turn() 直到无待处理工作
turn() 开启 turn 边界,内部循环执行 preStep()step()
preStep() 通过 inbox 认领消息,组装系统提示,agent/pre-step waterfall 决定是否进入步骤
step() 构建请求 → 流式调用 LLM → 组装 assistant 消息 → executeToolCalls
buildRequest() 构建冻结的 LLM 请求,agent/request waterfall 允许插件修改配置

5.2 AgentLoop — Service 插件

文件: packages/core/agent-loop/src/index.ts

export class AgentLoop extends Service implements AgentFactory {static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt']// FactoryOwnership 管理工厂级生命周期// 三方取消融合: caller / factory / owner
}
  • 构造函数注册 system-prompt 变量: provider, model, cwd
  • 配置驱动的 agent 在启动时通过 restoreOrCreateConfigured 创建或恢复

5.3 工具调用调度

文件: packages/core/agent-loop/src/tool-calls.ts

export async function executeToolCalls(ctx: Context, turn: number, step: number,toolCalls: ToolCallBlock[], signal: AbortSignal,acceptContext: (context: UserMessage) => void,
): Promise<{ concluded: boolean }>
  • executionMode (parallel/exclusive) 分组调度
  • 并行组使用有界滚动池 (maxParallelToolCalls)
  • 结果按模型顺序提交
  • 中断时为未启动调用记录合成错误结果

5.4 Inbox 消息路由

文件: packages/core/agent/src/inbox.ts

export class Inbox {get nextTurn(): readonly UserMessage[]    // 等待独立 turnget nextStep(): readonly UserMessage[]    // 当前 turn 的下一步消费claim(target: InboxTarget, turn: number): UserMessage[]append(target: InboxTarget, message: UserMessage): void
}

每次变更通过 session.append('agent/inbox/spliced', ...) 持久化记录。


6. 会话系统:事件溯源架构

6.1 SessionEvent 类型体系

SessionEventMap 是 merge-extensible 的类型映射,通过 declaration merging 扩展:

核心事件类型:

类别 事件
Turn/Step 边界 turn/start, turn/end, step/start, step/end
消息 user/message, assistant/message, assistant/chunk
工具 tool/call, tool/result
请求 request/header, request/context
插件扩展 agent/inbox/spliced, tool/code-dispatch-start, tool/code-dispatch
其他 todo/write, session/end-seed

关键不变量:SessionEventMap 成员默认 required-on-read——不知道其类型的构建拒绝日志,除非事件携带 ignorable: true。只有结构性格式变更才 bump SESSION_FORMAT_VERSION

6.2 Surface 管理

文件: packages/core/session/src/surface.ts

SurfaceManager 维护有序 surface,支持 appendreplace 操作。deriveMessages() 每个 surface 节点只投影一次(增量缓存)。

6.3 SessionStore 生命周期

create(id?, options?) → Session
prepare(id?, options?) → Session    // 预备但不通知
enter(session) → () => void         // 注册到 store
announce(session) → void            // 通知监听器
flush(session) → Promise<boolean>   // 持久化屏障
fork(source, boundary?, childId?) → Session

三步分离 (prepareenterannounce) 允许 agent 工厂将 session 生命周期折叠到一个有序复合 effect 中。


7. 能力缝隙(Capability Seam)设计模式

7.1 三角色架构

权威定义: docs/glossary.md

一个 capability seam(能力缝隙)包含三个角色:

角色 说明 形式约束
Service Definition 声明 ctx.<key> 和词汇类型的 Cordis Service 必须是抽象类或具体 registry,绝非 TS interface
Service Provider 一个或多个实现该接口的后端 各自独立成包
Consumer 注入该 service 的消费方 通常是与模型交互的 tool 包

核心原则:"替换一个 provider 可以改变整个产品行为"——例如将 filesystem provider 指向远程 sandbox,所有依赖它的工具(Bash, PTY, LSP)一起迁移。

7.2 三种 Registry 形态

形态 特征 示例
单 executor 一个 context 只能有一个 executor,duplicate 抛错 ShellExecutor, FileSystem
多 provider 按 name 注册多个 provider,dispatch 时选择 LlmRuntime, SubagentRuntime, WebRuntime
core 脊柱 无 provider 概念,本身就是实现 ctx.sessions, ctx.tools

7.3 Service Definition 设计原则

来自 packages/AGENTS.md

  1. 为所有当前 Consumer 设计:tool schema、Loader、UI、transport、provider 特定行为留在 Consumer 或 provider 中,不让单一 Consumer 污染 service 契约
  2. 反模式:只有一个内部调用者的 public service 方法 → 应改为私有 capability closure
  3. request/spec splitresolve() 是 request→spec 的唯一转换点,consumer 永不直接执行 raw request
  4. 能力校验 fail-loud:不支持的能力在 dispatch 前拒绝,不静默降级

7.4 全景 ctx 服务表(部分)

ctx key 角色 形态 说明
ctx.llm seam 多 provider LLM 流式调用
ctx.shell seam 单 executor Shell 执行
ctx.fs seam 单 executor 文件系统
ctx.web seam 多 provider Web 搜索/抓取
ctx.subagents seam 多 provider 子代理
ctx.sessions core 脊柱 会话存储
ctx.tools core 脊柱 工具注册表
ctx.systemPrompt core 脊柱 Prompt 组装
ctx.agents core 脊柱 Agent 注册表
ctx.agentLoop core 可替换 Agent 驱动循环

8. LLM 能力族

8.1 角色划分

文件: packages/llm/llm/src/index.ts

dsh-llm同时拥有 Service Definition 和 Consumer 角色——共享 streaming 词汇表。

角色 ctx key
llm/llm Service Definition + Consumer ctx.llm
llm/llm-deepseek Provider — DeepSeek 适配器 registers on ctx.llm
llm/llm-pi-ai Provider — 多 provider pi-ai registers on ctx.llm
llm/token-meter core — replay-aware token 测量 ctx.tokenMeter
llm/llm-retry 事件监听器 — 重试策略 listens agent/request-error

8.2 LlmRuntime Service

export class LlmRuntime extends Service {registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandleregisterConfigurableProviders(entries) // 声明可配置 provider 目录registerModelDiscovery(settingsNs, discover) // 端点模型发现prepareCall(config, signal): Promise<PreparedLlmCall> // 一次性 dispatch 句柄stream(options: GenerateOptions): AsyncIterable<StreamChunk>
}

关键设计:

  • prepareCall 防止 HMR 把一个 adapter 的能力结果与另一个 adapter 的 dispatch 混合
  • 全有或全无注册:duplicate provider 路由抛 DUPLICATE_ADAPTER
  • llm/stream waterfall:每次流式调用周围的重试/重放/路由拦截点

8.3 LlmAdapter 抽象

export abstract class LlmAdapter {abstract stream(options: GenerateOptions): AsyncIterable<StreamChunk>// 可选: providerInfo, providerRetryPolicy, listModels, resolveModel
}

8.4 流式协议

StreamChunk 是适配器发射的流式事件联合:

chunk 类型 说明
block-start 内容块开始
text-delta 文本增量
reasoning-delta 推理增量
tool-call-delta 工具调用增量
block-end 内容块结束
usage token 用量
finish 流结束

9. Shell / FS / Web / Subagent 能力族

9.1 Shell — request/spec split 模板

文件: packages/shell/shell/src/index.ts, types.ts

角色
shell/ Service Definition (ShellExecutor 抽象类)
bash-local/ Provider — 本地 subprocess
bash-sandbox/ Provider — 先应用 sandbox backend
pwsh-local/ Provider — Windows PowerShell
tool-bash/ Consumer — 模型可见的 Bash 工具

request/spec split(项目标志性设计模式):

// 调用者的请求(可选字段由 resolve() 填充)
interface ShellExecRequest {command: stringworkdir?: stringtimeoutMs?: numberstdoutMaxBytes?: number// ...
}// resolve() 产出的完全解析规格(所有必需字段已填充并 cap)
interface ShellExecSpec {command: stringworkdir: stringtimeoutMs: numberstdoutMaxBytes: number// ...
}abstract class ShellExecutor extends Service {abstract resolve(request: ShellExecRequest): ShellExecSpecabstract run(spec: ShellExecSpec): Promise<ShellRunResult>abstract start(spec: ShellExecSpec): ShellProcess
}

关键: resolve() 是 request→spec 的唯一转换点,把实现默认值和 cap 逻辑集中在 provider 内,consumer 永远拿不到未解析的 request 直接执行。

9.2 FS — 文件系统能力

文件: packages/fs/fs/src/index.ts

角色
fs/ Service Definition (FileSystem 抽象类)
fs-local/, fs-sandbox/, fs-e2b/ Providers
tool-fs/ Consumer
fs-observation-policy/ Companion — observed-state 检查

关键设计:

  • Branded 类型: FsTargetKey (不透明身份) 和 FsVersion (不透明版本 token)
  • editText 留在 Service Definition: version check、literal match、rewrite 共享一个临界区
  • Waterfall 事件: fs/write-intent (单 slot 决策), fs/edit-intent (单 slot 决策), fs/observed (emit 记录观察)

9.3 Web — 搜索/抓取

文件: packages/web/web/src/index.ts

Provider 选择语义(execution-time 解析,永不依赖注册顺序):

条件 结果
配置 id 已注册且 available() 该 provider
配置 id 未注册 WEB_PROVIDER_CONFIGURED_MISSING
配置 id 注册但不可用 WEB_PROVIDER_CONFIGURED_UNAVAILABLE
无配置 + 恰好一个 usable 该 provider
无配置 + 多个 usable WEB_PROVIDER_AMBIGUOUS
无配置 + 无 usable WEB_PROVIDER_UNAVAILABLE

环境变量 $DSH_WEB_SEARCH_PROVIDER/$DSH_WEB_FETCH_PROVIDER 等价于 config 字段,不是隐藏优先级链。

9.4 Subagent — 子代理

文件: packages/subagent/subagent/src/index.ts

角色
subagent/ Service Definition (SubagentRuntime)
subagent-spawn-in-process, subagent-fork-in-process, subagent-acp, subagent-codex, subagent-claude-code, subagent-dsh-sdk Providers (多种传输)
tool-subagent, tool-subagent-control, tool-ralph Consumers

关键设计:

  • one-shot vs continuable 分离: one-shot 返回 SubagentRun(一次性 foreground delegation),continuable 永不变成 SubagentRun
  • 能力校验: SubagentCapabilities 声明 start-time 特性(outputSchema/depthLimit/toolFilter/persona),service 在 dispatch 前校验
  • 方法存在即能力: prepareContinuable? 方法存在表示支持可继续子代理

10. Typert 类型图系统

Typert 是一个编译时类型分析 + 运行时反射系统,四个子包:

职责
typert/protocol 共享协议类型和装饰器 (@Remote(), @RemoteScope())
typert/generator TypeScript 编译器分析和代码生成
typert/registry 运行时注册表 (ctx.typert)
typert/loader 加载器

10.1 编译时流程

TypeScript AST → FaceModel (编译器无关分析模型)→ FaceModelEmitter → JS 运行时 (TYPERT 对象) + .d.ts 声明→ SchemaEmitter → Zod schema 定义→ (host face only) TYPERT_REMOTE + 声明合并到 TypertRemoteMap

TypeNodeModel 支持 17 种类型节点变体(keyword, literal, reference, union, intersection, array, tuple, object, function 等),不支持 operator, indexed-access, conditional, infer, mapped, template-literal。

10.2 运行时注册表

TypertRegistry 包含四个存储:

存储 内容
DescriptorStore (local) 生成的主机反射
RemoteStore (remotes) 消费者选择的 Remote contribution
LookupStore (lookups) Host 对象查找提供者
ContextStore (contexts) Host Context 解析器 + Client Context 绑定者

10.3 Gateway

文件: packages/api/gateway/src/index.ts

TypertGatewayService 通过 Connection RPC 拦截器暴露 Remote 方法:

  1. 拦截 /api 命名空间
  2. 解析描述符 → 验证参数 → 解析接收者上下文 → 解析参数 → 调用方法 → 验证结果
  3. 严格模式优先,SRC 回退(端点未被见过时从 binding 标记推导弱描述符)

11. SDK / ACP 传输层

11.1 JSON-RPC SDK

文件: packages/sdk/protocol/src/transport.ts, packages/sdk/client/src/client.ts, packages/sdk/server/src/server.ts

协议:换行分隔 JSON-RPC 2.0 over stdio

类型 方法 说明
请求 initialize 握手 (cwd, provider, model, maxTokens → serverInfo)
请求 session/prompt 发送提示 (sessionId, contentBlocks → messageId)
请求 shutdown 关闭
通知 session.event 会话日志事件流
通知 session.status agent 生命周期状态 (idle/running)
通知 subagent.started 子会话创建
通知 subagent.finished 子代理运行结束

HarnessClient 回收阶梯:EOF → SIGTERM → SIGKILL

DeepSeekHarness 高级封装实现 AsyncDisposable,惰性启动 + 握手,失败时回收并替换客户端。

11.2 ACP Server

文件: packages/acp/acp/src/index.ts

仅自动化 Agent Client Protocol server,不携带 UI/人机交互特性:

  • session/new: 创建 agent + session,验证 cwd 绝对路径
  • prompt: 文本提取 → followup → 等待 whole-agent idle
  • cancel: agent.cancel({kind: 'user'}) + settle
  • approval/request: one-shot 选项 (allow-once / reject-once),不推断持久授权

静默回收:取消所有 top-level agent → drain continuable subagents (child-first) → dispose 所有记录 → 失败聚合为 AggregateError


12. 会话持久化双后端

12.1 架构

后端 特征
JSONL session-persistence-jsonl 文件存储, Zstandard 压缩, 原子 link()+unlink()
SQLite session-persistence-sqlite 单数据库多会话, Schema v15, 事务原子性

共享 PersistenceCoordinator 编排器(~1360 行)。

12.2 写路径

session.append() → SessionWriteBehind (200ms 批处理窗口)→ PersistenceCoordinator (per-session 串行化)→ PersistenceBackend.appendBatch()

语义检查点(fail-closed):

  1. llm/stream — 模型请求前 flush
  2. tools/execute — 顶层工具调用前 flush
  3. agent/pre-step — 每步前 flush

12.3 崩溃恢复

加载时检测中断的 turn:

  • 保留最后 turn/end 之前的完整前缀
  • 之后的不完整行视为 torn tail,删除
  • 合成 step/end + turn/end {interrupted} 闭包
  • 为未启动工具调用合成 TOOL_OUTCOME_UNKNOWN 错误结果

12.4 JSONL 后端特性

  • Zstandard 压缩: 每帧独立可解码,readZstdPrefix() 扫描完整帧 + 尝试解码残缺尾帧
  • 原子物化: POSIX 用 link()+unlink()(非 rename(),防并发覆盖)
  • packed-chunks: 连续 assistant/chunk 打包为 text-chunks 行,减少 ~60% 日志大小
  • 修订标识: 基于 stat (dev/ino/size/mtimeNs/ctimeNs)

12.5 SQLite 后端特性

  • Schema v15,三张表: persistence_state, sessions, events
  • Seek-capable: loadStoredFrom() 直接 SQL WHERE seq >= ? 查询后缀
  • 修订标识: storeIdentity + incarnation + revision 计数器
  • 单事务原子性: session 行 + 所有事件行

12.6 SessionProjection 投影注册表

文件: packages/session/session-projection/src/index.ts

能力缝三方拆分:

  1. Domain plugins 贡献纯数学 (init/apply/view)
  2. 框架 拥有订阅、per-session watermark cache、变更通知
  3. Carriers 消费快照读面和变更 feed

ProjectionDefinition<K, S> — key + schema + 三个纯同步函数 + stateVersion


13. 测试与质量体系

13.1 测试分层

层级 命令 说明
Unit pnpm run test vitest, 包级 tests/**
Coverage gate pnpm run test:coverage packages/*/*/src 逐文件 100%
Real-API e2e pnpm run test:e2e 真实 provider API, 无 key 自跳过
Snapshot pnpm run test:snapshot 无 key 预期输出, transport/presentation/log
Web browser pnpm run test:web Chromium 重放 (Linux CI 门禁)

13.2 核心原则

  1. 真实实现优先于 mock: 只 mock 昂贵/非确定性边界 (LLM adapter, network, clock),下游全真实
  2. 验证世界而非自报告: e2e 断言重跑命令或外部读文件,不探查 agent 自身输出
  3. 测试真实入口路径: 通过 Loader 启动 cordis.yml 而非手工 ctx.plugin()
  4. 源码面测试: vitest 通过 vite-tsconfig-paths 解析到 src,不经过 lib/
  5. HMR 安全测试: 每个注册表必须有一个 (dispose fiber → 断言清理)
  6. 持久化契约测试: runPersistenceContract(name, make) 可复用的后端无关测试套件

13.3 非平凡行为变更要求

每个非平凡的模型或产品用户可见行为变更,必须在同一 PR 中通过真实可运行示例添加或更新无 key 快照。包测试、e2e-only 断言、mock-only fixture 不能替代组装的应用 transcript。

13.4 门控脚本

仓库有大量 verify-*gen-* 脚本形成机器可检查的 invariant:

脚本 职责
verify-md-links 检查 Markdown 交叉链接
verify-doc-budgets 文档字数预算门禁
verify-cordis-config 验证 cordis.yml 配置
verify-package-invariants 每个包的 runtime invariant
verify-export-jsdoc 导出 JSDoc 完整性
verify-type-equiv 类型声明漂移检测
gen-doc-graphs 生成 capability-seams.md 等
gen-cordis-catalog 生成 Cordis 目录
gen-tool-catalog 生成工具目录

14. 防御性编程模式

来自 docs/defensive-patterns.md,7 条硬性规则:

# 规则 说明
1 独立报告正交结果 不要将一个标志嵌套在另一个的分支中 (timeout AND exit 0)
2 双边遵守公共契约 实现接收多种表示时先归一化再返回
3 异步状态不是同步状态 agent.followup() 无每消息完成,不要将 whenIdle() 归因于一个 followup
4 Dispose 必须到达静默 await children exit (kill → await done),先关 listener 再 kill
5 在分发器中包含回调异常 try/catch 包裹 dispatch loop,一个坏订阅者不破坏核心生命周期
6 不给不可信输出环境变量或可预测路径 擦除 *KEY*/*SECRET*/*TOKEN*/*PASSWORD*,私有 0700 目录 + 随机名
7 取消链接 link 形路径 lstatSync().isSymbolicLink() 然后 unlinkSync,不要 rmSync 符号链接

15. 架构评估与关键发现

15.1 架构优势

全插件架构的高度可组合性:从 LLM provider 到文件系统再到 agent 循环本身,一切皆可替换。cordis.yml 的声明式组装让不同部署形态(headless CLI、Web GUI、ACP 自动化)共享同一套核心包,仅通过 bundle 组合差异化。

事件溯源的强一致性保证:会话日志是唯一真相源,deriveMessages() 投影保证"模型可见即已记录"的运行时不变量。Fork、resume、telemetry、persistence 全部从同一事件流派生,消除了状态同步问题。

能力缝隙的清晰角色分离:三角色设计(Definition / Provider / Consumer)使每个能力的替换边界明确。request/spec split 模式将实现默认值集中在 provider 内,consumer 永不接触未解析的请求。

工程化的质量门控:逐文件 100% 覆盖门禁 + 机器可检查的 invariant 脚本体系(~20 个 verify-/gen- 脚本)+ 双 TypeScript 聚合体设计,形成了严格的工程纪律。

15.2 设计亮点

Scope 作用域系统:不透明身份标签 + 双向路由(事件向上、注册向下)的设计,优雅地解决了多 agent 场景下的隔离与继承问题。

Typert 类型图系统:编译时 AST 分析生成运行时反射 + Zod schema + Remote 描述符,为进程间 RPC 提供了端到端类型安全,无需手写 schema。

崩溃恢复机制:JSONL 的 Zstandard 帧独立解码 + SQLite 的事务原子性 + 语义检查点策略,形成了多层次的崩溃恢复保障。合成 step/end + turn/end {interrupted} + tool error result 确保恢复后的日志一致。

Code Mode:工具展示的三种模式(native/code/both)允许模型通过 run_code 传输工具调用,在特定场景下减少 token 消耗。

15.3 复杂度与挑战

包数量庞大:52 个分组、200+ 子包的 monorepo 带来了显著的认知负荷。虽然 packages/README.mddocs/capability-seams.md 提供了导航,但新贡献者需要理解 Cordis 框架、能力缝隙模式、Scope 系统、Typert 系统等多个概念才能有效工作。

双 TypeScript 聚合体:host/client 分裂是 Cordis Context 合并语义的必然结果,但增加了构建和类型检查的复杂度。

pre-release 阶段的格式不稳定性:SQLite Schema v15、SESSION_FORMAT_VERSION: 0 明确声明无兼容承诺,后端拒绝旧格式。这对早期开发是正确的(基础优先于兼容性),但意味着任何持久化数据在版本升级时可能丢失。

严格的文档纪律:wordcount budget、doc-tier 分层、one-home-per-fact、slop checklist 等规则形成了高标准的文档治理,但也增加了非代码变更的维护成本。每个非平凡变更必须包含 Agent Note 的要求确保了决策可追溯。

15.4 关键架构决策汇总

决策 理由 代价
全插件架构(无特权核心) 最大可组合性,每个部分可替换 认知负荷高,需理解 Cordis
事件溯源会话日志 强一致性,唯一真相源 写入开销(即使有 write-behind)
能力缝隙三角色 替换边界明确,provider 互换驱动产品变化 包数量膨胀
request/spec split 实现默认值集中,consumer 不接触 raw request 增加 resolve() 步骤
Host/Client 双聚合体 解决 Cordis Context 合并冲突 构建复杂度
Typert 编译时生成 端到端类型安全,无需手写 schema 编译器依赖,类型支持有限
逐文件 100% 覆盖 最高质量保证 测试编写成本高
pre-release 格式不兼容 基础优先,不背技术债 升级时数据丢失

附录:关键文件索引

领域 文件路径
架构总览 docs/architecture.md
术语表 docs/glossary.md
能力缝隙全景 docs/capability-seams.md (生成)
测试策略 docs/testing.md
防御性模式 docs/defensive-patterns.md
包级规则 packages/AGENTS.md
文档标准 docs/AGENTS.md
Agent 循环 packages/core/agent-loop/src/agent.ts
Agent 注册表 packages/core/agent/src/index.ts
Session 核心 packages/core/session/src/index.ts
System Prompt packages/core/system-prompt/src/index.ts
Tools 执行管道 packages/core/tools/src/index.ts
Scope 系统 packages/core/scope/src/index.ts
LLM Runtime packages/llm/llm/src/index.ts
Shell Executor packages/shell/shell/src/index.ts
FileSystem packages/fs/fs/src/index.ts
Web Runtime packages/web/web/src/index.ts
Subagent Runtime packages/subagent/subagent/src/index.ts
持久化协调器 packages/session/session-persistence/src/coordinator.ts
JSONL 后端 packages/session/session-persistence-jsonl/src/index.ts
SQLite 后端 packages/session/session-persistence-sqlite/src/index.ts
Typert 生成器 packages/typert/generator/src/emitter.ts
Typert 注册表 packages/typert/registry/src/service.ts
API Gateway packages/api/gateway/src/index.ts
SDK 协议 packages/sdk/protocol/src/transport.ts
SDK 客户端 packages/sdk/client/src/client.ts
ACP Server packages/acp/acp/src/index.ts
← 返回列表