From ec47f2e40fdfbff3809ad1db874f9d5df9db33c4 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Wed, 15 Jul 2026 23:01:43 -0700 Subject: [PATCH] =?UTF-8?q?docs(i18n):=20core-docs=20batch=20=E2=80=94=20f?= =?UTF-8?q?ive=20bilingual=20pairs=20via=20the=20committed=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit architecture / cordis-primer / defensive-patterns / glossary / testing 五篇核心文档配对,译文由进仓流水线产出(committed renderer + 金标 few-shot + 严格 XML 协议),并经二遍校验(逐句对照原文复述核查 + 注入 architecture/glossary 仓库上下文的一致性修复)。五篇生成文档 (agent-lifecycle、capability-seams、event-producer-consumer、 graph-atlas、tool-execution-pipeline 均为 gen-doc-graphs 产物)列入 排除清单——手写译文会在再生成时失效,与既有生成目录排除策略一致。 --- docs/architecture.i18n.yaml | 6 + docs/architecture.md | 2 + docs/architecture.zh.md | 171 ++++++++++++++++++++++ docs/cordis-primer.i18n.yaml | 6 + docs/cordis-primer.md | 2 + docs/cordis-primer.zh.md | 44 ++++++ docs/defensive-patterns.i18n.yaml | 6 + docs/defensive-patterns.md | 2 + docs/defensive-patterns.zh.md | 29 ++++ docs/glossary.i18n.yaml | 6 + docs/glossary.md | 2 + docs/glossary.zh.md | 19 +++ docs/testing.i18n.yaml | 6 + docs/testing.md | 2 + docs/testing.zh.md | 35 +++++ scripts/translation-pairing.manifest.json | 10 ++ 16 files changed, 348 insertions(+) create mode 100644 docs/architecture.i18n.yaml create mode 100644 docs/architecture.zh.md create mode 100644 docs/cordis-primer.i18n.yaml create mode 100644 docs/cordis-primer.zh.md create mode 100644 docs/defensive-patterns.i18n.yaml create mode 100644 docs/defensive-patterns.zh.md create mode 100644 docs/glossary.i18n.yaml create mode 100644 docs/glossary.zh.md create mode 100644 docs/testing.i18n.yaml create mode 100644 docs/testing.zh.md diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml new file mode 100644 index 0000000000..9ff56c00ef --- /dev/null +++ b/docs/architecture.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +architecture.md: 32b9700b9aece988985ff932e597b537872f12c3 +architecture.zh.md: 1adb0111fb67c6e252153e2500732a320de44523 diff --git a/docs/architecture.md b/docs/architecture.md index 4049049e25..32b9700b9a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,5 +1,7 @@ # DeepSeek Harness Architecture +English | [中文](architecture.zh.md) + The **DeepSeek Harness SDK** builds agent harnesses on Cordis. The principle is simple: **everything is a plugin**. The shipped loop is one plugin, not a privileged kernel. ## Overview diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md new file mode 100644 index 0000000000..1adb0111fb --- /dev/null +++ b/docs/architecture.zh.md @@ -0,0 +1,171 @@ +# DeepSeek Harness 架构 + +[English](architecture.md) | 中文 + +**DeepSeek Harness SDK** 基于 Cordis 构建 agent harness(智能体框架)。原则很简单:**一切皆插件**。内置的循环只是一个插件,不是特权内核。 + +## 概览 + +一个 harness 就是一个 [Cordis](cordis-primer.md) 上下文。各包(package)贡献服务键、类型化事件和可 dispose(资源释放)的注册:服务暴露稳定的调用(`ctx.llm`、`ctx.tools`、`ctx.sessions`),事件提供拦截与通知(`agent/request`、`tools/pre-execute`、`session/event`),注册则安装提示词段、工具、提供方、适配器或监听器。 + +`packages/core/` 组织了默认的 agent 流程;周边能力同样是一等的 Cordis 插件。 + +### 默认服务 + +| ctx 键 | 包 | 职责 | +|---|---|---| +| — | [`dsh-scope`](../packages/core/scope/README.md) | 作用域上下文注册原语(库) | +| `ctx.sessions` | `dsh-session` | 内存中事件溯源的会话 | +| `ctx.systemPrompt` | `dsh-system-prompt` | 有序提示词段、工具 schema 与提示词变量 | +| `ctx.tools` | `dsh-tools` | 工具注册表与[执行流水线](tool-execution-pipeline.md) | +| `ctx.agents` | `dsh-agent` | 活跃 agent 注册表、公开 `Agent` 句柄、`agent/*` 事件 | +| `ctx.agentLoop` | `dsh-agent-loop` | 内置 `ReactLoopAgent` 驱动器 | + +### 能力服务 + +| ctx 键 | 包族 | 职责 | +|---|---|---| +| `ctx.llm` | [`llm/`](../packages/llm/README.md) | 适配器注册表与流式模型调用 | +| `ctx.bash` | [`bash/`](../packages/bash/README.md) | 前台/后台命令执行 | +| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | 同世界进程隔离(argv 包装、逐调用策略) | +| `ctx.codeRuntime` | [`code-runtime/`](../packages/code-runtime/README.md) | 模型编写的程序执行 | +| `ctx.fs` | [`fs/`](../packages/fs/README.md) | 文件系统提供方原语与策略事件 | +| `ctx.skills` | [`skill/`](../packages/skill/README.md) | skill(技能)提供方注册表与渐进式披露 | +| `ctx.web` | [`web/`](../packages/web/README.md) | 搜索/抓取提供方注册表 | +| `ctx.compact` | [`compact/`](../packages/compact/README.md) | 会话日志压缩(compaction) | +| `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | 命名委托提供方 | +| `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | 脚本驱动的多 agent 编排 | +| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | 会话日志的持久化存储 | +| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | 活跃优先的逻辑语料库与精确事件读取 | + +## 事件 + +事件构成服务扩展 API;详见完整的[事件目录](cordis-catalog/events.md)与[生产者/消费方映射](event-producer-consumer.md)。 + +### 事件域 + +- **会话事件**是持久的、可回放的事实。轮次与步骤边界、用户输入、助手输出、工具调用、工具结果、steering(中途引导)、压缩记录以及工具拥有的持久事实追加到会话日志,并流经 `session/event`。 +- **Agent 事件**携带活跃的 `Agent` 句柄,用于状态、诊断、prompt 准入、调用配置塑形、结果校验与续行策略。 +- **能力事件**归属于拥有该动作的 seam。`tools/*`、`llm/*`、`system-prompt/*`、`fs/*` 与 `subagent/*` 让策略和适配器无需导入循环即可接入。 + +### 拦截语义 + +waterfall(瀑布式事件)的行为类似 around 中间件:监听器通过调用 `next()` 委托下游;不调用 `next()` 直接返回即为否决或接管。完整规则见 [Cordis waterfall 语义](cordis-primer.md#cordis-waterfall-semantics)。 + +## 默认循环生命周期 + +内置循环消耗工作队列、组装请求、流式接收模型回答、执行工具、应用续行策略并持久化检查点。每一个暂停点都是一个服务调用或事件,可供插件介入。 + +**会话**是一个 agent 的仅追加事件日志。**轮次(turn)**消耗一批排队消息,运行到模型不再请求工具且没有插件要求续行为止。**步骤(step)**是一次模型请求加上该响应引发的工具执行。下面的流程中([时序图伴侣文档](agent-lifecycle.md)),带引号的名称是持久化的会话事件,事件名称是扩展点。 + +### 轮次流程 + +```text +prepare private session + agent.ctx -> await unpublished setup + -> enter session + agent -> session/created -> agent/created + -> enable driving -> agent/session-start(source) -> start driver +forever: + wait for queued messages + emit agent/status(running) + TURN: + 'turn/start' + each queued message -> agent/prompt-submit + allowed prompt -> 'user/message' plus injected context + every prompt blocked -> 'turn/end'(rejected) + STEP loop: + drain steering + assemble system prompt and tool schemas + agent/session-prefix (first step) + agent/pre-step + 'step/start' + snapshot the derived messages (the reconstruction boundary) + agent/request (config only) -> log request/header -> llm/stream (frozen) + 'assistant/chunk' + agent/step-result + 'assistant/message' + each tool call: + 'tool/call' + tools/pre-execute -> monotonic guards -> tools/execute -> tools/post-execute -> tools/result + 'tool/result' + append post-tool context and steering + 'step/end' + agent/turn-continuation + agent/turn-stop (terminal policy) + stop unless tools or continuation policy ask for another step + 'turn/end' + checkpoint persistence and notify idle/running status +``` + +循环每步骤渲染一次 prompt 组装。插件贡献有序段、工具 schema 与 `{{name}}` 变量;未知或无值的引用会使轮次失败,而非带着空洞发送。`dsh-system-prompt` 拥有 harness 身份与默认部署人格;agent 作用域的人格可以遮蔽默认值。循环提供 `model` 和 `cwd`。见 [prompt 所有权 RFC](rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md)。 + +Post-tool 上下文在所有工具结果之后落入,以保持 tool-call/result 的邻接稳定。steering 在步骤之间排空;轮次结束后的普通剩余 steering 作为输入重新入队。终止性的 `agent/turn-stop` 是显式例外:它在普通续行与 steering 折叠之后运行,然后在轮次关闭和刷新期间保持权威,因此这些后续监听器产生的 steering 被丢弃而非成为新的步骤或轮次;普通排队的 prompt 则被保留。 + +### 失败边界 + +轮次是容错边界。抛出异常的监听器、适配器错误结束或失败的步骤会以错误原因结束当前轮次,并通过 `agent/error` 报告实时诊断;它不会杀死驱动循环。`cancel()` 清除排队与 steering 工作,在可能时中止活跃的模型/工具边界,并记录相应的轮次结束。dispose 停止循环、等待静默、注销 agent,并让服务 disposer 排空。 + +每个会话事件都被轮次包围。重新加载崩溃的会话时,系统保留中断的尾部并以合成的 `interrupted` 轮次结束关闭它。持久化轮次已关闭后的失败仅通过 `agent/error` 报告,因为已没有安全的轮次内位置。轮次以一个 `TurnEndReason` 结束(`completed`、`aborted`、`error`、`disposed`、`max-tokens`、`rejected` 或 `interrupted`);各变体的语义见 [session.md § TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap)。 + +### Agent 句柄 + +`ctx.agents` 拥有活跃 agent 并返回 `AgentHandle { agent, dispose() }`。`Agent` 是其他插件驱动的 API:`send()` 入队工作,`steer()` 注入轮次中内容,`inject()` 追加上下文并在空闲时开启一次性注入轮次,`cancel()` 是公开的停止原语,`whenIdle()` 观察静默状态。调用方 fiber 与具体工厂提供方在结构上共同拥有编程式生命周期;消费方句柄是唯一的非结构性拆卸能力,且每个所有者到达同一个被 await 的 disposer。 + +### Agent 作用域 + +每个活跃 agent 拥有一个作用域化的 `agent.ctx`。其注册遮蔽同名全局注册,只接收该 agent 的派发,并随 agent 一起解除。`CreateAgentOptions.setup(agentCtx)` 在发布前组合作用域。[语义门禁 RFC](rfc/implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md) 定义了类型化解析器,从合并的 `Events` 签名与 `scopeTarget` 派生载体检查,消除了手写事件表。见 [agent 作用域 RFC](rfc/implemented/architecture/2026-07-08-agent-scope-contexts.md);subagent 组合控制另行记录于[此](rfc/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md)。 + +## 状态 + +### 会话日志 + +会话日志是真源。`deriveMessages()` 将会话事件投影为发送给模型的 `Message[]`;原始 `assistant/chunk` 事件留在日志中用于回放和 UI 保真。回放、fork、恢复、transcript(文本记录)渲染、遥测和持久化都从同一事件流派生。 + +**模型可见 ⟺ 已记录**:日志能重建每次请求——`step/start` 处的消息前置 header 的会话前缀,header 通过折叠 `request/header` 得出——开发不变式对此做断言([可重建性 RFC](rfc/implemented/architecture/2026-07-05-reconstructable-requests.md))。 + +持久性是插件关注点。持久化后端缓冲同步的 `session/event` 通知,循环在轮次结束检查点完成后才继续。`SessionPersistence` seam 直接存储 `SessionEvent`,元数据在 `SessionHeader` 中;JSONL 与 SQLite 共享同一套契约测试。 + +### 模型内容 + +消息是类型化内容块(`text`、`reasoning`、`tool-call`、`tool-result`)的数组。联合类型派生自可合并扩展的 `ContentBlockMap`;同一模式也用于 `MessageSource`、`FinishReason`、`TurnTrigger` 与 `TurnEndReason`。新的块类型需要跨适配器、UI 桥接、压缩计价与持久化协调,因此块类型仍是仓库级契约。 + +流式输出是原始分片协议(从 `block-start` 到 `finish`),`BlockAssembler` 是共享的 chunk 到 block 组装器。循环在组装分片以供派发的同时记录原始 chunk。`LlmAdapter` 是提供方 seam:继承它、实现 `stream()`、用 `ctx.llm.registerAdapter(models, adapter)` 注册。StreamChunk 约定见 [llm-streaming.md](core-data-structures/llm-streaming.md)。 + +## 扩展与组合 + +### 能力模式 + +一个可替换的能力通常拆分为**接口 / 实现 / 消费方**:接口拥有其 `ctx` 键与事件,实现注册后端,消费方通过工具或 prompt 暴露模型行为。Bash 是参考实现;[能力图](capability-seams.md)展示了每个族。 + +部分 seam 有意偏离模板。LLM(大语言模型)将接口与消费方词汇放在一起,因为适配器就是实现。文件系统在提供方原语周围添加策略门禁。Web 是一个服务加搜索/抓取两个提供方注册表,因此提供方替换不会重命名模型工具。skill 与 subagent 使用命名提供方注册表;本地 skill 扫描项目/用户根目录,其他提供方可以在不改动注册表/工具的情况下添加嵌入式或远程目录。subagent 可以全新 spawn、从父级已完成轮次的前缀 fork,或使用 ACP 子进程([subagent.md](core-data-structures/subagent.md))。 + +### Bundle 与应用 + +`dsh-agent-spine-demo` 是默认的组合 bundle:一个插件加载共享主干([README](../packages/examples/agent-spine-demo/README.md))。应用包将其与前端入口和启动 `bin` 组合:`dsh-stdio-demo` 用于终端 REPL,`dsh-acp-demo` 用于基于 JSON-RPC stdio 的 ACP(无 stdout logger)([ui/](../packages/ui/README.md))。`dsh-jsonrpc-agent` 则启动外部 `cordis.yml`;Python SDK 在未设置显式配置通道时注入包默认值,并通过行分隔的 stdio JSON-RPC 驱动 `dsh-jsonrpc`([Python SDK](../python/README.md))。一个部署就是一片薄薄的 `cordis.yml` 叶子:可替换的后端、一个应用入口和可选的产品工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[关系图索引](graph-atlas.md))。 + +### 新行为的归属 + +新行为应接入已记录的扩展点;修改内置循环需要同步更新本映射。 + +| 目标 | 机制 | +|---|---| +| 添加模型提供方 | 在 `ctx.llm` 上注册适配器 | +| 添加面向模型的能力 | 在 `ctx.tools` 上注册工具;schema 流入 prompt 组装 | +| 添加命令执行 | 实现并注册 `ctx.bash` 后端 | +| 添加文件系统访问或策略 | 实现 `ctx.fs` 提供方或监听 `fs/*` 策略事件 | +| 隔离 spawn 的进程 | 一个 `ctx.sandbox` 后端;消费方在 spawn 前包装 argv | +| 拦截 prompt、请求、工具使用或续行 | 监听相关的 `agent/*` 或 `tools/*` waterfall;使用串行 `agent/turn-stop` 实现单调终止 | +| 添加历史之外的会话稳定请求前缀 | 在 `agent/session-prefix` 上组合,每个循环实例一次;记录在请求 header 上 | +| 添加 UI 或编辑器集成 | 驱动 `ctx.agents` 并从 `session/event` 渲染 | +| 添加持久化会话状态 | 添加 `SessionEventMap` 成员并从日志渲染/回放 | +| fork 活跃会话 | 使用 `ctx.sessions.fork(source, boundary?, childSessionId?)` | +| 将工具、prompt 段或监听器限定到单个 agent | 通过该 agent 的 `agent.ctx` 注册(见 Agent 作用域) | + +[扩展实操手册(cookbook)](cookbook/extension-cookbook.md)提供插件骨架与功能到 seam 的映射;分步指南覆盖[包](cookbook/adding-a-package.md)、[工具](cookbook/adding-a-tool.md)、[LLM 适配器](cookbook/adding-an-llm-adapter.md)与[vendor 包](cookbook/adding-a-vendored-package.md)。 + +## 快速参考 +- 领域术语见[术语表](glossary.md) +- 类型定义见 [core-data-structures/](core-data-structures/core.md) +- 精确的事件与服务签名见[事件目录](cordis-catalog/events.md) +- [服务目录](cordis-catalog/services.md) +- 包契约见[包映射](../packages/README.md) +- [RFC](rfc/README.md) diff --git a/docs/cordis-primer.i18n.yaml b/docs/cordis-primer.i18n.yaml new file mode 100644 index 0000000000..405a377ed1 --- /dev/null +++ b/docs/cordis-primer.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +cordis-primer.md: 39d3d97b9ac43fec50cb0c832af449fc8bc6232f +cordis-primer.zh.md: 4915f665cae51b44f89190d43d147e5cda0df146 diff --git a/docs/cordis-primer.md b/docs/cordis-primer.md index d8e92dcb67..39d3d97b9a 100644 --- a/docs/cordis-primer.md +++ b/docs/cordis-primer.md @@ -1,5 +1,7 @@ # Cordis Primer +English | [中文](cordis-primer.zh.md) + Cordis is the vendored plugin framework underneath the DeepSeek Harness SDK. This primer teaches the Cordis ideas a harness plugin author needs before reading the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs. The vendored source and sync procedure live in [vendor/README.md](../vendor/README.md). ## Cordis In Five Ideas diff --git a/docs/cordis-primer.zh.md b/docs/cordis-primer.zh.md new file mode 100644 index 0000000000..4915f665ca --- /dev/null +++ b/docs/cordis-primer.zh.md @@ -0,0 +1,44 @@ +# Cordis 入门 + +[English](cordis-primer.md) | 中文 + +Cordis 是 DeepSeek Harness SDK 底层以 vendor 方式引入的插件框架。本入门文档讲解 harness 插件作者在阅读生成的[事件](cordis-catalog/events.md)与[服务](cordis-catalog/services.md)目录之前需要了解的 Cordis 核心概念。vendor 源码与同步流程见 [vendor/README.md](../vendor/README.md)。 + +## Cordis 五大理念 + +- **插件是实现了 Service 的对象。** 它可以是一个带有可选 `inject` 和 `apply(ctx)` 字段的函数,也可以是一个 `Service` 子类,其生命周期由 Cordis 挂载到当前上下文中。 +- **上下文是服务的注册表。** 一个服务在上下文中声明一个稳定的 `ctx.`(如 `ctx.tools`、`ctx.llm`、`ctx.sessions`);其他插件通过 key 查找服务,而非导入具体实现。 +- **通过 `inject` 声明服务依赖。** 插件声明所需的服务后,会等待这些服务就绪;加载顺序通过服务依赖表达,而非手动编排启动序列。 +- **类型化事件用于通信。** 服务通过 TypeScript 声明合并定义事件名,然后以 `emit`、`waterfall`(瀑布式事件)、`parallel` 或 `serial` 方式分发,分别对应监听者观察、包装、并行扇出或按序执行。 +- **注册是可逆的副作用。** 提示词片段、工具 schema、适配器、提供方和监听器通过 `ctx.effect()` 或 `ctx.on()` 安装,因此重载和拆卸能可预测地回退它们。 + +## 分发模式 + +每个事件具有以下分发模式之一,且只能通过对应的方法分发。 + +| 模式 | 是否 await? | 分发顺序 | 是否有返回值? | +|---|---|---|---| +| `emit` | 否 | 监听器按注册顺序观察 | 否 | +| `waterfall` | 否 | 监听器按注册顺序观察 | 是 | +| `parallel` | 是 | 所有监听器并行观察事件 | 否 | +| `serial` | 是 | 监听器按注册顺序观察 | 是 | + +分发模式是事件公开契约的一部分。新的 harness 事件通过 `@mode` 标签记录它,以便生成的目录能将声明与分发站点进行交叉校验。 + +## Cordis Waterfall 语义 + +`ctx.waterfall` 是环绕中间件。监听器接收 `(...args, next)`。调用 `next()` 将可能经过包装的结果委托给下一个服务;不调用 `next()` 直接返回则短路。值通过 `next()` 的返回值向下传播。 + +协作式监听器通常修改一个共享的请求或决策对象,然后委托。监听器也可以选择完全替换结果,下游监听器只会看到替换后的结果。仅当监听器必须在普通注册之前运行时才使用 `prepend: true`。 + +对于单决策事件,短路是设计意图。策略监听器在拥有决策权时可以不调用 `next()` 直接返回,而仅做标注或观察的监听器必须委托。 + +## Loader 配置 + +`@cordisjs/plugin-include` 将 `!!js` 解析为表达式节点,但 Loader 仅在挂载插件前对条目的 `config` 进行插值。条目元数据(`id`、`name`、`group`、`disabled`、`inject`、`intercept` 和 `isolate`)保持字面值;因此 `disabled: !!js ...` 是一个真值对象,总是会禁用该条目。当需要根据环境选择挂载哪些插件时,请使用显式的配置覆盖。 + +## 实践规则 + +将行为封装到插件中:工具流水线事件属于 `ctx.tools`,模型流式输出属于 `ctx.llm`,实时 agent 协调属于 `ctx.agents`。拦截和策略优先使用事件;直接能力调用优先使用服务方法。 + +每个注册都应有对应的 dispose(资源释放)器:要么从 `ctx.effect()` 返回一个,要么使用 Cordis 提供的辅助函数自动处理。如果拆卸顺序有要求,请将相关工作放在同一个 effect 中,以确保 dispose 按预期顺序回退。 diff --git a/docs/defensive-patterns.i18n.yaml b/docs/defensive-patterns.i18n.yaml new file mode 100644 index 0000000000..03765e44ae --- /dev/null +++ b/docs/defensive-patterns.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +defensive-patterns.md: fda0be0d2d3b7fa099162123b3d219673eebd07d +defensive-patterns.zh.md: 60d7389db50c30e2b85fd88b320f04b43e22d84d diff --git a/docs/defensive-patterns.md b/docs/defensive-patterns.md index cf30072094..fda0be0d2d 100644 --- a/docs/defensive-patterns.md +++ b/docs/defensive-patterns.md @@ -1,5 +1,7 @@ # Defensive patterns +English | [中文](defensive-patterns.zh.md) + Hard-won bug-class rules: each pattern below is a class of defect that actually shipped or nearly shipped here, stated as the rule that prevents its recurrence. Read this before writing lifecycle, concurrency, subprocess, or teardown code. Test-tier counterparts (real entry path, world-verification, resource ownership) are in [testing.md](testing.md). ## Report orthogonal outcomes independently diff --git a/docs/defensive-patterns.zh.md b/docs/defensive-patterns.zh.md new file mode 100644 index 0000000000..60d7389db5 --- /dev/null +++ b/docs/defensive-patterns.zh.md @@ -0,0 +1,29 @@ +# 防御性模式 + +[English](defensive-patterns.md) | 中文 + +来之不易的缺陷类别规则:以下每条模式都是本项目中实际发布或险些发布的一类缺陷,以防止其复发的规则形式陈述。在编写生命周期、并发、子进程或清理代码之前,请先阅读本文。测试层面的对应规则(真实入口路径、world 验证、资源归属)见 [testing.md](testing.md)。 + +## 正交结果独立上报 + +一个结果可以同时具有多重性质:进程可能既超时又以 exit 0 退出,因为它捕获了信号。每个独立事实(`timedOut`、`signal`、`exitCode`)都应独立暴露;永远不要把一个 flag 的上报嵌套在另一个 flag 的分支内,否则调用方会把一次被截断的运行误读为正常成功。 + +## 在接口两侧都遵守跨 seam 契约 + +当接口文档记录了两种有效的信号方式时——例如适配器可以通过从 `stream()` 抛出异常来报告失败,也可以通过以 `finish {kind:'error'|'aborted'}` 分片结束流来报告——消费方必须两种都处理,而不只是第一个实现碰巧使用的那种。基于库的适配器在流中途无法抛出异常,只能依赖带内路径;如果 agent loop 只捕获 throw,就会把提供方的 401 变成一个正常完成的轮次。请在类型定义处记录契约;通过真实消费方测试每个分支。 + +## 异步状态不是同步状态 + +`agent.send()` 不会在返回前翻转状态;后台任务的完成与轮次边界存在竞态;`reader.close()` 在 EOF 和 dispose(资源释放)两种情况下都会触发。永远不要基于一个你刚刚请求的状态来控制流程——应当基于实际触发的事件/promise 来驱动生命周期(`agent/status`、`task.done`),并观察状态转换(先看到 `running` 再看到 `idle`),而不是假设你发出的动作与轮次 1:1 对应(循环会批量处理排队的消息)。这条守则是双向的:如果等待的转换永远不会发生(EOF 且没有提交过工作 → 永远不会进入 `running`),等待就会挂起——请显式处理「无需等待」的分支。 + +## dispose 必须达到静止,而非仅仅请求停止 + +一个只发出 kill/abort 就返回的清理逻辑会留下孤儿进程。请让清理逻辑异步化并 await 子进程退出(kill → await `done`),并在 kill 之前关闭监听器/通知注册表,使迟到的完成事件保持静默。测试应证明 dispose 确实等到了进程退出(`await fiber.dispose()` 之后 pid 已不存在),而非仅仅证明进程最终会死。 + +## 在边界处包容回调异常 + +用户提供的监听器抛出异常时,不得导致它所在的 promise 被 reject,也不得饿死排在它之后的监听器。请在分发循环中用 try/catch 包裹并记录日志;一个有问题的订阅者永远不能破坏核心生命周期。 + +## 永远不要把环境变量或可预测路径暴露给不可信输出 + +spawn 的命令应获得一个经过清洗的 env(移除 `*KEY*`/`*SECRET*`/`*TOKEN*`),确保 harness 凭证不会泄漏到输出、`env` 或溢出文件中。临时/溢出文件应使用私有(0700)目录、随机文件名和排他的仅所有者可打开模式(`'wx'`、`0o600`)——可预测的全局可读路径会招致符号链接竞态和信息泄露。 diff --git a/docs/glossary.i18n.yaml b/docs/glossary.i18n.yaml new file mode 100644 index 0000000000..a0765ef752 --- /dev/null +++ b/docs/glossary.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +glossary.md: 23eebc5793e8232482a796f5bde1e794f0556208 +glossary.zh.md: 7163015eeed71c96743b9cae491db206585a70b2 diff --git a/docs/glossary.md b/docs/glossary.md index 81b9b4f84a..23eebc5793 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -1,5 +1,7 @@ # Glossary +English | [中文](glossary.zh.md) + Domain vocabulary for the DeepSeek Harness SDK uses one canonical term per concept. Terms link to their entries with standard Markdown anchors; implementation detail stays in package READMEs and RFCs. FIXME(glossary-completeness): Expand this glossary before the first release so it covers the SDK's other core and capability subsystems, not only agent scope. diff --git a/docs/glossary.zh.md b/docs/glossary.zh.md new file mode 100644 index 0000000000..7163015eee --- /dev/null +++ b/docs/glossary.zh.md @@ -0,0 +1,19 @@ +# 术语表 + +[English](glossary.md) | 中文 + +DeepSeek Harness SDK 的领域词汇对每个概念使用唯一的规范术语。各术语通过标准 Markdown 锚点互相链接;实现细节留在各 package README 和 RFC 中。 + +FIXME(glossary-completeness): Expand this glossary before the first release so it covers the SDK's other core and capability subsystems, not only agent scope. + +## agent 作用域 + +- **scope(作用域)**:按 agent(智能体)注册的单位。一项贡献(工具、prompt 段落、变量、限制、监听器)要么是*全局*的(对所有 agent 可见),要么是*有作用域*的(归属于恰好一个 [scope key](#scope-key))。只有两层,扁平结构:有作用域的注册不会向下继承给 subagent;子树行为通过[血统](#lineage)数据表达,从不通过作用域结构。 +- **scope key(作用域键)**:作用域的不透明标识,按对象同一性比较。harness 约定:一个活跃的 agent 就是其自身作用域的 key。 +- **agent context(`agent.ctx`)**:agent 的有作用域上下文;通过它进行的注册既是作用域可见的,也是作用域生命周期的(一个事实同时驱动两者),其上的监听器参与该 agent 的作用域过滤分发。注册表主体事件可以在其自身的事件契约下有意保持不过滤。 +- **scope carrier(作用域载体)**:作用域过滤分发所携带的 `thisArg`(由 `scopeTarget` 构建);其过滤器放行无标签监听器加上主体自身的监听器。*无主体*的载体(没有 key)只放行无标签监听器。 +- **scoped dispatch(作用域分发)**:规则是:关于某个 agent 活动的事件以该 agent 的载体进行分发。关于注册表本身的事件(如「一个工具被添加」)属于*注册表主体*事件,保持不过滤。 +- **shadowing(遮蔽)**:最具体者胜出的名称解析:一个有作用域的工具/段落/变量仅在该作用域内替代其同名的全局副本。这是按 agent 定制人设和按 agent 定制工具变体的机制。 +- **restriction / scope-local registration(限制 / 作用域局部注册)**:限制(`tools.restrict`)为单个作用域过滤全局工具面(按交集组合);作用域局部注册在过滤之后合并。被过滤掉的全局工具既不出现在 prompt 中,也拒绝执行,与不存在的工具无法区分。 +- **setup window(设置窗口)**:创建者组装 agent 有作用域世界的创建时隙(`CreateAgentOptions.setup`):在作用域和 agent 对象已存在、但 agent 或会话尚未发布、`agent/session-start` 尚未触发、首次 prompt 尚未组装之前。设置窗口只做注册,从不驱动 agent。 +- **lineage(血统)**:以数据形式携带的父子关系(`parentSession`、`subagentDepth`);从不影响可见性。 diff --git a/docs/testing.i18n.yaml b/docs/testing.i18n.yaml new file mode 100644 index 0000000000..3adcf28723 --- /dev/null +++ b/docs/testing.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +testing.md: ddb9da0b38e5dc9cc75ede81ec157c4744fd11c2 +testing.zh.md: 6d21a37b175c8052fbc34db0fc5a9b522c27f7ec diff --git a/docs/testing.md b/docs/testing.md index d4acdc33c4..ddb9da0b38 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,5 +1,7 @@ # Testing policy +English | [中文](testing.zh.md) + How this repo tests, tier by tier, and the rules that keep a green suite meaningful. Commands live in root [AGENTS.md](../AGENTS.md); linked RFCs carry the rationale. ## Tiers diff --git a/docs/testing.zh.md b/docs/testing.zh.md new file mode 100644 index 0000000000..6d21a37b17 --- /dev/null +++ b/docs/testing.zh.md @@ -0,0 +1,35 @@ +# 测试策略 + +[English](testing.md) | 中文 + +本文说明本仓库如何逐层测试,以及保持绿色测试套件有意义的规则。命令见根目录 [AGENTS.md](../AGENTS.md);关联 RFC 承载设计动机。 + +## 层级 + +- **单元测试**(`pnpm run test`):vitest 运行 `packages|examples/*/tests/**/*.spec.ts`,与被测代码同目录。每个注册表都有一个 HMR(热模块替换)安全测试(dispose 贡献该注册的 fiber,断言清理完成)。优先覆盖边界情况、错误路径、事件顺序、并发竞态与永久契约回归(见 `packages/core/agent-loop/tests/contract-regressions.spec.ts`)。 +- **覆盖率门禁**(`pnpm run test:coverage`):门禁级运行,对 `packages/*/*/src` 按文件 100% 覆盖。未覆盖的行往往是门禁正确标记出的死代码(应删除),而非需要补写的测试。行覆盖率是必要条件,绝非充分条件:它证明代码行被执行过,不证明功能按交付预期工作。 +- **真实 API e2e**(`pnpm run test:e2e`):带密钥测试,对接真实提供方 API——DeepSeek 模型加各提供方独立冒烟测试(各自依赖自己的密钥:`EXA_API_KEY`、`PERPLEXITY_API_KEY` 等);每个套件在缺少对应密钥时自动跳过,确保无密钥 CI 保持绿色([真实 API e2e RFC](rfc/implemented/testing/2026-06-19-real-api-e2e-ci.md))。 +- **快照测试**(`pnpm run test:snapshot`):启动真实示例子进程,无密钥回放录制的会话,将归一化后的 stdout 与重新持久化的日志同已提交的 golden 文件做 diff([快照 RFC](rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md))。当模型 transcript(文本记录)需要变更时使用 `pnpm run test:snapshot:record`;当已提交的 transcript 仍是正确的 mock LLM(大语言模型)输入、只需无密钥重写回放 golden 时使用 `pnpm run test:snapshot:refresh`。请评审 golden diff。系统提示词/工具 schema 内容由一个场景(`text-turn`)固定,其余 fixture(测试前置数据)中以 token 化形式引用,因此 prompt 或 schema 的修改只变动一行已提交内容([pinned-header RFC](rfc/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md))。 + +## 带密钥策略:推理在这里很便宜 + +我们是 DeepSeek:不要吝惜真实 API 测试。无密钥测试证明管道通了;只有带密钥运行才能证明 agent 对接真实模型时能正常工作。多写:文件写入 prompt、多轮对话、工具调用、流中取消。价值最高的是**冒烟测试**:启动真实示例、发送一条真实 prompt、检查外部世界的状态。它们能捕获「单元测试全绿、产品却坏了」这类 mock 在结构上无法发现的问题([事后分析 0001](postmortem/0001-acp-default-export-drops-inject.md))。自动跳过机制的存在仅仅是为了不阻塞无密钥 CI 和无密钥贡献者,它不是成本信号。每个示例都附带一个无密钥冒烟测试,以及(除非本身就不需要密钥)一个带密钥冒烟测试([examples/AGENTS.md](../examples/AGENTS.md))。 + +## 优先使用真实实现而非 mock + +只 mock 真正昂贵或不确定的边界(LLM 适配器、网络、时钟);下游一切保持真实。手写的替身只能证明桥接层在搬运字节,不能证明交付的工具行为符合断言——两者会漂移,而测试仍然绿着。示例:bridge 工具调用测试运行脚本化的 mock 模型,但使用真实的 tool + 真实的执行器(`makeBridgeHarness({ withBash: true })` 接入 `dsh-bash-local` + `dsh-tool-bash`,执行真正的 `echo`)。 + +## 验证外部世界,而非自我报告 + +e2e 断言应重新运行命令或从外部重新读取文件;仅对 agent 自身输出做关键词探测会让作弊的 agent 通过。断言未改动的文件字节相同。e2e 测试拥有自己的资源:在测试中创建 harness,在 `afterEach` 中 dispose(即使失败/重试/超时);共享 fixture 放在普通的 `tests/harness.ts` 中,绝不放在另一个 `*.e2e.ts` 里(import 一个 spec 会重新注册其 `describe`,导致真实 API 调用重复)。 + +## 测试真实入口路径 + +- 产品可见的插件需要一个非单元的真实组合测试。手工搭建的 `ctx.plugin(...)` 套件不够:通过 Loader 和 app/process 启动仅用于测试的 `cordis.yml`,只 mock 外部/不确定边界,断言模型可见的请求/日志、持久状态或用户可见输出。不要把 opt-in 混入默认交付。 +- 一个守卫只有在回归真正让它失败时才算守卫。对于没有 `inject` 的插件(bundle/组合插件),Loader 冒烟测试在导出形状损坏时仍然绿——需要加一个显式的 `expect('default' in mod).toBe(false)` 加 `unwrapExports` 往返断言,并证明它有效:引入回归、观察变红、还原。 +- 「真实入口路径」指已发布的产物:package 的 `bin` 指向构建出的 `lib/bin.js`,在原生 `node` 下运行;tsx 会掩盖竞态、模块解析问题以及静默以 0 退出的加载失败。同理适用于构建后 package 在运行时解析的任何非 index 运行时入口(worker-thread 运行时的兄弟文件 `lib/worker.cjs`)。保持构建产物冒烟测试绿色(`packages/ui/*/tests/built-bin.e2e.ts`、`packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`),并断言真正缺失的配置以非零退出。 +- 从临时 cwd spawn 示例的 e2e 测试需要设置 `TSX_TSCONFIG_PATH` 指向仓库根 tsconfig,否则会静默回退到陈旧的构建 `lib/`([examples/AGENTS.md](../examples/AGENTS.md))。 + +## 何时需要快照测试 + +任何影响编辑器侧 transcript 或端到端 agent 用户体验的变更——ACP bridge、agent loop(智能体循环)的可观测输出、工具呈现——都应在所属示例的快照套件中添加或更新场景(`examples//tests/snapshots/`,基于 [`dsh-acp-snapshot`](../packages/support/acp-snapshot/README.md) 套件工厂的场景表;`examples/acp-agent` 是主套件),或在 PR 中说明为何不适用。新的能力 seam、生命周期形态或 transcript 表面在计划阶段就要标明各层的覆盖方式,并验证 harness 能表达它——harness 的缺口是排期工作,不是构建中途的意外。 diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json index 35a957e57d..ee0a35b599 100644 --- a/scripts/translation-pairing.manifest.json +++ b/scripts/translation-pairing.manifest.json @@ -2,31 +2,41 @@ "requiredSince": "2026-07-14", "required": [ "README.md", + "docs/architecture.md", "docs/cookbook/adding-a-package.md", "docs/cookbook/adding-a-tool.md", "docs/cookbook/adding-a-vendored-package.md", "docs/cookbook/adding-an-llm-adapter.md", "docs/cookbook/extension-cookbook.md", "docs/cookbook/responding-to-pr-review-on-a-stack.md", + "docs/cordis-primer.md", + "docs/defensive-patterns.md", "docs/development.md", + "docs/glossary.md", "docs/i18n/README.md", "docs/i18n/translation-rules.md", "docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md", "docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md", + "docs/testing.md", "python/README.md", "python/sdk-runtime/README.md", "python/sdk/README.md" ], "excluded": [ "docs/AGENTS.md", + "docs/agent-lifecycle.md", + "docs/capability-seams.md", "docs/config-catalog.md", "docs/cordis-catalog/", + "docs/event-producer-consumer.md", + "docs/graph-atlas.md", "docs/i18n/style-samples.md", "docs/i18n/terminology.md", "docs/i18n/translation-prompt.md", "docs/module-graph.md", "docs/persistence-catalog.md", "docs/tool-catalog.md", + "docs/tool-execution-pipeline.md", "python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/" ] }