DeepSeek Harness (dsh) 项目深度分析报告
分析日期: 2026-08-13
仓库路径: ~/PycharmProjects/deepseek-harness
版本: 0.1.0-rc.5
目录
- 项目定位与总览
- 仓库布局与工程体系
- 核心架构:Cordis 全插件框架
- Core 包脊柱(8 个子包)
- Agent 循环引擎
- 会话系统:事件溯源架构
- 能力缝隙(Capability Seam)设计模式
- LLM 能力族
- Shell / FS / Web / Subagent 能力族
- Typert 类型图系统
- SDK / ACP 传输层
- 会话持久化双后端
- 测试与质量体系
- 防御性编程模式
- 架构评估与关键发现
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 插件框架。没有特权核心——每个功能都通过挂载插件实现:
- 插件向共享 Context 贡献服务、类型化事件和可逆 effect
- 注册即 effect:
ctx.effect()/ctx.on()返回 disposer,fiber 释放时自动撤回 - Profile + Bundle 组合:运行中的 dsh 是一个从有序层组合的插件树
3.2 Profile / Bundle 组合模型
| 概念 | 说明 |
|---|---|
| Profile | Harness home 中的命名组合,列出堆叠的 bundle,持有 out-of-tree 插件和 cordis.patch.yml |
| Bundle | Cordis 配置行 + 挂载代码的分发格式,在 package.json 的 dsh 字段下声明 |
| 层应用顺序 | 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 - Fork:
fork(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, variablesassemble(context)合并全局 + 作用域 provider,分离 tool parameters,应用规范排序,运行system-prompt/assemblewaterfall{{variable}}严格插值:未知/未定义引用直接抛错completesection:标记为complete: true的 section 在 waterfall 后恢复为唯一 prompt section
4.4 dsh-tools — 工具注册表和执行管道
核心类: ToolRuntime (Service, ctx.tools)
文件: packages/core/tools/src/index.ts
工具执行管道三阶段:
- prepare — 参数物化 +
tools/pre-executewaterfall (allow/deny/ask) + monotonic guards - dispatch —
tools/executewaterfall (around-dispatch) + 工具 body - finalize —
tools/post-executewaterfall (accept/block/replace) +finalizeContent+tools/result通知
关键特性:
- 三种 presentation mode:
native(默认),code(只发送run_code+ SDK,模型直接调用其他工具被拒绝为UNKNOWN_TOOL),both - 并发分类:
isConcurrencySafe区分parallelvsexclusive - 取消语义: caller signal + wrapper signal 融合,
ABORTEDvsABORTED_BEFORE_DISPATCH - 作用域级工具过滤:
restrict({ allow, deny })
4.5 dsh-agent — Agent 接口和注册表
核心类: AgentRegistry (Service, ctx.agents)
文件: packages/core/agent/src/index.ts
关键设计:
AsyncLocalStorageinitiator 作用域:同进程因果归属,不是授权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,支持 append 和 replace 操作。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
三步分离 (prepare → enter → announce) 允许 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:
- 为所有当前 Consumer 设计:tool schema、Loader、UI、transport、provider 特定行为留在 Consumer 或 provider 中,不让单一 Consumer 污染 service 契约
- 反模式:只有一个内部调用者的 public service 方法 → 应改为私有 capability closure
- request/spec split:
resolve()是 request→spec 的唯一转换点,consumer 永不直接执行 raw request - 能力校验 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/streamwaterfall:每次流式调用周围的重试/重放/路由拦截点
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 方法:
- 拦截
/api命名空间 - 解析描述符 → 验证参数 → 解析接收者上下文 → 解析参数 → 调用方法 → 验证结果
- 严格模式优先,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):
llm/stream— 模型请求前 flushtools/execute— 顶层工具调用前 flushagent/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()直接 SQLWHERE seq >= ?查询后缀 - 修订标识: storeIdentity + incarnation + revision 计数器
- 单事务原子性: session 行 + 所有事件行
12.6 SessionProjection 投影注册表
文件: packages/session/session-projection/src/index.ts
能力缝三方拆分:
- Domain plugins 贡献纯数学 (
init/apply/view) - 框架 拥有订阅、per-session watermark cache、变更通知
- 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 核心原则
- 真实实现优先于 mock: 只 mock 昂贵/非确定性边界 (LLM adapter, network, clock),下游全真实
- 验证世界而非自报告: e2e 断言重跑命令或外部读文件,不探查 agent 自身输出
- 测试真实入口路径: 通过 Loader 启动
cordis.yml而非手工ctx.plugin() - 源码面测试: vitest 通过 vite-tsconfig-paths 解析到
src,不经过lib/ - HMR 安全测试: 每个注册表必须有一个 (dispose fiber → 断言清理)
- 持久化契约测试:
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.md 和 docs/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 |