diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml index 465a658ca8..2d39db2332 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml @@ -2,5 +2,5 @@ # 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 .agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md -2026-07-14-provider-routed-llm-adapters.md: 27277280e423553f79d5a34f512b673413f495ff -2026-07-14-provider-routed-llm-adapters.zh.md: aeb09a500d5750ef2793bc9a7fc09834055a56a4 +2026-07-14-provider-routed-llm-adapters.md: 9039334370ba5d71eb71879970c2e572a5b023df +2026-07-14-provider-routed-llm-adapters.zh.md: eff33496e1a61472534009533270f8f943ccc1f8 diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md index 27277280e4..9039334370 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md @@ -84,7 +84,7 @@ The on-disk session format remains the pre-release pinned version `0`, with no c - Unit coverage exercises registry conflicts, request reconstruction, session validation, profile resolution, single-attempt option forwarding, native API selection including OpenAI Responses, conversion, replay validation, error mapping, caller cancellation, idle-timeout transport termination, content rewrites, and same-instance versus different-instance replay dispatch. - Keyless loop/session tests and ACP snapshots exercise durable provider/model metadata, resume and fork propagation, workflow/subagent overrides, and unchanged user-visible transcripts; the key-gated DeepSeek e2e retains real provider streaming and tool follow-up coverage. -- Public JSDoc, package READMEs, architecture and core-data-structure docs, generated catalogs, examples, session fixtures, and Python SDK pairs use provider/model targets consistently and are checked by the repository documentation and type-equivalence gates. +- Public JSDoc, package READMEs, architecture and subsystem docs, generated catalogs, examples, session fixtures, and Python SDK pairs use provider/model targets consistently and are checked by the repository documentation and type-equivalence gates. ## Risks diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md index aeb09a500d..eff33496e1 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md @@ -8,9 +8,9 @@ Status: implemented `dsh-llm` 按精确模型名称注册适配器。插件在 Cordis 启动时提供模型列表,`LlmService` 为列表中的每个字符串保存一个适配器,`GenerateOptions.model` 同时选择适配器与提供方模型。两个正式适配器都只面向相同的两个 DeepSeek 模型时,这种方式可以工作,但它混淆了两个独立决策:由哪个上游提供方承接请求,以及该提供方应运行哪个模型。 -这种混淆使提供方网关无法提供开放的模型目录。例如,OpenRouter 是一个包含大量模型 ID 的提供方,私有 OpenAI 兼容端点也可能在不修改 harness 插件树的情况下增加模型。目前,每个新选择的模型都必须在插件启动期间完成注册。同一个模型 ID 还可能存在于多个提供方中,因此仅按模型注册无法表达调用方预期使用的提供方。 +这种混淆使提供方网关无法提供开放的模型目录。例如,OpenRouter 是一个包含大量模型 ID 的提供方,私有 OpenAI 兼容端点也可能在不修改 Harness 插件树的情况下增加模型。目前,每个新选择的模型都必须在插件启动期间完成注册。同一个模型 ID 还可能存在于多个提供方中,因此仅按模型注册无法表达调用方预期使用的提供方。 -`dsh-llm-pi-ai` 没有暴露 pi-ai 的提供方抽象。它以内联方式构造 DeepSeek `openai-completions` 模型,应用 DeepSeek 专用载荷补丁,并将每条回放的助手消息标记为 DeepSeek。pi-ai 自身提供提供方/模型目录,能够选择 `openai-responses`、`anthropic-messages`、`google-generative-ai` 等 API,并保留提供方专用的响应 ID,以及后续轮次所需的推理(reasoning)和工具签名。harness 转换丢弃了这些来源信息,因此仅将内联模型替换为目录查询,会导致同模型回放与跨提供方移交不完整。 +`dsh-llm-pi-ai` 没有暴露 pi-ai 的提供方抽象。它以内联方式构造 DeepSeek `openai-completions` 模型,应用 DeepSeek 专用的 payload 补丁,并将每条回放的助手消息标记为 DeepSeek。pi-ai 自身提供提供方/模型目录,能够选择 `openai-responses`、`anthropic-messages`、`google-generative-ai` 等 API,并保留提供方专用的响应 ID,以及后续轮次所需的推理和工具签名。Harness 转换丢弃了这些来源信息,因此仅将内联模型替换为目录查询,会导致同模型回放与跨提供方移交不完整。 适配器配置同样假定只存在一个 DeepSeek API 密钥和端点。通用后端需要为各提供方分别配置凭据和端点覆盖,同时继续由 pi-ai 处理 AWS、Google ADC、OAuth 等环境认证机制。 @@ -18,9 +18,9 @@ Status: implemented ### 提供方作为适配器注册键 -`GenerateOptions` 与 `LlmCallConfig` 在 `model: string` 之外携带 `provider: string`,`AgentOptions` 则携带对应的可选创建字段。只有两个值都非空时,agent loop(智能体循环)请求才有效;两个值也都是已记录请求头的一部分。`agent/request` 可以在任意步骤返回替换后的字段组合,因此会话可以切换提供方与模型,无需改变 Cordis 插件生命周期。 +`GenerateOptions` 与 `LlmCallConfig` 在 `model: string` 之外携带 `provider: string`,`AgentOptions` 则携带对应的可选创建字段。只有两个值都非空时,agent loop(智能体循环)请求才有效;两个值也都会写入请求头日志。`agent/request` 可以在任意步骤返回替换后的字段组合,因此会话可以切换提供方与模型,无需改变 Cordis 插件生命周期。 -`LlmService` 按提供方注册和解析适配器。`registerAdapter(providers, adapter)` 在修改注册表前检查整个提供方列表,遇到重复项时以 `DUPLICATE_ADAPTER` 拒绝注册,并将整组注册作为一个 effect 统一 dispose(资源释放)。模型 ID 不作为注册键;仍由选中的适配器负责验证或转发。后续的 [LLM 目录与 ACP(Agent Client Protocol)模型选择 Agent Note](2026-07-15-llm-model-catalog-and-acp-selection.md) 增加了建议性的 `listProviders()` / `listModels()` 发现接口,但不会把目录成员关系变成请求校验规则。 +`LlmService` 按提供方注册和解析适配器。`registerAdapter(providers, adapter)` 在修改注册表前检查整个提供方列表,遇到重复项时返回 `DUPLICATE_ADAPTER`,并将整组注册作为一个 effect 释放。模型 ID 不作为注册键;仍由选中的适配器负责验证或转发。后续的 [LLM 目录与 ACP 模型选择 Agent Note](2026-07-15-llm-model-catalog-and-acp-selection.md) 增加了建议性的 `listProviders()` / `listModels()` 发现接口,但不会把目录成员关系变成请求校验规则。 在一个 Cordis 上下文中,一个提供方只能有一个适配器所有者。`dsh-llm-deepseek` 注册 `deepseek`;`dsh-llm-pi-ai` 也可以注册 `deepseek`,但同时加载两个所有者属于配置错误,不采用顺序规则或回退行为。若部署选择手写的 DeepSeek 实现,需从 pi-ai 配置中排除 `deepseek`;若部署选择 pi-ai 的 DeepSeek 实现,则不挂载 `dsh-llm-deepseek`。 @@ -28,29 +28,29 @@ Status: implemented ### 显式 pi-ai 提供方配置 -`dsh-llm-pi-ai` 接受一个非空的提供方配置列表。列表内的提供方名称必须唯一,并且存在于 pi-ai 的 `getProviders()` 结果中。每项配置包含提供方名称,以及可选的 `apiKey`、`baseURL`、headers、推理级别和预算、缓存保留设置、传输方式、SDK 超时、harness 流空闲超时,以及由提供方拥有的 `retryPolicy`。适配器强制将 pi-ai 的 `maxRetries` 设为零,使一次 `stream()` 调用只发起一次可见的提供方请求;`dsh-llm-retry` 则在 agent 失败步骤 seam 上执行解析后的策略。凭据不设全局值:显式密钥仅对所属配置生效;未提供密钥时,pi-ai 使用标准环境变量、OAuth token、AWS 凭据链、Google ADC 或其他提供方原生环境认证。显式空密钥属于无效配置,不会回退到环境认证。 +`dsh-llm-pi-ai` 接受一个非空的提供方配置列表。列表内的提供方名称必须唯一,并且存在于 pi-ai 的 `getProviders()` 结果中。每项配置包含提供方名称,以及可选的 `apiKey`、`baseURL`、headers、推理级别和预算、缓存保留设置、传输方式、SDK 超时、Harness 流空闲超时,以及由提供方拥有的 `retryPolicy`。适配器强制将 pi-ai 的 `maxRetries` 设为零,使一次 `stream()` 调用只发起一次可见的提供方请求;`dsh-llm-retry` 则在 agent 失败步骤 seam 上执行解析后的策略。凭据不设全局值:显式密钥仅对所属配置生效;未提供密钥时,pi-ai 使用标准环境变量、OAuth token、AWS 凭据链、Google ADC 或其他提供方原生环境认证。显式空密钥属于无效配置,不会回退到环境认证。 插件通过一次全有或全无调用,将所有已配置的提供方名称注册到同一个 `PiAiAdapter`。请求按 provider 选择对应配置,并在 `getModels(provider)` 中查找模型以取得目录描述符。未知提供方会在插件加载时失败;未知模型会在网络 I/O 前以 `UNKNOWN_MODEL` 失败。适配器不会修改目录对象。当配置提供 `baseURL` 时,适配器复制选中的描述符,仅覆盖 `baseUrl`,使私有端点保留 pi-ai 的 API、能力、兼容标志、上下文限制与推理映射。私有端点必须实现所选提供方的协议,模型 ID 也仍须存在于已安装的 pi-ai 目录中。 -适配器调用 pi-ai 的 `streamSimple()`,因此每个目录模型会选择其注册的 API 实现;描述符为 `openai-responses` 时使用 OpenAI Responses,而非 Chat Completions。harness 的 temperature、最大 token 数、signal、session ID,以及提供方配置中的通用流选项均直接传递。配置 headers 与 harness 强制归因 headers 合并;发生保留名称冲突时,以 harness 归因为准。适配器不再维护 DeepSeek 专用载荷重写或提供方协议矩阵。 +适配器调用 pi-ai 的 `streamSimple()`,因此每个目录模型会选择其注册的 API 实现;描述符为 `openai-responses` 时使用 OpenAI Responses,而非 Chat Completions。Harness 的 temperature、最大 token 数、signal、session ID,以及提供方配置中的通用流选项均直接传递。配置 headers 与 Harness 强制归因 headers 合并;发生保留名称冲突时,以 Harness 归因为准。适配器不再维护 DeepSeek 专用 payload 重写或提供方协议矩阵。 -pi-ai 的通用流选项不支持停止序列。若 harness `stop` 选项已定义,`dsh-llm-pi-ai` 会以 `UNSUPPORTED_OPTION` 拒绝请求,不会静默忽略,也不会增加第二套提供方专用 payload 实现。`dsh-llm-deepseek` 继续通过原生请求序列化器支持 `stop`。 +pi-ai 的通用流选项不支持停止序列。若 Harness `stop` 选项已定义,`dsh-llm-pi-ai` 会以 `UNSUPPORTED_OPTION` 拒绝请求,不会静默忽略,也不会增加第二套提供方专用 payload 实现。`dsh-llm-deepseek` 继续通过原生请求序列化器支持 `stop`。 ### 持久化助手来源信息与回放状态 助手消息携带提供方无关的来源信息,其中包含请求的 `provider` 和 `model`,以及可选的 JSON 可序列化适配器回放状态。成功的 `assistant/message` 会话事件记录这些来源信息,`deriveMessages()` 返回助手消息时也会包含这些信息。用户、system、context 与工具结果消息不携带助手来源信息。provider/model 字段是 agent loop 的权威数据;适配器仅拥有其不透明回放状态 payload。 -成功的终止 `finish` 分片可以携带回放状态,`BlockAssembler` 会将其与 token 用量和结束原因一起保留。agent loop 会把该状态附加到组装后的助手来源信息,不再暴露响应改写 hook。错误或中止响应不会生成正常助手消息,因此不会进入后续模型历史。 +成功的终止 `finish` 分片可以携带回放状态,`BlockAssembler` 会将其与 token 用量和结束原因一起保留。只有当 `agent/step-result` 处理后的内容与提供方组装输出在结构上相等时,agent loop 才会把回放状态附加到助手来源信息。监听器重写内容后,provider/model 来源信息仍会保留,但已经陈旧的回放状态会被移除。错误或中止响应不会生成正常助手消息,因此不会进入后续模型历史。 -pi-ai 回放状态是其成功 `AssistantMessage` 的带版本最小投影,包含源 API/provider/model、响应 ID/model、停止原因,以及按索引对齐的文本、thinking 和工具调用签名。它不会重复 harness 内容块中已有的文本或工具参数,也不包含诊断信息、时间戳、用量或错误。后续请求中,只有历史提供方和目标提供方当前归同一个适配器实例所有时,`LlmService` 才会把回放状态交给目标适配器。适配器在能够恢复历史响应时,将 harness 记录的内容与回放状态组合,并负责所需的跨模型或跨提供方转换。适配器收到未知版本或块形状不匹配的回放状态时会显式失败;其他适配器只能收到提供方无关的内容与来源信息。 +pi-ai 回放状态是其成功 `AssistantMessage` 的带版本最小投影,包含源 API/provider/model、响应 ID/model、停止原因,以及按索引对齐的文本、thinking 和工具调用签名。它不会重复 Harness 内容块中已有的文本或工具参数,也不包含诊断信息、时间戳、用量或错误。后续请求中,只有历史提供方和目标提供方当前归同一个适配器实例所有时,`LlmService` 才会把回放状态交给目标适配器。适配器在能够恢复历史响应时,将 Harness 记录的内容与回放状态组合,并负责所需的跨模型或跨提供方转换。适配器收到未知版本或块形状不匹配的回放状态时会显式失败;其他适配器只能收到提供方无关的内容与来源信息。 该状态属于模型可见的回放输入,因此遵循现有的[请求可重建规则](2026-07-05-reconstructable-requests.md):它同时存在于终止 `finish` 分片和驱动派生的已组装 `assistant/message` 来源信息中。恢复和 fork 会原样保留该状态。压缩(compaction)遮蔽助手消息时,也会从活动 surface 中移除其回放状态;摘要属于普通的提供方无关内容。 ### 在所有请求生产方中传播目标 -每个模型选择接口都同时携带 provider 与 model:声明式 agent、ACP 和 stdio 应用配置、JSON-RPC initialize 请求、subagent 覆盖与继承、工作流子 agent 覆盖,以及直接压缩摘要。subagent 先从父 agent 继承两个字段,再应用请求覆盖。系统提示词变量集合在 `model` 之外增加 `provider`。 +每个模型选择接口都同时携带 provider 与 model:声明式 agent、ACP(Agent Client Protocol)和 stdio 应用配置、JSON-RPC initialize 请求、subagent 覆盖与继承、工作流子 agent 覆盖,以及直接压缩摘要。subagent 先从父 agent 继承两个字段,再应用请求覆盖。系统提示词变量集合在 `model` 之外增加 `provider`。 -压缩配置在 `summarizationModel` 之外增加 `summarizationProvider`。两个值均为空时继承,均非空时选择显式目标;只配置其中一个会导致加载失败。继承优先使用最近一次记录的请求目标,没有时回退到 agent 创建选项。`compact/summary` 使用现有模型调用封装记录两个字段。 +压缩配置在 `summarizationModel` 之外增加 `summarizationProvider`。两个值均为空时继承,均非空时选择显式目标;只配置其中一个会导致加载失败。继承优先使用最近一次记录的请求目标,没有时回退到 agent 创建选项。`compact/summary` 使用现有模型调用 envelope 记录两个字段。 JSON-RPC 运行时显式接收 provider 与 model。仅当 `deepseek` 提供方没有注册所有者时,其便利回退才会挂载 `dsh-llm-deepseek`;其他缺失的提供方会直接失败,不会猜测适配器。 @@ -68,7 +68,7 @@ JSON-RPC 运行时显式接收 provider 与 model。仅当 `deepseek` 提供方 **每个提供方挂载一个 pi-ai 插件实例。** 独立实例可以隔离配置,但会重复插件声明,也无法实现配置注册的原子性。每个请求本就向同一个适配器提供 provider,因此经过验证的配置映射具有更小的生命周期接口。 -**接受任意内联 pi-ai 模型描述符。** 这种方式可支持目录外的私有模型 ID,但会将 pi-ai 的模型与兼容性 schema 暴露为 harness 配置,并要求适配器验证协议专用组合。当前版本通过覆盖目录模型的 `baseURL` 支持自定义端点;只有实际出现目录外部署需求后,才会另行决策是否支持自定义描述符。 +**接受任意内联 pi-ai 模型描述符。** 这种方式可支持目录外的私有模型 ID,但会将 pi-ai 的模型与兼容性 schema 暴露为 Harness 配置,并要求适配器验证协议专用组合。当前版本通过覆盖目录模型的 `baseURL` 支持自定义端点;只有实际出现目录外部署需求后,才会另行决策是否支持自定义描述符。 ## 影响 @@ -84,7 +84,7 @@ JSON-RPC 运行时显式接收 provider 与 model。仅当 `deepseek` 提供方 - 单元测试覆盖注册表冲突、请求重建、会话验证、配置解析、单次请求的选项转发、包括 OpenAI Responses 在内的原生 API 选择、转换、回放验证、错误映射、调用方取消、空闲超时导致的传输终止、内容重写,以及同一实例与不同实例间的回放分发。 - 无密钥的 agent loop/会话测试和 ACP 快照覆盖持久化 provider/model 元数据、恢复与 fork 传播、工作流/subagent 覆盖,以及不变的用户可见 transcript(文本记录);密钥门控的 DeepSeek e2e 测试保留真实提供方的流式输出与工具后续调用覆盖率。 -- 公共 JSDoc、包的 README、架构与核心数据结构文档、生成目录、示例、会话 fixture(测试前置数据)和 Python SDK 配对文档统一使用 provider/model 目标,并由仓库文档与类型等价门禁校验。 +- 公共 JSDoc、package README、架构与子系统文档、生成目录、示例、会话 fixture(测试前置数据)和 Python SDK 配对文档统一使用 provider/model 目标,并由仓库文档与类型等价门禁校验。 ## 风险 diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml index 72944d7433..7756f7803c 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml @@ -2,5 +2,5 @@ # 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 .agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md -2026-07-16-persistent-pty-sessions.md: 88296a9318b2c398fead382153bd2c652dbd9e68 -2026-07-16-persistent-pty-sessions.zh.md: 40f26c67fe046ad292f05919501600bd2e146c62 +2026-07-16-persistent-pty-sessions.md: 219f48235d527cffbf66020ceb22e80ca473093d +2026-07-16-persistent-pty-sessions.zh.md: 18b81cd6a8d3f9cdbaa8566e1b166c3877ae8742 diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md index 88296a9318..219f48235d 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md @@ -160,7 +160,7 @@ The package ships concise tool guidance explaining persistent state, owner isola - Subprocess process fixtures cover non-leader and non-main-thread stdin waits, zombie quiescence, unreadable process state, supported syscall tables, unsupported architectures, and false-positive rejection; macOS inspector logic is injected into the same unit suite. - Real `node-pty` and PTY-consumer tests jointly exercise shell state, shared sandbox policy, environment scrubbing, raw-mode foreground `SIGINT`, a TERM-ignoring descendant, and immediate post-disposal quiescence on supported hosts. - A Loader-driven `cordis.yml` test mounts the real three-package composition. ACP and headless snapshots pin the six schemas, bounded results, and errors through opt-in overlays; TUI snapshots pin terminal and generic card presentation. -- Package contracts, the architecture map, core data structures, generated catalogs, and the website API describe the same shipped surface. +- Package contracts, the architecture map, subsystem pages, generated catalogs, and the website API describe the same shipped surface. - The repository CI-equivalent sequence owns type, lint, coverage, snapshot, documentation, build, hygiene, demo, and built-entry verification. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md index 40f26c67fe..18b81cd6a8 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md @@ -160,7 +160,7 @@ plugins: - 子进程 fixture 覆盖非 leader 与非主线程的 stdin 等待、僵尸进程完全停稳、不可读进程状态、受支持的 syscall 表、不支持的架构和误报拒绝;同一单元测试套件通过注入覆盖 macOS 检查器逻辑。 - 真实 `node-pty` 与 PTY 消费方测试共同在受支持宿主上覆盖 shell 状态、共享沙箱策略、环境清洗、raw mode 前台 `SIGINT`、忽略 `SIGTERM` 的后代进程,以及 dispose 返回后立即完全停稳。 - Loader 驱动的 `cordis.yml` 测试挂载真实三包组合。ACP 与 headless 快照通过 opt-in overlay 固定 6 个 schema、有界结果和错误;TUI 快照固定 terminal 与 generic 卡片展示。 -- 包契约、架构图、核心数据结构、生成目录和 website API 描述同一个已发布接口。 +- 包契约、架构图、子系统页面、生成目录和 website API 描述同一个已发布接口。 - 仓库 CI 等价序列负责类型、lint、覆盖率、快照、文档、构建、hygiene、demo 和 built-entry 验证。 ## 后果 diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index ec72119627..3303bcb935 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/README.md -README.md: 59e5a4db09d114e96c19dc0fe29a5ebe8d8c8d32 -README.zh.md: 6b5fa58bf45654708b08d780aa0e17a8d90b7e17 +README.md: d225e5f4eeb31d1e6c71cbd8311e28bae1e8b3a5 +README.zh.md: 9f78ac3e1c79440b053ee4e344dbdba2c11b3dfc diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index 59e5a4db09..d225e5f4ee 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -6,8 +6,8 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | Page | Owns | |---|---| -| [core.md](core.md) | the spine vocabulary every turn traffics in — `Message`/`ContentBlock`, `StreamChunk`, `SessionEvent`, the `Agent` handle, `ToolDefinition`, branded ids, the `…Map → derived-union` pattern | -| [llm-streaming.md](llm-streaming.md) | the `StreamChunk` wire protocol + adapter contract, `BlockAssembler`, the `LlmAdapter` seam | +| [core.md](core.md) | the `packages/core` control spine: the package-by-package loop map, the `Agent` handle with its delivery/cancellation/interception contracts, the `SessionEvent` envelope, branded ids, the `…Map → derived-union` pattern | +| [llm-streaming.md](llm-streaming.md) | the `packages/llm` conversation vocabulary — `Message`/`ContentBlock`, the assembled model request, the `StreamChunk` wire protocol + adapter contract, `BlockAssembler`, the `LlmAdapter` seam | | [token-meter.md](token-meter.md) | immutable scalar and positional replay measurements with consumed-log revisions | | [scope.md](scope.md) | scoped registration identity, dispatch carriers, and the owned `Scope` context | | [typert.md](typert.md) | Remote invocation descriptors, lookup/Context declarations, TypeRT registries, and the Host Gateway/Client API seams | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 6b5fa58bf4..9f78ac3e1c 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -6,8 +6,8 @@ | 页面 | 负责内容 | |---|---| -| [core.md](core.md) | 每个轮次都在使用的主干词汇——`Message`/`ContentBlock`、`StreamChunk`、`SessionEvent`、`Agent` 句柄、`ToolDefinition`、品牌化 id,以及 `…Map → 派生联合` 模式 | -| [llm-streaming.md](llm-streaming.md) | `StreamChunk` 协议格式(wire format)+ 适配器契约(adapter contract)、`BlockAssembler`、`LlmAdapter` seam | +| [core.md](core.md) | `packages/core` 控制主干:逐包循环地图、`Agent` 句柄及其投递/取消/拦截契约、`SessionEvent` 信封、品牌化 id,以及 `…Map → 派生联合` 模式 | +| [llm-streaming.md](llm-streaming.md) | `packages/llm` 的对话词汇——`Message`/`ContentBlock`、组装完成的模型请求、`StreamChunk` 协议格式(wire format)+ 适配器契约(adapter contract)、`BlockAssembler`、`LlmAdapter` seam | | [token-meter.md](token-meter.md) | 不可变的标量与位置回放度量,附带已消费日志修订号 | | [scope.md](scope.md) | 作用域注册标识、dispatch 载体,以及拥有的 `Scope` 上下文 | | [typert.md](typert.md) | 远程调用描述符、lookup/Context 声明、TypeRT 注册表,以及 Host Gateway/Client API seam | diff --git a/docs/subsystems/compaction.i18n.yaml b/docs/subsystems/compaction.i18n.yaml index eb2332a715..86348bad3a 100644 --- a/docs/subsystems/compaction.i18n.yaml +++ b/docs/subsystems/compaction.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/compaction.md -compaction.md: eeea249fa7a9f95e607edb10e6a8c93a24162b64 -compaction.zh.md: 24c55f062404ff827292f324392347ad59bb7846 +compaction.md: 5aef839192ea6f67bb409622187fe65fed795e73 +compaction.zh.md: 63b28518e68f71cc648d2f4e78a82bc5beafb621 diff --git a/docs/subsystems/compaction.md b/docs/subsystems/compaction.md index eeea249fa7..5aef839192 100644 --- a/docs/subsystems/compaction.md +++ b/docs/subsystems/compaction.md @@ -224,7 +224,7 @@ pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null pruneSession(session: Session): PruneResult ``` -Types: [ContentBlock](core.md) · [Session](session.md) +Types: [ContentBlock](llm-streaming.md) · [Session](session.md) Source: [`packages/compact/compact-tool-result-prune/src/index.ts:44`](../../packages/compact/compact-tool-result-prune/src/index.ts) diff --git a/docs/subsystems/compaction.zh.md b/docs/subsystems/compaction.zh.md index 24c55f0624..63b28518e6 100644 --- a/docs/subsystems/compaction.zh.md +++ b/docs/subsystems/compaction.zh.md @@ -224,7 +224,7 @@ pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null pruneSession(session: Session): PruneResult ``` -Types: [ContentBlock](core.md) · [Session](session.md) +Types: [ContentBlock](llm-streaming.md) · [Session](session.md) Source: [`packages/compact/compact-tool-result-prune/src/index.ts:44`](../../packages/compact/compact-tool-result-prune/src/index.ts) diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index 0994de32ec..16c8e29cf2 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/core.md -core.md: 8f27727d63e598fbf31b09a87ab53ad686155b4f -core.zh.md: fff5bd502cbed6f2167eb23731fb5c3825efb7af +core.md: ba9319d377cf250148a145ba3f1fa1baa967c858 +core.zh.md: 276dc4f99f1f802e66aca050b2fbcdc178931611 diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 8f27727d63..ba9319d377 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -1,20 +1,29 @@ -# Core Data Structures +# Core English | [中文](core.zh.md) -This page catalogs the **core** data structures of the DeepSeek Harness — what each spine type represents, its literal shape, and where the full detail lives. It complements [architecture.md](../architecture.md), which describes *behavior* (the service map, the session/turn/step lifecycle, the event taxonomy); this page describes the *vocabulary* that behavior moves around. The sibling subsystem pages are indexed in the [folder README](README.md). +The **core** subsystem is [`packages/core`](../../packages/core/README.md) — the control spine every composition boots: the event-sourced session log, system-prompt assembly, the tool registry, the agent vocabulary, and the concrete loop that drives them. This page owns the spine's shared vocabulary — the `Agent` handle, its delivery and interception contracts, and the repo-wide type patterns — and orients to the group's dedicated pages; the folder is indexed in the [subsystems README](README.md). -## What counts as "core" +## The spine, package by package -The harness is a microkernel: a tiny core plus many plugins. Most types belong to one plugin or one capability. A handful, though, are the **spine** — the language the agent loop and its events traffic in on *every* turn, no matter which optional plugins are loaded. Those are "core". +A turn flows through the six packages in one loop: the driver in [`agent-loop`](../../packages/core/agent-loop) claims a queued prompt, opens a turn on the [session log](session.md) (`ctx.sessions`), assembles the request prefix through [system-prompt](system-prompt.md) (`ctx.systemPrompt`) and derives history from the log, streams the model response through the [LLM seam](llm-streaming.md), dispatches tool calls through the [tool registry](tools.md) (`ctx.tools`), and appends every model-visible fact back onto the log before the next step derives from it. -Precisely, a data structure is **core** if either: +| Package | Owns | Page | +|---|---|---| +| `session/` | The append-only `SessionEvent` log and in-memory store — the single source of truth (`ctx.sessions`) | [session.md](session.md) | +| `system-prompt/` | Prompt-section and tool-schema assembly (`ctx.systemPrompt`) | [system-prompt.md](system-prompt.md) | +| `tools/` | The scoped tool registry and guarded execution pipeline (`ctx.tools`) | [tools.md](tools.md) | +| `agent/` | The `Agent` interface, live registry, initiator scope, and `agent/*` event vocabulary (`ctx.agents`) | this page | +| `agent-loop/` | The concrete driver implementing the public `Agent` contract (`ctx.agentLoop`) | this page | +| `scope/` | The scoped-registration primitive the registries and loop build per-agent scoping on | [scope.md](scope.md) | -1. it flows through the agent-loop spine — the loop holds it, derives it, streams it, or logs it on every turn (a `Message`, a `StreamChunk`, a `SessionEvent`, the `Agent` handle itself), independent of which plugins are present; **or** -2. it is the single headline type a plugin author writes against a pipeline — `ToolDefinition` (what every tool *is*). +`scope/` is the one non-service package: a dependency-free library (`createScope`/`scopeOf`/`scopeTarget`) that sits below `session/` and `system-prompt/` in the module graph precisely so they can consume it without a cycle. `agent-loop` is the one concrete implementation of the `agent` seam and lives here because it is the harness's default product loop; it runs each driver inside `ctx.agents.withInitiator()`. Extension plugins depend on `agent` — including when they need the initiating Agent — and never on `agent-loop` directly, so the loop stays swappable. The default composition that wires this spine into a runnable agent is [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.md). -Everything else is documented on its own subsystem page, not here. The rule that draws the line: *the type you write, hold, or receive is core; the machinery that types it, renders it, or persists it is another page’s detail.* So `ToolDefinition` is core, but the `ValueSchemaSpec`/`ParameterSchemaSpec` inference machinery that types it, the `ToolCallView`/`ToolResultView` render-intent vocabulary that renders it, and the `SessionPersistence` seam that stores the event log are not — they live on their own pages ([index](README.md)). + +## What this page owns + +The conversation vocabulary the loop moves — `Message`, `ContentBlock`, `StreamChunk`, the model request — is declared by [`packages/llm`](../../packages/llm/README.md) and documented on [llm-streaming.md](llm-streaming.md); the session-event, prompt-assembly, and tool vocabularies live on this group's dedicated pages above. What remains here is the vocabulary shared by everything: the `Agent` handle and its delivery, cancellation, and interception contracts (declared by `packages/core/agent`), the `SessionEvent` envelope, and the two type patterns every subsystem follows. The scoping rule is recorded in the [subsystems-catalog Agent Note](../../.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md): the type you write, hold, or receive is documented where its declaring subsystem is; the machinery that types, renders, or persists it stays on that machinery's page. ## The `…Map → derived-union` pattern @@ -37,13 +46,14 @@ declare module '@deepseek-ai/dsh-llm' { } ``` -Five canonical maps use this pattern; a plugin author extends these: +Six canonical maps use this pattern; a plugin author extends these: | Map | Package | Derives | Catalog | |---|---|---|---| -| `ContentBlockMap` | dsh-llm | `ContentBlock` | [below](#content-blocks-and-messages) | -| `MessageSourceMap` | dsh-llm | `MessageSource` | [below](#content-blocks-and-messages) | -| `FinishReasonMap` | dsh-llm | `FinishReason` | [below](#the-model-request-and-result) | +| `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](llm-streaming.md#the-model-request-and-result) | +| `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](session.md) | | `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](session.md) | | `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](session.md) | @@ -64,458 +74,6 @@ type Branded = string & { readonly [BRAND]: B } The two core IDs are `CallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `TaskId` in [tasks.md](tasks.md). -## Content blocks and messages - -A conversation is `Message`s; a message is an array of typed **content blocks**. The block union derives from `ContentBlockMap`. - -Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -The block interfaces (full fields in source): `TextBlock` (`text`), `ReasoningBlock` (thinking, distinct from visible text), `ToolCallBlock` (`id: CallId`, `name`, raw-JSON `arguments`), `ToolResultBlock` (`toolCallId`, nested `content: ContentBlock[]`, `isError?`). `ContentBlock = ContentBlockMap[ContentBlockType]`. The core set is limited to blocks every shipping path honors — multimodal content (images, audio, …) has no core block type; a feature that needs one adds it via the merge-extensible map together with the adapter/UI/compaction support that honors it. - -Source: [`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) - -A `Message` is one identified, immutable role/source/content value. Model-produced assistant messages carry provider/model ownership and optional adapter-private replay metadata in their source: - -```ts type-equiv -/** Provider ownership and adapter-private replay data for an assistant message. */ -interface AssistantProvenance { - /** Provider route that produced the message. */ - provider: string - /** Provider model id that produced the message. */ - model: string - /** - * Lossless-JSON adapter state needed to replay the provider response. - * `LlmService` exposes it to a target adapter only when that adapter instance - * currently owns both this historical provider and the target provider. - */ - replayState?: unknown -} -``` - -```ts type-equiv -/** One immutable message representation shared by delivery, durable history, and model requests. */ -interface Message { - /** Stable identity preserved across every representation boundary. */ - readonly id: MessageId - /** Provider-neutral conversation role. */ - readonly role: 'system' | 'user' | 'assistant' - /** Exact model-facing blocks. */ - readonly content: ContentBlock[] - /** Required producer provenance. */ - readonly source: MessageSource -} -``` - -Where a message came from is itself a merge-extensible sum type: - -```ts type-equiv -/** - * Where a message (or injected content) came from. - * Merge-extensible sum type — plugins add their own `kind`s. - */ -interface MessageSourceMap { - user: { kind: 'user' } - plugin: { kind: 'plugin'; plugin: string } & ContextFormed - model: ModelMessageSource - tool: ToolMessageSource -} -``` - -Provenance and shape are two independent axes. `kind` answers *who produced this*; the optional `form` a producer mixes in answers *what shape of information it is*, so several producers may share one presentation and one producer may emit more than one shape over a session. The vocabulary is semantic and grows one value at a time; an absent or unrecognized value is the documented default, presented as opaque content: - -```ts type-equiv -/** - * What SHAPE of information a producer-supplied context carries, declared by - * the producer beside its provenance. - * - * `MessageSource.kind` answers *who produced this*; `form` answers *what kind - * of thing it is*, and the two axes are deliberately independent — several - * producers share one form (three snapshot producers today), and one producer - * may emit more than one form over a session. - * - * The vocabulary is SEMANTIC, never visual: a value states that the content is - * a file's instructions or a catalog of available items, and a consumer decides - * what that looks like. Colors, icons, ordering, and collapse defaults are the - * consumer's business and must not enter this union. It grows one value at a - * time as producers gain the structured fields their form needs; an absent or - * unknown value is the documented default, presented as opaque content. - */ -type ContextForm = - /** Instructions read out of workspace files the model is expected to follow. */ - | 'instructions' - /** A catalog of items available in this session, republished as it changes. */ - | 'catalog' - /** Current state, where a later snapshot from the same producer supersedes an earlier one. */ - | 'snapshot' - /** A one-off account of something that just happened; it supersedes nothing. */ - | 'notice' - /** A message another agent addressed to this one. */ - | 'relay' - /** Material lifted out of another session's log, possibly reduced on the way in. */ - | 'recall' -``` - -```ts type-equiv -/** One named contribution to a `snapshot`-form context, in assembly order. */ -interface ContextSnapshotSection { - /** The contributing subsystem's name. */ - readonly name: string - /** That contribution's model-facing text, exactly as assembled. */ - readonly text: string -} -``` - -```ts type-equiv -/** - * Producer-declared {@link ContextForm} and the fields that form requires, - * mixed into the source shapes that carry one. - * - * Discriminated by `form` so a producer cannot declare a shape without the - * facts that shape is presented from: a `notice` must record its one-line - * account, a `snapshot` its sections. Omitting `form` stays valid — an - * undeclared context is the documented default. - */ -type ContextFormed = - | { readonly form?: never } - | { readonly form: 'instructions' } - | { readonly form: 'catalog' } - | { - readonly form: 'snapshot' - /** The named contributions this snapshot assembled, in order. */ - readonly sections: readonly ContextSnapshotSection[] - } - | { - readonly form: 'notice' - /** One-line account of what happened, shown without expanding the row. */ - readonly summary: string - } - | { readonly form: 'relay' } - | { readonly form: 'recall' } -``` - -## Streaming - -Adapters emit a raw **chunk** protocol; the loop logs the chunks (replay fidelity) while feeding the same chunks through a `BlockAssembler` to rebuild blocks and messages. `StreamChunk` is a closed discriminated union over `type` — `block-start`, `text-delta`, `reasoning-delta`, `tool-call-delta`, `block-end`, `usage`, `finish`. - -The full union, the adapter contract (usage-before-finish, raw-JSON tool arguments, the two sanctioned error paths), and `BlockAssembler` live on **[llm-streaming.md](llm-streaming.md)**. - - - -## The model request - -One model call is a fully-assembled `GenerateOptions`. The adapter answers with a raw `StreamChunk` stream; the consumer assembles it with `BlockAssembler` (see [llm-streaming.md](llm-streaming.md)). - -Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -Provider and model discovery uses small provider-neutral descriptors. A model catalog is advisory: routing still keys on a registered provider, and an adapter may accept unlisted model ids. - -Registering an adapter returns a handle: the disposer, plus the atomic route replacement a plugin whose route set is user-configurable needs. - -```ts type-equiv -/** - * What {@link LlmService.registerAdapter} returns: the disposer, plus an - * atomic route replacement for the same adapter instance. - */ -interface AdapterRegistrationHandle { - /** Release every route this registration currently holds. */ - (): void - /** - * Replace this registration's routes with `providers`, keeping the same - * adapter instance. The candidate set is validated in full first — a - * conflict with another adapter, an invalid name, or bad provider metadata - * throws and leaves the current routes untouched — and the swap itself is - * one synchronous section, so no request can observe a gap. An empty array - * is legal here (a settings section that emptied holds zero routes while - * staying registered), unlike an empty initial registration. - * - * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration - * has been released: its routes are gone and its disposer has already run, - * so anything registered afterwards would have no owner left to release it. - * @param providers - the complete next route set for this registration. - */ - replace(providers: string[]): void -} -``` - -```ts type-equiv -/** Display metadata for one registered provider route. */ -interface LlmProviderInfo { - /** Provider route key used by {@link GenerateOptions.provider}. */ - id: string - /** Human-readable provider name for selectors and diagnostics. */ - name: string -} -``` - -Adapter plugins additionally declare which routes *could* run through `registerConfigurableProviders()`, addressing each one's user-settings section, so configuration surfaces can offer dormant providers before any route registers. - -```ts type-equiv -/** - * One provider route an adapter plugin can activate through configuration, - * whether or not the route is currently registered. Configuration surfaces - * merge this directory with `listProviders()` to offer every configurable - * provider alongside its live/dormant state. - */ -interface LlmConfigurableProvider { - /** Provider route key this entry activates when configured. */ - provider: string - /** Human-readable provider name for configuration surfaces. */ - displayName: string - /** User-settings namespace whose section configures this provider. */ - settingsNs: string - /** - * Path from that namespace's section root to this provider's profile - * object; empty when the whole section is the profile. - */ - settingsPath: readonly string[] -} -``` - -```ts type-equiv -/** One adapter-discovered model; catalog membership is advisory, not request validation. */ -interface LlmModelInfo { - /** Provider route that owns this model entry. */ - provider: string - /** Model id passed to {@link GenerateOptions.model}. */ - id: string - /** Human-readable model name for selectors. */ - name: string - /** Optional user-facing distinction from otherwise similar models. */ - description?: string -} -``` - -A provider a surface is still drafting has no route and no catalog, so interrogation is described separately: the request carries the draft the user is editing, and the reply is candidates a surface may adopt rather than a catalog it must serve. - -```ts type-equiv -/** - * One interrogation of a provider endpoint that configuration has not stored - * yet. Configuration surfaces send the draft a user is still editing, so the - * request carries the endpoint and credential directly instead of naming a - * route: a provider being added has no route to name. - */ -interface LlmModelDiscoveryRequest { - /** - * Route the draft is editing, when it edits an existing one. A route whose - * adapter already knows its models answers from that knowledge instead of - * asking the endpoint — the adapter's own registry is the better answer, and - * it costs no network call. - */ - provider?: string - /** - * Endpoint to interrogate. Optional because a route the adapter already - * describes needs none; a route it does not must supply one. - */ - baseURL?: string - /** Wire protocol the endpoint speaks, when the draft names one. */ - api?: string - /** Credential for this interrogation alone; the harness never stores it. */ - apiKey?: string - /** Caller cancellation; implementations must settle promptly after it aborts. */ - signal?: AbortSignal -} -``` - -```ts type-equiv -/** - * One model an endpoint reports about itself. Every field but the id is - * optional because most provider listings disclose an id and nothing else; - * a surface adopting one of these still owes the capacities its adapter needs. - */ -interface LlmDiscoveredModel { - /** Model id the endpoint accepts. */ - id: string - /** Human-readable name when the endpoint supplies one. */ - name?: string - /** Maximum combined request and response context, when disclosed. */ - contextWindow?: number - /** Maximum output tokens, when disclosed. */ - maxTokens?: number -} -``` - -Correctness-sensitive metadata is resolved separately from the advisory catalog and is owned by the adapter serving the exact route. Context capacity, adapter call defaults, and reasoning choices share one exact-model result so consumers do not repeat authoritative model resolution. - -```ts type-equiv -/** Provider-owned context capacity for one exact provider/model route. */ -interface LlmModelContext { - /** Maximum combined request and response context in tokens. */ - contextWindow: number -} -``` - -Reasoning effort is another exact-route capability. The core brands identifiers but does not enumerate their values; each adapter owns the ordered set, display names, and optional deployment default. - -```ts type-equiv -/** Adapter-owned identifier for one model's selectable reasoning effort. */ -type ReasoningEffortId = Branded<'ReasoningEffortId'> -``` - -```ts type-equiv -/** Display metadata for one adapter-owned reasoning effort. */ -interface LlmReasoningEffortInfo { - /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ - id: ReasoningEffortId - /** Human-readable effort name for selectors and diagnostics. */ - name: string - /** Optional user-facing distinction from otherwise similar efforts. */ - description?: string -} -``` - -```ts type-equiv -/** Selectable reasoning efforts for one exact provider/model route. */ -interface LlmModelReasoningInfo { - /** Supported efforts in adapter-preferred display order. */ - efforts: readonly LlmReasoningEffortInfo[] - /** - * Adapter-configured default materialized into requests when callers omit - * an effort. Absence preserves the provider's own default. - */ - defaultEffort?: ReasoningEffortId -} -``` - -```ts type-equiv -/** Exact-route model metadata resolved by its owning adapter. */ -interface LlmResolvedModelInfo extends LlmModelInfo { - /** Provider-owned context capacity when known. */ - context?: LlmModelContext - /** Adapter-configured per-request output cap materialized when callers omit one. */ - defaultMaxTokens?: number - /** Adapter-owned selectable reasoning levels when exposed. */ - reasoning?: LlmModelReasoningInfo -} -``` - -```ts type-equiv -/** A single model request, fully assembled. */ -interface GenerateOptions { - /** Registered provider route selecting the adapter instance. */ - provider: string - model: string - /** Adapter-owned reasoning effort selected for this exact model. */ - reasoningEffort?: ReasoningEffortId - /** - * Ordered conversation messages, exactly as the provider sees them (after - * the `system` slot). A loop-built request assembles them as - * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. - */ - messages: Message[] - /** System prompt text (adapters map to the provider's system slot). */ - system?: string - /** Tool schemas (adapters map to the provider's `tools` field). */ - tools?: ToolSchema[] - temperature?: number - maxTokens?: number - /** - * Stop sequences: generation halts as soon as the model produces any one of - * these strings (adapters map to the provider's stop field, e.g. OpenAI - * `stop`). The stop string itself is not included in the output. - */ - stop?: string[] - signal?: AbortSignal - /** - * Session identity stamped by the loop for listener routing. Adapters ignore - * it; replay uses it to keep concurrent parent and child cursors independent. - */ - sessionId?: Branded<'SessionId'> - /** - * Provider-neutral classification for an auxiliary model call. Adapters may - * map the purpose to model-hidden transport metadata or purpose-specific - * generation policy. Ordinary conversation requests leave it unset. - */ - purpose?: 'compaction' | 'session-title' -} -``` - -Why a model response stopped is a merge-extensible reason. Terminal provider failures carry the streaming contract's [`LlmFailure`](llm-streaming.md#llmfailure): - -```ts type-equiv -/** - * Why a model response stopped. - * Merge-extensible so adapters can surface provider-specific reasons. - */ -interface FinishReasonMap { - 'stop': { kind: 'stop' } - 'tool-calls': { kind: 'tool-calls' } - 'max-tokens': { kind: 'max-tokens' } - 'aborted': { kind: 'aborted'; failure: LlmFailure } - 'error': { kind: 'error'; failure: LlmFailure } -} -``` - -`FinishReason = FinishReasonMap[keyof FinishReasonMap]`. `TokenUsage` (per-call accounting with disjoint cache fields) is detailed on [llm-streaming.md](llm-streaming.md). - -`GenerateOptions.tools` carries `ToolSchema` — the JSON-schema description of a tool, as sent to the model. It is declared in dsh-llm (not dsh-tools) precisely because it is part of the request the loop assembles every step: - -```ts type-equiv -/** - * JSON-schema description of a tool, as sent to the model. - * - * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; - * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import - * it from this package. - */ -interface ToolSchema { - name: string - description: string - /** JSON Schema object for the arguments. */ - parameters: Record -} -``` - -The model-facing `ToolSchema` is the wire shape; the registered `ToolDefinition` that produces it (schema + `execute`) is on [tools.md](tools.md). - -### The request envelope: `LlmCallConfig` and the logged header - -The loop builds each request from logged state. `EpochHeader` records call config, adapter-default provenance, rendered prompt, and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md). - -`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus provenance under the turn signal. The prepared call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests. - -On the wire, a loop-built request reads the `system` slot (the rendered prompt assembly) followed by the derived history — the boundary snapshot, whose tail is the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request. - -```ts type-equiv -/** - * Provider, model, reasoning effort, and sampling scalars of one conversation's - * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; - * the loop builds requests from the logged header rather than accepting these - * per call. - */ -interface LlmCallConfig { - provider: string - model: string - reasoningEffort?: ReasoningEffortId - temperature?: number - maxTokens?: number - stop?: string[] -} -``` - -```ts type-equiv -/** - * Effective config fields supplied by exact-model adapter resolution rather - * than by the caller's request proposal. - */ -interface LlmCallConfigAdapterDefaults { - reasoningEffort?: true - maxTokens?: true -} -``` - ## Sessions A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`), not stored separately. The event vocabulary derives from `SessionEventMap`: @@ -531,7 +89,7 @@ Source: [`packages/core/session/src/types.ts`](../../packages/core/session/src/t * * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional: * they only exist on {@link SurfaceEventType} variants (`user/message`, - * `assistant/message`, `tool/result`). + * `assistant/message`, `tool/result`, `steering/message`). * Non-surface events (boundary markers, chunks, usage, errors) never carry * surface metadata — the compiler enforces this at `Session.append()` * call sites. @@ -559,7 +117,7 @@ type SessionEvent = { }[T] ``` -The session event variants, `deriveMessages()` projection rules, `TurnEndReason` vocabulary, and execution-enclosure and standalone-event rules are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**. +The twelve event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`, `todo/write`, `request/header`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the execution-enclosure and standalone-event rules are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**. ## The agent handle @@ -568,11 +126,72 @@ The session event variants, `deriveMessages()` projection rules, `TurnEndReason` Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) ```ts type-equiv -/** One of the two ordered pending-message lists owned by an agent. */ -type InboxTarget = 'next-turn' | 'next-step' +/** + * Which inbox queue a {@link Agent.send} item joins: + * - `next-turn` — the item becomes its own turn, claimed at a turn boundary. + * - `next-step` — during prompt admission or an open turn, the item stages for + * the next safe step boundary; otherwise it is promoted per its `wakeup` + * flag. + */ +type SendTarget = 'next-turn' | 'next-step' ``` -Every pending occurrence is its `UserMessage`; `MessageId` is the sole identity. `Inbox.append`, `prepend`, `replace`, `remove`, `clear`, `splice`, and `claim` record normalized durable `agent/inbox/spliced` mutations and reject duplicate pending ids. `replace(messageId, newMessage)` and `remove(messageId)` locate the pending message across both lists; replacement may change identity and emits the old message as discarded followed by the new message as inserted. Ordinary removals and `clear()` are cancellations. `claim(target)` removes the proposed step batch — all `next-step` input plus, at a turn boundary, one `next-turn` message — through pure deletion splices without emitting discarded notifications, and the loop separately emits per-message claimed notifications. Whole-queue consumers such as UI projections reconstruct `nextTurn` and `nextStep` from the durable splices, while consumers following one message use the exact `agent/inbox/inserted`, `claimed`, and `discarded` notifications. +```ts type-equiv +/** Resolved inbox placement reported when an accepted message is enqueued. */ +type InboxPlacement = 'queued' | 'steering' +``` + +`InboxItemId` is a process-local branded string minted for each accepted FIFO occurrence. It is intentionally distinct from `MessageId`: sending the same immutable message twice creates two independently addressable pending items. + +```ts type-equiv +/** One independently addressable accepted occurrence in an agent inbox. */ +interface InboxItem { + /** Agent-loop-minted occurrence identity. */ + readonly id: InboxItemId + /** Identified message delivered by the caller. */ + readonly message: UserMessage + /** Acceptance-time FIFO classification. */ + readonly placement: InboxPlacement +} +``` + +```ts type-equiv +/** A user-requested mutation of one still-pending queued occurrence. */ +type InboxAction = + | { readonly kind: 'edit'; readonly content: ContentBlock[] } + | { readonly kind: 'remove' } + | { readonly kind: 'steer' } +``` + +```ts type-equiv +/** Result of applying an inbox action at the synchronous ownership boundary. */ +type InboxActionResult = 'applied' | 'not-found' | 'steer-unavailable' +``` + +```ts type-equiv +/** + * Options for the unified {@link Agent.send} primitive over the + * (`target` × `wakeup`) matrix. Named presets: {@link Agent.followup} + * (`next-turn`/wakeup), {@link Agent.steer} (`next-step`/wakeup), and + * {@link Agent.inject} (`next-step`/no-wakeup). + * + * The object is complete so routing policy is explicit. + */ +interface SendOptions { + /** Queue the item joins. */ + target: SendTarget + /** + * Whether this item makes the model run: wake a parked driver (`next-turn`) + * or force a continuation step (`next-step` while running). A `false` + * `next-turn` item queues without waking; a `false` + * `next-step` item attaches durable context without forcing another step + * (the injection preset). + */ + wakeup: boolean +} +``` + +The fixed-preset aliases own `target` and `wakeup`; their already identified `UserMessage` carries role, content, and provenance. Its `MessageId` remains stable when an edit replaces content or strict steer transfers the immutable message. The original queued occurrence ends and strict steer accepts a new steering occurrence with a distinct `InboxItemId`. Injection bypasses the FIFOs and never appears on inbox lifecycle events. ```ts type-equiv /** Options for {@link Agent.cancel}. */ @@ -580,25 +199,28 @@ interface CancelOptions { /** * Preserve queued and steering inbox items instead of discarding them. The * active turn is still aborted, but un-started and pending work survives for a - * later turn and no canceled inbox splice is logged. + * later turn and no `agent/inbox/discard` fires. */ - keepInbox?: boolean | undefined + keepInbox?: boolean } ``` +`SteeringReceipt.outcome` always resolves. `admitted` identifies the turn and step whose immutable request history contains that exact message; `rejected` means lifecycle or terminal policy discarded it first. Synchronous input validation still throws from `steer()`. + ```ts type-equiv -/** Why an active agent driver was cancelled. */ +/** Stable runtime cause accepted by {@link Agent.cancel}. */ type AgentCancelCause = | { readonly kind: 'user' } | { readonly kind: 'parent' } - | { readonly kind: 'hook'; readonly reason: string } - | { readonly kind: 'disposed' } ``` -`Agent` is an interface over the public live-agent contract. Its unified `send` method exposes target and wakeup routing directly; `followup`, `steer`, and `inject` are fixed-preset aliases. +`Agent` is an interface over the public live-agent contract. Concrete drivers own the `followup`/`steer`/`inject` aliases and route them through `send`'s (`target` × `wakeup`) matrix. ```ts type-equiv -/** Public live-agent handle. */ +/** + * Public live-agent handle with aliases over the unified delivery primitive. + * @typert object + */ interface Agent { /** The single identity shared with {@link session}. */ readonly id: SessionId @@ -606,28 +228,78 @@ interface Agent { readonly options: AgentOptions /** The live session this agent drives; its log is the durable source of truth. */ readonly session: Session - /** The agent-owned projection of durable pending work. */ - readonly inbox: Inbox /** The current lifecycle state, mirrored on every `agent/status` transition. */ readonly status: AgentStatus + /** + * Whether a `next-step` send currently stages for prompt admission or the + * open turn. Unlike {@link status}, this excludes admission exit and turn + * settlement, when a waking `next-step` send becomes a queued follow-up. + */ + readonly acceptsNextStep: boolean /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */ readonly ctx: Context + /** + * The unified delivery primitive over the (`target` × `wakeup`) matrix. + * It routes the caller's typed content and source as follows: + * + * - `next-turn` queues an item that becomes the sole ordinary message of its + * own FIFO-ordered turn; `wakeup:true` wakes a + * parked driver, while `wakeup:false` queues without waking. + * - `next-step` with `wakeup:true` stages steering during prompt admission + * or an open turn; outside that window it falls back to a woken + * `next-turn`. + * - `next-step` with `wakeup:false` injects durable model-facing context + * without running the model: admission or an open turn stages it for the + * next safe log position, while an injection outside that window appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. + * The agent publishes or queues the identified frozen message as-is. + * @param message - identified model-facing content and its producer provenance. + * @param options - target queue and wakeup decision. + */ + send(message: UserMessage, options: SendOptions): void + + /** + * Reserve admission of the next ordinary turn while this agent is idle, so an + * operation can mutate durable history before any queued prompt derives a + * request from it. Already-accepted waking work has right of way, including a + * send whose wake is still a pending microtask. Later sends keep their + * ordinary placement, FIFO order, and `wakeup` facts, and + * {@link acceptsNextStep} stays `false`, so a waking `next-step` send becomes + * a queued follow-up rather than steering; cancellation and disposal may + * still discard them. {@link inject} is not withheld. {@link whenIdle} treats + * a live reservation as activity, while lifecycle teardown does not await it. + * @returns the idempotent release, or `undefined` when the agent is running, already reserved, or already committed to waking work. + */ + reserveTurnAdmission(): (() => void) | undefined + + /** + * Mutate one still-pending queued occurrence synchronously. Editing preserves + * the message identity and queue position; removal publishes its terminal + * discard. Steer strictly transfers the message into the current next-step + * window, or returns `steer-unavailable` without changing the queued + * occurrence. Steering occurrences and driver-claimed items return + * `not-found`. + * @param id - independently addressable queued occurrence. + * @param action - edit, remove, or strict steer operation. + * @returns the applied outcome or the reason no mutation occurred. + */ + updateInbox(id: InboxItemId, action: InboxAction): InboxActionResult + /** * Clear queued and steering work — unless `keepInbox` — and abort the active - * turn or between-turn task. The first cause wins for that activity. With no - * active activity, cancellation is a no-op and does not arm later work. - * @param cause - the stable caller intent carried by the active operation signal. + * turn. An effective call first emits `agent/cancel-requested` with the + * resolved typed cause. The first cause wins for the active turn, and + * `whenIdle()` resolves after cancellation reaches quiescence. Idle + * cancellation is a no-op and does not arm later work. + * @param cause - the stable caller intent carried by the current turn signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ cancel(cause: AgentCancelCause, options?: CancelOptions): void - /** - * Resolve after the current whole-agent activity reaches quiescence. This - * follows replacement work started before the observed driver retires, - * but does not identify the settlement of any particular message. - * @returns fulfillment after no active driver or maintenance task remains. - */ + /** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */ whenIdle(): Promise /** @@ -662,29 +334,35 @@ interface Agent { followup(message: UserMessage): void /** - * Submit steering for the nearest step. An idle driver starts a turn; - * a running driver consumes it at its next step boundary. - * A rejected step leaves steering parked in the inbox until the next - * wake; cancellation or disposal may discard pending steering. + * Submit steering with a message-owned admission receipt — the + * `next-step`/wakeup preset of {@link send}. During prompt admission or an + * open turn, the message waits in the steering FIFO until a committed step + * snapshots it; outside that window it enters the ordinary queued FIFO. The + * receipt resolves `admitted` only after the message joins that step's + * immutable request history, or `rejected` when terminal policy, + * cancellation, or disposal discards it first. A non-terminal turn close may + * leave it staged for a later admitted prompt without settling the receipt. * @param message - identified steering content and its producer provenance. + * @returns the receipt for this exact message's eventual admission outcome. */ - steer(message: UserMessage): void + steer(message: UserMessage): SteeringReceipt /** - * Queue model-facing context for the next pre-step without waking the - * driver. A running driver claims it at the nearest later step boundary; - * idle drivers leave it pending until follow-up or steering - * wakes them. It may miss a request whose pre-step already claimed its - * batch. Cancellation or disposal may discard pending context. + * Append model-facing context without running the model — the + * `next-step`/no-wakeup preset of {@link send}. Admission or an open turn + * stages it at the next safe log position; outside that window it appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. * @param message - identified injected context and its producer provenance. */ inject(message: UserMessage): void } ``` -`AgentStatus` is `'idle' | 'running'`, and `SessionId` is branded. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. `followup()` returns no handle: its `MessageId` identifies durable inbox insertion, claim, and discard facts, not a later assistant output or turn ending. `whenIdle()` observes the whole agent, so callers may call a receipt-to-idle interval a run only when they explicitly own that interval ([decision](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)). `AgentOptions` is merge-extensible: core declares `provider?`, `model?`, and `maxTokens?` (dispatch requires provider and model after `agent/request`). When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default. +`AgentStatus` is `'idle' | 'running'`, and `SessionId` is branded. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. `acceptsNextStep` is the narrower routing predicate for callers that must choose between steering the current admission/turn and submitting a fresh admitted prompt. A live turn-admission reservation is quiescence-relevant without changing `status` or turning later queue entries into steering; its only authority is to defer the next driver claim until release. `AgentOptions` is merge-extensible: core declares `provider?`, `model?`, and `maxTokens?` (dispatch requires provider and model after `agent/request`). When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default. -The cause is a TypeScript-enforced same-process input. An active cancellation holder copies it into the runtime-only `AbortSignal.reason`; a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result. +The cause is a TypeScript-enforced same-process input. An active `TurnCancellation` holder copies its discriminant into the runtime-only `AbortSignal.reason` and is retired before `turn/end` publication; the frozen `AbortSignal.reason` remains readable after that retirement. Only the loop reads the cause (`user`, `parent`, or lifecycle-only `disposed`) back off its own machine-private signal at settlement — there is no public reader, and a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result. The [event taxonomy](../architecture.md#event) owns the `agent/*` lifecycle, checkpoint, and waterfall contracts. Turn and step boundaries are durable session events rather than agent emits. @@ -694,19 +372,22 @@ The process-local initiator carried by `ctx.agents` is the exact `Agent` above, ## Interception decisions -Pre-step decisions use the same identified `UserMessage` shape as durable user-role input. The entered batch is authoritative and preserves every message's identity and provenance. Hook bridges map their native decision fields onto this typed result. +Prompt and post-tool decisions use the same identified `UserMessage` shape as durable user-role input. Each `additionalContexts` entry becomes a separate `user/message`, preserving its identity and provenance. Hook bridges map their native decision fields onto these typed results. Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) -`agent/pre-step` receives one payload carrying the exclusive claimed batch (`messages`), the proposed step's coordinates (`turn`, `step`), and the current turn's cancellation `signal`. The initial proposal runs inside an open turn before any step; a tool continuation may submit an empty claimed batch between steps: - -It returns a `PreStepDecision`. Reject opens no step. Enter supplies the complete message batch appended after `step/start`; claimed messages omitted by the final decision remain removed, while input inserted after the claim stays pending: +`agent/prompt-submit` returns a `PromptDecision` before a turn opens. Allow may rewrite the claimed prompt or attach `additionalContexts`; block rejects admission without creating turn events: ```ts type-equiv -/** Whether and with which messages the loop enters a proposed step. */ -type PreStepDecision = - | { kind: 'reject' } - | { kind: 'enter'; messages: UserMessage[] } +/** + * Prompt interception result. `allow.content` replaces the prompt, while + * `additionalContexts` appends model-facing context before the turn starts. + * An `allow` returned by a listener is authoritative: a listener wrapping + * `next()` preserves both fields unless it intentionally replaces them. + */ +type PromptDecision = + | { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: UserMessage[] } + | { kind: 'block'; reason: string } ``` `agent/request-error` runs after a failed model step closes and before its turn closes. Listeners can repair durable state or await policy work while the failed turn's signal is still live. A handling listener returns `{ kind: 'retry' }` without calling `next()`; the default `undefined` leaves the failure terminal. @@ -716,7 +397,12 @@ type PreStepDecision = type RequestErrorAction = { kind: 'retry' } | undefined ``` -`agent/pre-step` is the single serial boundary before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain. +```ts type-equiv +/** Model-request failure with an optional machine-routable provider code. */ +type RequestError = Error & { code?: string } +``` + +`agent/step` is the single serial boundary before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain. `agent/session-start` carries a `SessionStartSource` (why the session lifecycle began; a bridge keys its SessionStart matcher on it): @@ -776,7 +462,7 @@ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise @@ -954,6 +640,29 @@ Source: [`packages/core/agent/src/index.ts:242`](../../packages/core/agent/src/i ### `agent/*` events + + +#### `agent/cancel-requested` — emit + +Effective broad cancellation was requested, before queued/outbox work is cleared or the active turn is aborted. This observe-only notification cannot veto cancellation; listener failures are contained. + +```ts cordis-catalog +/** + * Effective broad cancellation was requested, before queued/outbox work + * is cleared or the active turn is aborted. This observe-only notification + * cannot veto cancellation; listener failures are contained. + * @param agent - the agent whose current work is being cancelled. + * @param cause - the explicit typed cancellation cause. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/cancel-requested'(this: Scoped, agent: Agent, cause: AgentCancelCause): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:353`](../../packages/core/agent/src/types.ts) + #### `agent/created` — emit @@ -967,11 +676,11 @@ A fully configured agent and live session were published. Setup is composition-o * Synchronous listener failure vetoes publication, while returned-promise * rejection is reported. Detach requested during dispatch waits until every * creation listener has observed the stable entry. - * @param payload.agent - the newly registered agent with its live session and completed setup. + * @param agent - the newly registered agent with its live session and completed setup. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/created'(this: Scoped, payload: { agent: Agent }): void +'agent/created'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -989,11 +698,11 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco * An agent left the registry; AgentLoop emits this after driver quiescence * and scoped-registration unwind, but before session detachment. Custom * registry users own their driver-ordering contract. - * @param payload.agent - the exact agent removed from the registry. + * @param agent - the exact agent removed from the registry. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/disposed'(this: Scoped, payload: { agent: Agent }): void +'agent/disposed'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -1004,111 +713,138 @@ Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/t #### `agent/error` — emit -A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. +A step or turn errored. The machine reports a failure here (plus the logger) even when the error has no in-turn position for a durable record. ```ts cordis-catalog /** - * A step or turn errored. The machine reports a failure here even when - * the error has no in-turn position for a durable record. - * @param payload.agent - the agent whose turn errored. - * @param payload.turn - the turn in which the failure surfaced. - * @param payload.step - the step at which the failure surfaced. - * @param payload.error - the failure, verbatim. + * A step or turn errored. The machine reports a failure here (plus the + * logger) even when the error has no in-turn position for a durable record. + * @param agent - the agent whose turn errored. + * @param turn - the turn in which the failure surfaced. + * @param step - the step at which the failure surfaced. + * @param error - the failure, verbatim. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void +'agent/error'(this: Scoped, agent: Agent, turn: number, step: number, error: unknown): void ``` Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/claimed` — emit +#### `agent/inbox/dequeue` — emit -One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. +The driver claimed one item out of the inbox: a queued item at a turn boundary, or steering drained between steps. Fires after the item leaves its FIFO and before it becomes a durable message. ```ts cordis-catalog /** - * One message left the inbox inside its open turn. If the proposed step - * is rejected, the claimed message ends here: it is neither discarded nor - * re-emitted as a user/message, and the turn closes without a step. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the claimed message. - * @param payload.turn - the owning turn. + * The driver claimed one item out of the inbox: a queued item at a turn + * boundary, or steering drained between steps. Fires after the item leaves + * its FIFO and before it becomes a durable message. + * @param agent - the agent whose inbox item was claimed. + * @param item - the exact claimed occurrence. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void +'agent/inbox/dequeue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/discarded` — emit +#### `agent/inbox/discard` — emit -One message was discarded from the live inbox. +Pending inbox items were dropped without delivering them, so every enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, emits this after `agent/cancel-requested` when applicable and before aborting the active work. Fires once per drop with every dropped item. ```ts cordis-catalog /** - * One message was discarded from the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the discarded message. + * Pending inbox items were dropped without delivering them, so every + * enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR + * `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, + * emits this after `agent/cancel-requested` when applicable and before + * aborting the active work. Fires once per drop with every dropped item. + * @param agent - the agent whose inbox items were dropped. + * @param items - the discarded occurrences in FIFO order (queued then steering); never empty. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/discard'(this: Scoped, agent: Agent, items: InboxItem[]): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/inserted` — emit +#### `agent/inbox/enqueue` — emit -One message entered the live inbox. +An item entered the queued or steering inbox. `placement` is the acceptance-time routing result; listeners must not reconstruct it from later agent or session state. ```ts cordis-catalog /** - * One message entered the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the inserted message. + * An item entered the queued or steering inbox. `placement` is the + * acceptance-time routing result; listeners must not reconstruct it from + * later agent or session state. + * @param agent - the owning agent. + * @param item - accepted occurrence, message, and resolved placement. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/enqueue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) - + -#### `agent/pre-step` — waterfall +#### `agent/inbox/update` — emit -Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. +A still-pending queued item changed content. The item id, placement, and position remain stable while the event carries the replacement message. ```ts cordis-catalog /** - * Reject a proposed step or replace the messages that enter it. Calling - * `next()` preserves the current messages. - * @param payload.agent - the agent proposing the step. - * @param payload.messages - messages removed from the inbox for this step. - * @param payload.turn - the turn that will own the step. - * @param payload.step - the step proposed by the loop. - * @param payload.signal - the current turn's cancellation signal. + * A still-pending queued item changed content. The item id, placement, and + * position remain stable while the event carries the replacement message. + * @param agent - the owning agent. + * @param item - the complete post-update occurrence. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/update'(this: Scoped, agent: Agent, item: InboxItem): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:321`](../../packages/core/agent/src/types.ts) + + + +#### `agent/prompt-submit` — waterfall + +Allow, rewrite, or block one claimed prompt before it becomes a user message or opens a turn. Call `next()` for the unchanged default. The signal controls only this admission attempt; listeners may cooperate with it but must not retain it for a later attempt or turn. + +```ts cordis-catalog +/** + * Allow, rewrite, or block one claimed prompt before it becomes a user + * message or opens a turn. Call `next()` for the unchanged default. The + * signal controls only this admission attempt; listeners may cooperate with + * it but must not retain it for a later attempt or turn. + * @param agent - the agent whose turn claimed the message. + * @param message - the frozen claimed message, including identity and source. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/prompt-submit'(this: Scoped, agent: Agent, message: UserMessage, signal: AbortSignal, next: () => Promise): Promise ``` Types: [Scoped](scope.md) · [UserMessage](session.md) @@ -1127,17 +863,17 @@ Replace the frozen call configuration. `await next()` yields the config the mach * the machine would use (agent options on the first request, the logged * header afterwards); return a replacement to switch. Model-visible * content must use logged channels; this seam cannot mutate messages. - * @param payload.agent - the agent making the model call. - * @param payload.turn - the open turn number. - * @param payload.step - the step whose request this is. - * @param payload.signal - the current turn's explicit abort signal. + * @param agent - the agent making the model call. + * @param turn - the open turn number. + * @param step - the step whose request this is. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/request'(this: Scoped, agent: Agent, turn: number, step: number, signal: AbortSignal, next: () => Promise): Promise ``` -Types: [Scoped](scope.md) +Types: [LlmCallConfig](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) @@ -1145,25 +881,28 @@ Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/t #### `agent/request-error` — waterfall -Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. +Handle a model-request failure after its failed step has closed but before the failed turn closes. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns the error, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. ```ts cordis-catalog /** - * Handle one failed model-request attempt before the loop retries or closes - * its step. A listener returns `{ kind: 'retry' }` without calling `next()` - * when it owns recovery, or calls `next()` to delegate. The default - * `undefined` leaves the failure terminal. - * @param payload.agent - the agent whose request failed. - * @param payload.turn - the turn containing the failed request. - * @param payload.step - the step containing the failed request attempt. - * @param payload.provider - the provider selected for the failed request. - * @param payload.failure - serializable facts normalized at the final adapter boundary. - * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. - * @param payload.signal - the turn abort signal. + * Handle a model-request failure after its failed step has closed but + * before the failed turn closes. A listener returns `{ kind: 'retry' }` + * without calling `next()` when it owns the error, or calls `next()` to + * delegate. The default `undefined` leaves the failure terminal. + * @param agent - the agent whose request failed. + * @param turn - the open turn number. + * @param step - the failed step number. + * @param error - the original model-request failure. + * @param failure - serializable facts normalized at the final adapter boundary. + * @param priorFailures - immutable failures that already authorized another + * retry turn in this consecutive sequence. + * @param retryPolicy - immutable policy of the adapter registration that served + * the failed request, or `undefined` if no final adapter served it. + * @param signal - the turn abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise +'agent/request-error'(this: Scoped, agent: Agent, turn: number, step: number, error: RequestError, failure: LlmFailure, priorFailures: readonly LlmFailure[], retryPolicy: ResolvedRetryPolicy | undefined, signal: AbortSignal, next: () => Promise): Promise ``` Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) @@ -1182,12 +921,12 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to * `agent.inject()` to seed model-facing context. This is a notification, not * a veto; disposal requested by a lifecycle owner is rechecked before the * driver starts. - * @param payload.agent - the agent whose session lifecycle began. - * @param payload.source - why the session started (fresh startup, resume, …). + * @param agent - the agent whose session lifecycle began. + * @param source - why the session started (fresh startup, resume, …). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void +'agent/session-start'(this: Scoped, agent: Agent, source: SessionStartSource): void ``` Types: [Scoped](scope.md) @@ -1198,19 +937,18 @@ Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/t #### `agent/status` — emit -Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. +Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` synchronously; drive lifecycle from this event. ```ts cordis-catalog /** - * Agent status changed (`idle` ⇄ `running`). A waking delivery enters - * `running` synchronously after reserving cancellation; `idle` means no - * driver remains scheduled or active. - * @param payload.agent - the agent whose status flipped. - * @param payload.status - the status just entered (the transition's destination). + * Agent status changed (`idle` ⇄ `running`). `send()` does not enter + * `running` synchronously; drive lifecycle from this event. + * @param agent - the agent whose status flipped. + * @param status - the status just entered (the transition's destination). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void +'agent/status'(this: Scoped, agent: Agent, status: AgentStatus): void ``` Types: [Scoped](scope.md) @@ -1221,7 +959,7 @@ Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/t #### `agent/turn-stopping` — serial -The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. ```ts cordis-catalog /** @@ -1231,17 +969,14 @@ The turn is about to close: the model owes no response (no live tool calls, no f * re-reads its inbox: fresh steering runs another step, none closes the * turn. Data decides, so listener order cannot change the outcome. The * inverse control (stop a tool loop early) is data too: a tool result - * carrying `concludesTurn` ends the turn at its step. The conclusion - * never short-circuits already-submitted next-step work: same-step - * `additionalContexts` or racing steering still runs, and the turn - * closes only when that inbox drains. - * @param payload.agent - the agent whose turn is at its stop boundary. - * @param payload.turn - the turn about to close. - * @param payload.signal - the current turn's explicit abort signal. + * carrying `concludesTurn` ends the turn at its step. + * @param agent - the agent whose turn is at its stop boundary. + * @param turn - the turn about to close. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode serial */ -'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void +'agent/turn-stopping'(this: Scoped, agent: Agent, turn: number, signal: AbortSignal): Promise | void ``` Types: [Scoped](scope.md) @@ -1264,12 +999,12 @@ A declarative agent entry failed before it could publish a live agent. Consumers * Consumers that buffer work for the configured identity use this * transient signal to reject that work instead of waiting forever. Normal * factory teardown suppresses failures from the cancelled startup attempt. - * @param payload.sessionId - exact shared agent/session identity that failed startup. - * @param payload.error - persistence, setup, or publication failure. + * @param sessionId - exact shared agent/session identity that failed startup. + * @param error - persistence, setup, or publication failure. * @mode emit */ -'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void +'agent-loop/config-start-failed'(sessionId: SessionId, error: unknown): void ``` -Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) +Source: [`packages/core/agent-loop/src/index.ts:157`](../../packages/core/agent-loop/src/index.ts) diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index fff5bd502c..276dc4f99f 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -1,22 +1,29 @@ -# 核心数据结构 +# 核心 [English](core.md) | 中文 -本页编目 DeepSeek Harness 的**核心**数据结构:每个主干类型代表什么、它的字面形状,以及完整细节在哪里。它与 [architecture.md](../architecture.md) 互补——后者描述*行为*(服务映射、会话/轮次/步骤生命周期、事件分类体系);本页描述行为所操作的*词汇*。同级的各子系统页面见[目录 README](README.md)。 +**核心**子系统即 [`packages/core`](../../packages/core/README.md)——每个组合都会启动的控制主干:事件溯源的会话日志、系统提示词组装、工具注册表、agent 词汇,以及驱动它们的具体循环。本页拥有主干的共享词汇——`Agent` 句柄及其投递与拦截契约,以及全仓通用的类型模式——并指引到该组的专属页面;目录索引见[子系统 README](README.md)。 -## 什么算"核心" +## 主干逐包速览 -harness 是一个微内核:一个极小的核心加上众多插件。大多数类型属于某一个插件或某一项能力。但有少数类型构成**主干**——agent loop(智能体循环)及其事件在*每一个*轮次中使用的语言,无论加载了哪些可选插件。这些就是"核心"。 +一个轮次按同一条循环流经六个包:[`agent-loop`](../../packages/core/agent-loop) 中的 driver 认领一条排队的 prompt,在[会话日志](session.md)(`ctx.sessions`)上开启轮次,通过 [system-prompt](system-prompt.md)(`ctx.systemPrompt`)组装请求前缀并从日志派生历史,经 [LLM seam](llm-streaming.md) 流式获取模型响应,经[工具注册表](tools.md)(`ctx.tools`)分发工具调用,并把每个模型可见的事实追加回日志,供下一步派生。 -精确地说,一个数据结构是**核心**的,当且仅当满足以下条件之一: +| 包 | 负责内容 | 页面 | +|---|---|---| +| `session/` | 只追加的 `SessionEvent` 日志与内存 store——唯一事实来源(`ctx.sessions`) | [session.md](session.md) | +| `system-prompt/` | 提示词段落与工具 schema 组装(`ctx.systemPrompt`) | [system-prompt.md](system-prompt.md) | +| `tools/` | 带作用域的工具注册表与受保护的执行流水线(`ctx.tools`) | [tools.md](tools.md) | +| `agent/` | `Agent` 接口、实时注册表、发起者作用域与 `agent/*` 事件词汇(`ctx.agents`) | 本页 | +| `agent-loop/` | 实现公开 `Agent` 契约的具体 driver(`ctx.agentLoop`) | 本页 | +| `scope/` | 注册表与循环用于构建按 agent 作用域的注册原语 | [scope.md](scope.md) | -1. 它流经 agent loop 主干——循环在每个轮次中持有、派生、流式输出或记录它(`Message`、`StreamChunk`、`SessionEvent`、`Agent` 句柄本身),与当前加载了哪些插件无关;**或者** -2. 它是插件作者面向某条流水线编写的代表性类型——`ToolDefinition`(每个工具*是什么*)。 +`scope/` 是这里唯一的非服务包:一个零依赖库(`createScope`/`scopeOf`/`scopeTarget`),在模块图中位于 `session/` 与 `system-prompt/` 之下,正是为了让它们消费它而不形成环。`agent-loop` 是 `agent` seam 的唯一具体实现,放在这里因为它是 harness 的默认产品循环;它在 `ctx.agents.withInitiator()` 内运行每个 driver。扩展插件依赖 `agent`——包括需要发起 Agent 时——而绝不直接依赖 `agent-loop`,因此循环保持可替换。把这条主干接成可运行 agent 的默认组合是 [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.md)。 -其他一切都记录在各自的子系统页面上,而非本页。划线的规则是:*你编写、持有或接收的类型是核心;为它提供类型推导、渲染或持久化的机制是其他页面的细节*。因此 `ToolDefinition` 是核心,但为它提供类型推导的 `ValueSchemaSpec`/`ParameterSchemaSpec` 机制、为它提供渲染意图的 `ToolCallView`/`ToolResultView` 词汇,以及存储事件日志的 `SessionPersistence` seam 都不是——它们各有自己的页面([目录](README.md))。 + +## 本页拥有什么 - +循环搬运的对话词汇——`Message`、`ContentBlock`、`StreamChunk`、模型请求——由 [`packages/llm`](../../packages/llm/README.md) 声明,记录在 [llm-streaming.md](llm-streaming.md);会话事件、提示词组装与工具词汇在上表该组的专属页面。留在本页的是所有部分共享的词汇:`Agent` 句柄及其投递、取消与拦截契约(由 `packages/core/agent` 声明)、`SessionEvent` 信封,以及每个子系统都遵循的两个类型模式。范围界定规则记录在[子系统目录 Agent Note](../../.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md):你编写、持有或接收的类型记录在其声明子系统所在之处;为它提供类型推导、渲染或持久化的机制留在该机制自己的页面。 ## `…Map → derived-union` 模式 @@ -39,13 +46,14 @@ declare module '@deepseek-ai/dsh-llm' { } ``` -五个规范 map 使用此模式;插件作者扩展它们: +六个规范 map 使用此模式;插件作者扩展它们: | Map | 包 | 派生 | 目录 | |---|---|---|---| -| `ContentBlockMap` | dsh-llm | `ContentBlock` | [下文](#content-blocks-and-messages) | -| `MessageSourceMap` | dsh-llm | `MessageSource` | [下文](#content-blocks-and-messages) | -| `FinishReasonMap` | dsh-llm | `FinishReason` | [下文](#the-model-request-and-result) | +| `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](llm-streaming.md#the-model-request-and-result) | +| `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](session.md) | | `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](session.md) | | `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](session.md) | @@ -68,460 +76,6 @@ type Branded = string & { readonly [BRAND]: B } 两个核心 ID 是 `CallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [tasks.md](tasks.md) 中的 `TaskId`。 - - -## 内容块与消息 - -一段对话由 `Message` 组成;一条消息是一个类型化**内容块**的数组。块的联合类型从 `ContentBlockMap` 派生。 - -源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ToolCallBlock`(`id: CallId`、`name`、原始 JSON `arguments`)、`ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。核心集仅限于每条交付路径都尊重的块——多模态内容(图像、音频等)没有核心块类型;需要的功能通过可合并扩展的 map 添加,同时提供适配器/UI/压缩支持。 - -源码:[`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) - -`Message` 是一个带标识且不可变的角色/来源/内容值。模型产生的 assistant 消息会在其来源中携带提供方/模型所有权与可选的适配器私有回放元数据: - -```ts type-equiv -/** Provider ownership and adapter-private replay data for an assistant message. */ -interface AssistantProvenance { - /** Provider route that produced the message. */ - provider: string - /** Provider model id that produced the message. */ - model: string - /** - * Lossless-JSON adapter state needed to replay the provider response. - * `LlmService` exposes it to a target adapter only when that adapter instance - * currently owns both this historical provider and the target provider. - */ - replayState?: unknown -} -``` - -```ts type-equiv -/** One immutable message representation shared by delivery, durable history, and model requests. */ -interface Message { - /** Stable identity preserved across every representation boundary. */ - readonly id: MessageId - /** Provider-neutral conversation role. */ - readonly role: 'system' | 'user' | 'assistant' - /** Exact model-facing blocks. */ - readonly content: ContentBlock[] - /** Required producer provenance. */ - readonly source: MessageSource -} -``` - -消息来源本身也是一个可合并扩展的和类型: - -```ts type-equiv -/** - * Where a message (or injected content) came from. - * Merge-extensible sum type — plugins add their own `kind`s. - */ -interface MessageSourceMap { - user: { kind: 'user' } - plugin: { kind: 'plugin'; plugin: string } & ContextFormed - model: ModelMessageSource - tool: ToolMessageSource -} -``` - -溯源与形态是相互独立的两根轴。`kind` 回答「由谁产生」;生产方可选混入的 `form` 回答「这是何种形态的信息」,因此多个生产方可以共用一种呈现,一个生产方在一次会话中也可以发出多种形态。该词汇表是语义的,逐个取值增长;未声明或无法识别的取值是有文档的默认,按不透明内容呈现: - -```ts type-equiv -/** - * What SHAPE of information a producer-supplied context carries, declared by - * the producer beside its provenance. - * - * `MessageSource.kind` answers *who produced this*; `form` answers *what kind - * of thing it is*, and the two axes are deliberately independent — several - * producers share one form (three snapshot producers today), and one producer - * may emit more than one form over a session. - * - * The vocabulary is SEMANTIC, never visual: a value states that the content is - * a file's instructions or a catalog of available items, and a consumer decides - * what that looks like. Colors, icons, ordering, and collapse defaults are the - * consumer's business and must not enter this union. It grows one value at a - * time as producers gain the structured fields their form needs; an absent or - * unknown value is the documented default, presented as opaque content. - */ -type ContextForm = - /** Instructions read out of workspace files the model is expected to follow. */ - | 'instructions' - /** A catalog of items available in this session, republished as it changes. */ - | 'catalog' - /** Current state, where a later snapshot from the same producer supersedes an earlier one. */ - | 'snapshot' - /** A one-off account of something that just happened; it supersedes nothing. */ - | 'notice' - /** A message another agent addressed to this one. */ - | 'relay' - /** Material lifted out of another session's log, possibly reduced on the way in. */ - | 'recall' -``` - -```ts type-equiv -/** One named contribution to a `snapshot`-form context, in assembly order. */ -interface ContextSnapshotSection { - /** The contributing subsystem's name. */ - readonly name: string - /** That contribution's model-facing text, exactly as assembled. */ - readonly text: string -} -``` - -```ts type-equiv -/** - * Producer-declared {@link ContextForm} and the fields that form requires, - * mixed into the source shapes that carry one. - * - * Discriminated by `form` so a producer cannot declare a shape without the - * facts that shape is presented from: a `notice` must record its one-line - * account, a `snapshot` its sections. Omitting `form` stays valid — an - * undeclared context is the documented default. - */ -type ContextFormed = - | { readonly form?: never } - | { readonly form: 'instructions' } - | { readonly form: 'catalog' } - | { - readonly form: 'snapshot' - /** The named contributions this snapshot assembled, in order. */ - readonly sections: readonly ContextSnapshotSection[] - } - | { - readonly form: 'notice' - /** One-line account of what happened, shown without expanding the row. */ - readonly summary: string - } - | { readonly form: 'relay' } - | { readonly form: 'recall' } -``` - -## 流式输出 - -适配器发出原始**分片**协议;循环记录分片(回放保真度),同时将同一批分片送入 `BlockAssembler` 以重建块和消息。`StreamChunk` 是基于 `type` 的封闭判别联合——`block-start`、`text-delta`、`reasoning-delta`、`tool-call-delta`、`block-end`、`usage`、`finish`。 - -完整联合类型、适配器契约(usage-before-finish、原始 JSON 工具参数、两条认可的错误路径)和 `BlockAssembler` 在 **[llm-streaming.md](llm-streaming.md)** 中。 - - - -## 模型请求 - -一次模型调用是一个完全组装好的 `GenerateOptions`。适配器以原始 `StreamChunk` 流作答;消费方用 `BlockAssembler` 组装它(见 [llm-streaming.md](llm-streaming.md))。 - -源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -提供方与模型发现使用小型、提供方无关的描述符。模型目录仅供参考:路由仍以已注册提供方为键,适配器也可以接受未列出的模型 id。 - -注册适配器会返回一个句柄:既是释放器,也带有原子的路由替换——路由集合由用户配置决定的插件正需要它。 - -```ts type-equiv -/** - * What {@link LlmService.registerAdapter} returns: the disposer, plus an - * atomic route replacement for the same adapter instance. - */ -interface AdapterRegistrationHandle { - /** Release every route this registration currently holds. */ - (): void - /** - * Replace this registration's routes with `providers`, keeping the same - * adapter instance. The candidate set is validated in full first — a - * conflict with another adapter, an invalid name, or bad provider metadata - * throws and leaves the current routes untouched — and the swap itself is - * one synchronous section, so no request can observe a gap. An empty array - * is legal here (a settings section that emptied holds zero routes while - * staying registered), unlike an empty initial registration. - * - * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration - * has been released: its routes are gone and its disposer has already run, - * so anything registered afterwards would have no owner left to release it. - * @param providers - the complete next route set for this registration. - */ - replace(providers: string[]): void -} -``` - -```ts type-equiv -/** Display metadata for one registered provider route. */ -interface LlmProviderInfo { - /** Provider route key used by {@link GenerateOptions.provider}. */ - id: string - /** Human-readable provider name for selectors and diagnostics. */ - name: string -} -``` - -适配器插件还会通过 `registerConfigurableProviders()` 声明哪些路由*可以*运行,并指明每条路由的用户设置分节,使配置界面能在任何路由注册之前就呈现休眠的提供方。 - -```ts type-equiv -/** - * One provider route an adapter plugin can activate through configuration, - * whether or not the route is currently registered. Configuration surfaces - * merge this directory with `listProviders()` to offer every configurable - * provider alongside its live/dormant state. - */ -interface LlmConfigurableProvider { - /** Provider route key this entry activates when configured. */ - provider: string - /** Human-readable provider name for configuration surfaces. */ - displayName: string - /** User-settings namespace whose section configures this provider. */ - settingsNs: string - /** - * Path from that namespace's section root to this provider's profile - * object; empty when the whole section is the profile. - */ - settingsPath: readonly string[] -} -``` - -```ts type-equiv -/** One adapter-discovered model; catalog membership is advisory, not request validation. */ -interface LlmModelInfo { - /** Provider route that owns this model entry. */ - provider: string - /** Model id passed to {@link GenerateOptions.model}. */ - id: string - /** Human-readable model name for selectors. */ - name: string - /** Optional user-facing distinction from otherwise similar models. */ - description?: string -} -``` - -界面正在起草的提供方既没有路由也没有 catalog,因此询问被单独描述:请求携带用户正在编辑的草稿,回复是界面可以采纳的候选,而不是它必须服务的 catalog。 - -```ts type-equiv -/** - * One interrogation of a provider endpoint that configuration has not stored - * yet. Configuration surfaces send the draft a user is still editing, so the - * request carries the endpoint and credential directly instead of naming a - * route: a provider being added has no route to name. - */ -interface LlmModelDiscoveryRequest { - /** - * Route the draft is editing, when it edits an existing one. A route whose - * adapter already knows its models answers from that knowledge instead of - * asking the endpoint — the adapter's own registry is the better answer, and - * it costs no network call. - */ - provider?: string - /** - * Endpoint to interrogate. Optional because a route the adapter already - * describes needs none; a route it does not must supply one. - */ - baseURL?: string - /** Wire protocol the endpoint speaks, when the draft names one. */ - api?: string - /** Credential for this interrogation alone; the harness never stores it. */ - apiKey?: string - /** Caller cancellation; implementations must settle promptly after it aborts. */ - signal?: AbortSignal -} -``` - -```ts type-equiv -/** - * One model an endpoint reports about itself. Every field but the id is - * optional because most provider listings disclose an id and nothing else; - * a surface adopting one of these still owes the capacities its adapter needs. - */ -interface LlmDiscoveredModel { - /** Model id the endpoint accepts. */ - id: string - /** Human-readable name when the endpoint supplies one. */ - name?: string - /** Maximum combined request and response context, when disclosed. */ - contextWindow?: number - /** Maximum output tokens, when disclosed. */ - maxTokens?: number -} -``` - -对正确性敏感的元数据与参考目录分开解析,并归服务该确切路由的适配器所有。上下文容量、适配器调用默认值和推理选项共用同一个确切模型结果,消费方因而无需重复执行权威模型解析。 - -```ts type-equiv -/** Provider-owned context capacity for one exact provider/model route. */ -interface LlmModelContext { - /** Maximum combined request and response context in tokens. */ - contextWindow: number -} -``` - -推理强度是另一项针对确切路由的能力。核心为标识符添加品牌类型,但不枚举其值;有序集合、展示名称和可选的部署默认值均由各适配器持有。 - -```ts type-equiv -/** Adapter-owned identifier for one model's selectable reasoning effort. */ -type ReasoningEffortId = Branded<'ReasoningEffortId'> -``` - -```ts type-equiv -/** Display metadata for one adapter-owned reasoning effort. */ -interface LlmReasoningEffortInfo { - /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ - id: ReasoningEffortId - /** Human-readable effort name for selectors and diagnostics. */ - name: string - /** Optional user-facing distinction from otherwise similar efforts. */ - description?: string -} -``` - -```ts type-equiv -/** Selectable reasoning efforts for one exact provider/model route. */ -interface LlmModelReasoningInfo { - /** Supported efforts in adapter-preferred display order. */ - efforts: readonly LlmReasoningEffortInfo[] - /** - * Adapter-configured default materialized into requests when callers omit - * an effort. Absence preserves the provider's own default. - */ - defaultEffort?: ReasoningEffortId -} -``` - -```ts type-equiv -/** Exact-route model metadata resolved by its owning adapter. */ -interface LlmResolvedModelInfo extends LlmModelInfo { - /** Provider-owned context capacity when known. */ - context?: LlmModelContext - /** Adapter-configured per-request output cap materialized when callers omit one. */ - defaultMaxTokens?: number - /** Adapter-owned selectable reasoning levels when exposed. */ - reasoning?: LlmModelReasoningInfo -} -``` - -```ts type-equiv -/** A single model request, fully assembled. */ -interface GenerateOptions { - /** Registered provider route selecting the adapter instance. */ - provider: string - model: string - /** Adapter-owned reasoning effort selected for this exact model. */ - reasoningEffort?: ReasoningEffortId - /** - * Ordered conversation messages, exactly as the provider sees them (after - * the `system` slot). A loop-built request assembles them as - * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. - */ - messages: Message[] - /** System prompt text (adapters map to the provider's system slot). */ - system?: string - /** Tool schemas (adapters map to the provider's `tools` field). */ - tools?: ToolSchema[] - temperature?: number - maxTokens?: number - /** - * Stop sequences: generation halts as soon as the model produces any one of - * these strings (adapters map to the provider's stop field, e.g. OpenAI - * `stop`). The stop string itself is not included in the output. - */ - stop?: string[] - signal?: AbortSignal - /** - * Session identity stamped by the loop for listener routing. Adapters ignore - * it; replay uses it to keep concurrent parent and child cursors independent. - */ - sessionId?: Branded<'SessionId'> - /** - * Provider-neutral classification for an auxiliary model call. Adapters may - * map the purpose to model-hidden transport metadata or purpose-specific - * generation policy. Ordinary conversation requests leave it unset. - */ - purpose?: 'compaction' | 'session-title' -} -``` - -模型响应为何停止由可合并扩展的原因表示。提供方终态失败携带流式契约的 [`LlmFailure`](llm-streaming.md#llmfailure): - -```ts type-equiv -/** - * Why a model response stopped. - * Merge-extensible so adapters can surface provider-specific reasons. - */ -interface FinishReasonMap { - 'stop': { kind: 'stop' } - 'tool-calls': { kind: 'tool-calls' } - 'max-tokens': { kind: 'max-tokens' } - 'aborted': { kind: 'aborted'; failure: LlmFailure } - 'error': { kind: 'error'; failure: LlmFailure } -} -``` - -`FinishReason = FinishReasonMap[keyof FinishReasonMap]`。`TokenUsage`(逐调用计量,含不相交的缓存字段)详见 [llm-streaming.md](llm-streaming.md)。 - -`GenerateOptions.tools` 携带 `ToolSchema`——工具的 JSON Schema 描述,发送给模型。它声明在 dsh-llm(而非 dsh-tools)中,正是因为它是循环每一步组装请求的一部分: - -```ts type-equiv -/** - * JSON-schema description of a tool, as sent to the model. - * - * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; - * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import - * it from this package. - */ -interface ToolSchema { - name: string - description: string - /** JSON Schema object for the arguments. */ - parameters: Record -} -``` - -面向模型的 `ToolSchema` 是协议格式;产出它的已注册 `ToolDefinition`(schema + `execute`)在 [tools.md](tools.md) 中。 - -### 请求信封:`LlmCallConfig` 与记录的 header - -循环从已记录状态构建每个请求。`EpochHeader` 通过完整的 `request/header` 快照记录调用配置、适配器默认值来源、渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。 - -`agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall 开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置及其来源。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。 - -在协议格式上,循环构建的请求先读取 `system` 槽位(渲染后的提示词组装),再读取派生历史——边界快照,其尾部在轮次首步是最新的 `user/message`,在后续步骤是上一步的工具结果。开发不变式针对每个循环构建的请求精确重算此等式。 - -```ts type-equiv -/** - * Provider, model, reasoning effort, and sampling scalars of one conversation's - * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; - * the loop builds requests from the logged header rather than accepting these - * per call. - */ -interface LlmCallConfig { - provider: string - model: string - reasoningEffort?: ReasoningEffortId - temperature?: number - maxTokens?: number - stop?: string[] -} -``` - -```ts type-equiv -/** - * Effective config fields supplied by exact-model adapter resolution rather - * than by the caller's request proposal. - */ -interface LlmCallConfigAdapterDefaults { - reasoningEffort?: true - maxTokens?: true -} -``` - ## 会话 `Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM(大语言模型)消息历史从日志*派生*(`deriveMessages()`),而非单独存储。事件词汇从 `SessionEventMap` 派生: @@ -537,7 +91,7 @@ interface LlmCallConfigAdapterDefaults { * * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional: * they only exist on {@link SurfaceEventType} variants (`user/message`, - * `assistant/message`, `tool/result`). + * `assistant/message`, `tool/result`, `steering/message`). * Non-surface events (boundary markers, chunks, usage, errors) never carry * surface metadata — the compiler enforces this at `Session.append()` * call sites. @@ -565,7 +119,7 @@ type SessionEvent = { }[T] ``` -会话事件变体、`deriveMessages()` 投影规则、`TurnEndReason` 词汇以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` seam、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.md)** 中。 +十二种事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`steering/message`、`todo/write`、`request/header`)、`deriveMessages()` 投影规则、`TurnTrigger`/`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` seam、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.md)** 中。 @@ -576,11 +130,72 @@ type SessionEvent = { 源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) ```ts type-equiv -/** One of the two ordered pending-message lists owned by an agent. */ -type InboxTarget = 'next-turn' | 'next-step' +/** + * Which inbox queue a {@link Agent.send} item joins: + * - `next-turn` — the item becomes its own turn, claimed at a turn boundary. + * - `next-step` — during prompt admission or an open turn, the item stages for + * the next safe step boundary; otherwise it is promoted per its `wakeup` + * flag. + */ +type SendTarget = 'next-turn' | 'next-step' ``` -每个待处理入队项就是其 `UserMessage`;`MessageId` 是唯一标识。`Inbox.append`、`prepend`、`replace`、`remove`、`clear`、`splice` 与 `claim` 会记录规范化的持久 `agent/inbox/spliced` 变更,并拒绝重复的待处理 id。`replace(messageId, newMessage)` 与 `remove(messageId)` 通过 `MessageId` 跨两份列表定位待处理消息;替换可以改变标识,并先将旧消息作为 discarded 发布,再将新消息作为 inserted 发布。普通删除和 `clear()` 都表示取消。`claim(target)` 通过无 outcome 的纯删除 splice 移除拟进入步骤的批次——全部 `next-step` 输入,外加轮次边界上的一条 `next-turn` 消息——且不发出 discarded 通知;循环另行逐条发出 claimed 通知。UI 投影等整体队列消费方通过持久 splice 重建 `nextTurn` 与 `nextStep`,而跟踪单条消息的消费方使用精确的 `agent/inbox/inserted`、`claimed` 与 `discarded` 通知。 +```ts type-equiv +/** Resolved inbox placement reported when an accepted message is enqueued. */ +type InboxPlacement = 'queued' | 'steering' +``` + +`InboxItemId` 是为每次获准进入 FIFO 的项铸造的进程本地品牌字符串。它有意区别于 `MessageId`:同一条不可变消息发送两次,会创建两个可独立寻址的待处理项。 + +```ts type-equiv +/** One independently addressable accepted occurrence in an agent inbox. */ +interface InboxItem { + /** Agent-loop-minted occurrence identity. */ + readonly id: InboxItemId + /** Identified message delivered by the caller. */ + readonly message: UserMessage + /** Acceptance-time FIFO classification. */ + readonly placement: InboxPlacement +} +``` + +```ts type-equiv +/** A user-requested mutation of one still-pending queued occurrence. */ +type InboxAction = + | { readonly kind: 'edit'; readonly content: ContentBlock[] } + | { readonly kind: 'remove' } + | { readonly kind: 'steer' } +``` + +```ts type-equiv +/** Result of applying an inbox action at the synchronous ownership boundary. */ +type InboxActionResult = 'applied' | 'not-found' | 'steer-unavailable' +``` + +```ts type-equiv +/** + * Options for the unified {@link Agent.send} primitive over the + * (`target` × `wakeup`) matrix. Named presets: {@link Agent.followup} + * (`next-turn`/wakeup), {@link Agent.steer} (`next-step`/wakeup), and + * {@link Agent.inject} (`next-step`/no-wakeup). + * + * The object is complete so routing policy is explicit. + */ +interface SendOptions { + /** Queue the item joins. */ + target: SendTarget + /** + * Whether this item makes the model run: wake a parked driver (`next-turn`) + * or force a continuation step (`next-step` while running). A `false` + * `next-turn` item queues without waking; a `false` + * `next-step` item attaches durable context without forcing another step + * (the injection preset). + */ + wakeup: boolean +} +``` + +固定预设的别名方法自带 `target` 与 `wakeup`;其已有标识的 `UserMessage` 会携带角色、内容与 provenance。编辑替换内容或严格 steering(中途引导)转移不可变消息时,其 `MessageId` 都保持稳定。原 queued 单次入队项会结束,严格 steering 则接受一个具有不同 `InboxItemId` 的新 steering 单次入队项。注入绕过两个 FIFO,从不出现在 inbox 生命周期事件中。 ```ts type-equiv /** Options for {@link Agent.cancel}. */ @@ -588,25 +203,28 @@ interface CancelOptions { /** * Preserve queued and steering inbox items instead of discarding them. The * active turn is still aborted, but un-started and pending work survives for a - * later turn and no canceled inbox splice is logged. + * later turn and no `agent/inbox/discard` fires. */ - keepInbox?: boolean | undefined + keepInbox?: boolean } ``` +`SteeringReceipt.outcome` 始终会解析。`admitted` 标识其不可变请求历史包含该确切消息的轮次与步骤;`rejected` 表示生命周期或终止策略先丢弃了该消息。同步输入校验仍会从 `steer()` 抛出异常。 + ```ts type-equiv -/** Why an active agent driver was cancelled. */ +/** Stable runtime cause accepted by {@link Agent.cancel}. */ type AgentCancelCause = | { readonly kind: 'user' } | { readonly kind: 'parent' } - | { readonly kind: 'hook'; readonly reason: string } - | { readonly kind: 'disposed' } ``` -`Agent` 是覆盖公开活跃 agent 契约的接口。它的统一 `send` 方法直接公开目标与唤醒路由;`followup`、`steer` 和 `inject` 是固定预设别名。 +`Agent` 是覆盖公开活跃 agent 契约的接口。具体驱动器拥有 `followup`/`steer`/`inject` 别名方法,并将它们经由 `send` 的(`target` × `wakeup`)矩阵路由。 ```ts type-equiv -/** Public live-agent handle. */ +/** + * Public live-agent handle with aliases over the unified delivery primitive. + * @typert object + */ interface Agent { /** The single identity shared with {@link session}. */ readonly id: SessionId @@ -614,28 +232,78 @@ interface Agent { readonly options: AgentOptions /** The live session this agent drives; its log is the durable source of truth. */ readonly session: Session - /** The agent-owned projection of durable pending work. */ - readonly inbox: Inbox /** The current lifecycle state, mirrored on every `agent/status` transition. */ readonly status: AgentStatus + /** + * Whether a `next-step` send currently stages for prompt admission or the + * open turn. Unlike {@link status}, this excludes admission exit and turn + * settlement, when a waking `next-step` send becomes a queued follow-up. + */ + readonly acceptsNextStep: boolean /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */ readonly ctx: Context + /** + * The unified delivery primitive over the (`target` × `wakeup`) matrix. + * It routes the caller's typed content and source as follows: + * + * - `next-turn` queues an item that becomes the sole ordinary message of its + * own FIFO-ordered turn; `wakeup:true` wakes a + * parked driver, while `wakeup:false` queues without waking. + * - `next-step` with `wakeup:true` stages steering during prompt admission + * or an open turn; outside that window it falls back to a woken + * `next-turn`. + * - `next-step` with `wakeup:false` injects durable model-facing context + * without running the model: admission or an open turn stages it for the + * next safe log position, while an injection outside that window appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. + * The agent publishes or queues the identified frozen message as-is. + * @param message - identified model-facing content and its producer provenance. + * @param options - target queue and wakeup decision. + */ + send(message: UserMessage, options: SendOptions): void + + /** + * Reserve admission of the next ordinary turn while this agent is idle, so an + * operation can mutate durable history before any queued prompt derives a + * request from it. Already-accepted waking work has right of way, including a + * send whose wake is still a pending microtask. Later sends keep their + * ordinary placement, FIFO order, and `wakeup` facts, and + * {@link acceptsNextStep} stays `false`, so a waking `next-step` send becomes + * a queued follow-up rather than steering; cancellation and disposal may + * still discard them. {@link inject} is not withheld. {@link whenIdle} treats + * a live reservation as activity, while lifecycle teardown does not await it. + * @returns the idempotent release, or `undefined` when the agent is running, already reserved, or already committed to waking work. + */ + reserveTurnAdmission(): (() => void) | undefined + + /** + * Mutate one still-pending queued occurrence synchronously. Editing preserves + * the message identity and queue position; removal publishes its terminal + * discard. Steer strictly transfers the message into the current next-step + * window, or returns `steer-unavailable` without changing the queued + * occurrence. Steering occurrences and driver-claimed items return + * `not-found`. + * @param id - independently addressable queued occurrence. + * @param action - edit, remove, or strict steer operation. + * @returns the applied outcome or the reason no mutation occurred. + */ + updateInbox(id: InboxItemId, action: InboxAction): InboxActionResult + /** * Clear queued and steering work — unless `keepInbox` — and abort the active - * turn or between-turn task. The first cause wins for that activity. With no - * active activity, cancellation is a no-op and does not arm later work. - * @param cause - the stable caller intent carried by the active operation signal. + * turn. An effective call first emits `agent/cancel-requested` with the + * resolved typed cause. The first cause wins for the active turn, and + * `whenIdle()` resolves after cancellation reaches quiescence. Idle + * cancellation is a no-op and does not arm later work. + * @param cause - the stable caller intent carried by the current turn signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ cancel(cause: AgentCancelCause, options?: CancelOptions): void - /** - * Resolve after the current whole-agent activity reaches quiescence. This - * follows replacement work started before the observed driver retires, - * but does not identify the settlement of any particular message. - * @returns fulfillment after no active driver or maintenance task remains. - */ + /** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */ whenIdle(): Promise /** @@ -670,29 +338,35 @@ interface Agent { followup(message: UserMessage): void /** - * Submit steering for the nearest step. An idle driver starts a turn; - * a running driver consumes it at its next step boundary. - * A rejected step leaves steering parked in the inbox until the next - * wake; cancellation or disposal may discard pending steering. + * Submit steering with a message-owned admission receipt — the + * `next-step`/wakeup preset of {@link send}. During prompt admission or an + * open turn, the message waits in the steering FIFO until a committed step + * snapshots it; outside that window it enters the ordinary queued FIFO. The + * receipt resolves `admitted` only after the message joins that step's + * immutable request history, or `rejected` when terminal policy, + * cancellation, or disposal discards it first. A non-terminal turn close may + * leave it staged for a later admitted prompt without settling the receipt. * @param message - identified steering content and its producer provenance. + * @returns the receipt for this exact message's eventual admission outcome. */ - steer(message: UserMessage): void + steer(message: UserMessage): SteeringReceipt /** - * Queue model-facing context for the next pre-step without waking the - * driver. A running driver claims it at the nearest later step boundary; - * idle drivers leave it pending until follow-up or steering - * wakes them. It may miss a request whose pre-step already claimed its - * batch. Cancellation or disposal may discard pending context. + * Append model-facing context without running the model — the + * `next-step`/no-wakeup preset of {@link send}. Admission or an open turn + * stages it at the next safe log position; outside that window it appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. * @param message - identified injected context and its producer provenance. */ inject(message: UserMessage): void } ``` -`AgentStatus` 为 `'idle' | 'running'`,`SessionId` 是品牌类型。dispose(资源释放)会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。`followup()` 不返回 handle:其 `MessageId` 标识持久 inbox 的插入、领取与丢弃事实,而不标识之后的助手输出或轮次结束。`whenIdle()` 观察整个 agent,因此只有显式拥有从回执到 idle 这一完整区间的调用方才能将其称为一次运行([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。`AgentOptions` 可合并扩展:core 声明 `provider?`、`model?` 与 `maxTokens?`(在 `agent/request` 后,分发要求 provider 与 model 都存在)。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。Persona 归 `dsh-system-prompt` 所有:agent 作用域的 `deployment:persona` 可以遮蔽全局默认值。 +`AgentStatus` 为 `'idle' | 'running'`,`SessionId` 是品牌类型。dispose(资源释放)会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。对于需要在把输入作为 steering 加入当前提示词准入/轮次,还是提交为一个新的待准入提示词之间做选择的调用方,`acceptsNextStep` 才是更窄且准确的路由判断条件。活动的轮次接纳预留与完全停稳相关,但不会改变 `status`,也不会把之后的队列项变成 steering;它的唯一权限是将驱动器的下一次认领延迟到释放时。`AgentOptions` 可合并扩展:core 声明 `provider?`、`model?` 与 `maxTokens?`(在 `agent/request` 后,分发要求 provider 与 model 都存在)。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。Persona 归 `dsh-system-prompt` 所有:agent 作用域的 `deployment:persona` 可以遮蔽全局默认值。 -cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`;signal 不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。 +cause 是由 TypeScript 强制约束的同进程输入。活跃的 `TurnCancellation` 持有者会把其判别字段复制到仅运行时的 `AbortSignal.reason`,并在发布 `turn/end` 前退役;冻结后的 `AbortSignal.reason` 仍可读取。只有 loop 会在结算时从自己机器私有的 signal 上读回 cause(`user`、`parent` 或仅用于生命周期的 `disposed`)——不存在公开的读取器,signal 也不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。 [事件分类](../architecture.md#event)拥有 `agent/*` 生命周期、检查点与 waterfall(瀑布式事件)契约。轮次和步骤边界是持久会话事件,而不是 agent emit。 @@ -702,19 +376,22 @@ cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有 ## 拦截决策 -pre-step 决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。进入步骤的批次具有权威性,并保留每条消息的标识与 provenance。钩子桥接层把其原生决策字段映射到这一类型化结果上。 +提示词决策与工具后决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。每个 `additionalContexts` 条目都会成为一条独立的 `user/message`,保留各自的标识与 provenance。钩子桥接层把其原生决策字段映射到这些类型化结果上。 源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) -`agent/pre-step` 接收一个 payload,携带独占的已领取批次(`messages`)、拟进入步骤的坐标(`turn`、`step`)与当前轮次的取消 `signal`。首次提案在已打开的轮次内、任何步骤开始前运行;工具 continuation 可以在步骤之间提交空的已领取批次: - -它返回 `PreStepDecision`。reject 不会打开步骤。enter 提供在 `step/start` 后追加的完整消息批次;最终决策省略的已领取消息保持已删除,而领取后插入的输入仍留待后续处理: +`agent/prompt-submit` 在轮次打开前返回 `PromptDecision`。allow 可以改写已领取的提示词或附加 `additionalContexts`;block 拒绝准入且不产生任何轮次事件: ```ts type-equiv -/** Whether and with which messages the loop enters a proposed step. */ -type PreStepDecision = - | { kind: 'reject' } - | { kind: 'enter'; messages: UserMessage[] } +/** + * Prompt interception result. `allow.content` replaces the prompt, while + * `additionalContexts` appends model-facing context before the turn starts. + * An `allow` returned by a listener is authoritative: a listener wrapping + * `next()` preserves both fields unless it intentionally replaces them. + */ +type PromptDecision = + | { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: UserMessage[] } + | { kind: 'block'; reason: string } ``` `agent/request-error` 在失败的模型步骤关闭之后、其轮次关闭之前运行。listener 可以在失败轮次的 signal 仍然存活时修复持久状态或 await 策略工作。处理该错误的 listener 返回 `{ kind: 'retry' }` 且不调用 `next()`;默认的 `undefined` 会让失败保持终态。 @@ -724,7 +401,12 @@ type PreStepDecision = type RequestErrorAction = { kind: 'retry' } | undefined ``` -`agent/pre-step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。 +```ts type-equiv +/** Model-request failure with an optional machine-routable provider code. */ +type RequestError = Error & { code?: string } +``` + +`agent/step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。 `agent/session-start` 携带 `SessionStartSource`(会话生命周期为何开始;桥接层据此匹配其 SessionStart): @@ -784,7 +466,7 @@ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise @@ -962,6 +644,29 @@ Source: [`packages/core/agent/src/index.ts:242`](../../packages/core/agent/src/i ### `agent/*` events + + +#### `agent/cancel-requested` — emit + +Effective broad cancellation was requested, before queued/outbox work is cleared or the active turn is aborted. This observe-only notification cannot veto cancellation; listener failures are contained. + +```ts cordis-catalog +/** + * Effective broad cancellation was requested, before queued/outbox work + * is cleared or the active turn is aborted. This observe-only notification + * cannot veto cancellation; listener failures are contained. + * @param agent - the agent whose current work is being cancelled. + * @param cause - the explicit typed cancellation cause. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/cancel-requested'(this: Scoped, agent: Agent, cause: AgentCancelCause): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:353`](../../packages/core/agent/src/types.ts) + #### `agent/created` — emit @@ -975,11 +680,11 @@ A fully configured agent and live session were published. Setup is composition-o * Synchronous listener failure vetoes publication, while returned-promise * rejection is reported. Detach requested during dispatch waits until every * creation listener has observed the stable entry. - * @param payload.agent - the newly registered agent with its live session and completed setup. + * @param agent - the newly registered agent with its live session and completed setup. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/created'(this: Scoped, payload: { agent: Agent }): void +'agent/created'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -997,11 +702,11 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco * An agent left the registry; AgentLoop emits this after driver quiescence * and scoped-registration unwind, but before session detachment. Custom * registry users own their driver-ordering contract. - * @param payload.agent - the exact agent removed from the registry. + * @param agent - the exact agent removed from the registry. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/disposed'(this: Scoped, payload: { agent: Agent }): void +'agent/disposed'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -1012,111 +717,138 @@ Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/t #### `agent/error` — emit -A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. +A step or turn errored. The machine reports a failure here (plus the logger) even when the error has no in-turn position for a durable record. ```ts cordis-catalog /** - * A step or turn errored. The machine reports a failure here even when - * the error has no in-turn position for a durable record. - * @param payload.agent - the agent whose turn errored. - * @param payload.turn - the turn in which the failure surfaced. - * @param payload.step - the step at which the failure surfaced. - * @param payload.error - the failure, verbatim. + * A step or turn errored. The machine reports a failure here (plus the + * logger) even when the error has no in-turn position for a durable record. + * @param agent - the agent whose turn errored. + * @param turn - the turn in which the failure surfaced. + * @param step - the step at which the failure surfaced. + * @param error - the failure, verbatim. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void +'agent/error'(this: Scoped, agent: Agent, turn: number, step: number, error: unknown): void ``` Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/claimed` — emit +#### `agent/inbox/dequeue` — emit -One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. +The driver claimed one item out of the inbox: a queued item at a turn boundary, or steering drained between steps. Fires after the item leaves its FIFO and before it becomes a durable message. ```ts cordis-catalog /** - * One message left the inbox inside its open turn. If the proposed step - * is rejected, the claimed message ends here: it is neither discarded nor - * re-emitted as a user/message, and the turn closes without a step. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the claimed message. - * @param payload.turn - the owning turn. + * The driver claimed one item out of the inbox: a queued item at a turn + * boundary, or steering drained between steps. Fires after the item leaves + * its FIFO and before it becomes a durable message. + * @param agent - the agent whose inbox item was claimed. + * @param item - the exact claimed occurrence. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void +'agent/inbox/dequeue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/discarded` — emit +#### `agent/inbox/discard` — emit -One message was discarded from the live inbox. +Pending inbox items were dropped without delivering them, so every enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, emits this after `agent/cancel-requested` when applicable and before aborting the active work. Fires once per drop with every dropped item. ```ts cordis-catalog /** - * One message was discarded from the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the discarded message. + * Pending inbox items were dropped without delivering them, so every + * enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR + * `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, + * emits this after `agent/cancel-requested` when applicable and before + * aborting the active work. Fires once per drop with every dropped item. + * @param agent - the agent whose inbox items were dropped. + * @param items - the discarded occurrences in FIFO order (queued then steering); never empty. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/discard'(this: Scoped, agent: Agent, items: InboxItem[]): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/inserted` — emit +#### `agent/inbox/enqueue` — emit -One message entered the live inbox. +An item entered the queued or steering inbox. `placement` is the acceptance-time routing result; listeners must not reconstruct it from later agent or session state. ```ts cordis-catalog /** - * One message entered the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the inserted message. + * An item entered the queued or steering inbox. `placement` is the + * acceptance-time routing result; listeners must not reconstruct it from + * later agent or session state. + * @param agent - the owning agent. + * @param item - accepted occurrence, message, and resolved placement. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/enqueue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) - + -#### `agent/pre-step` — waterfall +#### `agent/inbox/update` — emit -Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. +A still-pending queued item changed content. The item id, placement, and position remain stable while the event carries the replacement message. ```ts cordis-catalog /** - * Reject a proposed step or replace the messages that enter it. Calling - * `next()` preserves the current messages. - * @param payload.agent - the agent proposing the step. - * @param payload.messages - messages removed from the inbox for this step. - * @param payload.turn - the turn that will own the step. - * @param payload.step - the step proposed by the loop. - * @param payload.signal - the current turn's cancellation signal. + * A still-pending queued item changed content. The item id, placement, and + * position remain stable while the event carries the replacement message. + * @param agent - the owning agent. + * @param item - the complete post-update occurrence. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/update'(this: Scoped, agent: Agent, item: InboxItem): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:321`](../../packages/core/agent/src/types.ts) + + + +#### `agent/prompt-submit` — waterfall + +Allow, rewrite, or block one claimed prompt before it becomes a user message or opens a turn. Call `next()` for the unchanged default. The signal controls only this admission attempt; listeners may cooperate with it but must not retain it for a later attempt or turn. + +```ts cordis-catalog +/** + * Allow, rewrite, or block one claimed prompt before it becomes a user + * message or opens a turn. Call `next()` for the unchanged default. The + * signal controls only this admission attempt; listeners may cooperate with + * it but must not retain it for a later attempt or turn. + * @param agent - the agent whose turn claimed the message. + * @param message - the frozen claimed message, including identity and source. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/prompt-submit'(this: Scoped, agent: Agent, message: UserMessage, signal: AbortSignal, next: () => Promise): Promise ``` Types: [Scoped](scope.md) · [UserMessage](session.md) @@ -1135,17 +867,17 @@ Replace the frozen call configuration. `await next()` yields the config the mach * the machine would use (agent options on the first request, the logged * header afterwards); return a replacement to switch. Model-visible * content must use logged channels; this seam cannot mutate messages. - * @param payload.agent - the agent making the model call. - * @param payload.turn - the open turn number. - * @param payload.step - the step whose request this is. - * @param payload.signal - the current turn's explicit abort signal. + * @param agent - the agent making the model call. + * @param turn - the open turn number. + * @param step - the step whose request this is. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/request'(this: Scoped, agent: Agent, turn: number, step: number, signal: AbortSignal, next: () => Promise): Promise ``` -Types: [Scoped](scope.md) +Types: [LlmCallConfig](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) @@ -1153,25 +885,28 @@ Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/t #### `agent/request-error` — waterfall -Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. +Handle a model-request failure after its failed step has closed but before the failed turn closes. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns the error, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. ```ts cordis-catalog /** - * Handle one failed model-request attempt before the loop retries or closes - * its step. A listener returns `{ kind: 'retry' }` without calling `next()` - * when it owns recovery, or calls `next()` to delegate. The default - * `undefined` leaves the failure terminal. - * @param payload.agent - the agent whose request failed. - * @param payload.turn - the turn containing the failed request. - * @param payload.step - the step containing the failed request attempt. - * @param payload.provider - the provider selected for the failed request. - * @param payload.failure - serializable facts normalized at the final adapter boundary. - * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. - * @param payload.signal - the turn abort signal. + * Handle a model-request failure after its failed step has closed but + * before the failed turn closes. A listener returns `{ kind: 'retry' }` + * without calling `next()` when it owns the error, or calls `next()` to + * delegate. The default `undefined` leaves the failure terminal. + * @param agent - the agent whose request failed. + * @param turn - the open turn number. + * @param step - the failed step number. + * @param error - the original model-request failure. + * @param failure - serializable facts normalized at the final adapter boundary. + * @param priorFailures - immutable failures that already authorized another + * retry turn in this consecutive sequence. + * @param retryPolicy - immutable policy of the adapter registration that served + * the failed request, or `undefined` if no final adapter served it. + * @param signal - the turn abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise +'agent/request-error'(this: Scoped, agent: Agent, turn: number, step: number, error: RequestError, failure: LlmFailure, priorFailures: readonly LlmFailure[], retryPolicy: ResolvedRetryPolicy | undefined, signal: AbortSignal, next: () => Promise): Promise ``` Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) @@ -1190,12 +925,12 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to * `agent.inject()` to seed model-facing context. This is a notification, not * a veto; disposal requested by a lifecycle owner is rechecked before the * driver starts. - * @param payload.agent - the agent whose session lifecycle began. - * @param payload.source - why the session started (fresh startup, resume, …). + * @param agent - the agent whose session lifecycle began. + * @param source - why the session started (fresh startup, resume, …). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void +'agent/session-start'(this: Scoped, agent: Agent, source: SessionStartSource): void ``` Types: [Scoped](scope.md) @@ -1206,19 +941,18 @@ Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/t #### `agent/status` — emit -Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. +Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` synchronously; drive lifecycle from this event. ```ts cordis-catalog /** - * Agent status changed (`idle` ⇄ `running`). A waking delivery enters - * `running` synchronously after reserving cancellation; `idle` means no - * driver remains scheduled or active. - * @param payload.agent - the agent whose status flipped. - * @param payload.status - the status just entered (the transition's destination). + * Agent status changed (`idle` ⇄ `running`). `send()` does not enter + * `running` synchronously; drive lifecycle from this event. + * @param agent - the agent whose status flipped. + * @param status - the status just entered (the transition's destination). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void +'agent/status'(this: Scoped, agent: Agent, status: AgentStatus): void ``` Types: [Scoped](scope.md) @@ -1229,7 +963,7 @@ Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/t #### `agent/turn-stopping` — serial -The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. ```ts cordis-catalog /** @@ -1239,17 +973,14 @@ The turn is about to close: the model owes no response (no live tool calls, no f * re-reads its inbox: fresh steering runs another step, none closes the * turn. Data decides, so listener order cannot change the outcome. The * inverse control (stop a tool loop early) is data too: a tool result - * carrying `concludesTurn` ends the turn at its step. The conclusion - * never short-circuits already-submitted next-step work: same-step - * `additionalContexts` or racing steering still runs, and the turn - * closes only when that inbox drains. - * @param payload.agent - the agent whose turn is at its stop boundary. - * @param payload.turn - the turn about to close. - * @param payload.signal - the current turn's explicit abort signal. + * carrying `concludesTurn` ends the turn at its step. + * @param agent - the agent whose turn is at its stop boundary. + * @param turn - the turn about to close. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode serial */ -'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void +'agent/turn-stopping'(this: Scoped, agent: Agent, turn: number, signal: AbortSignal): Promise | void ``` Types: [Scoped](scope.md) @@ -1272,12 +1003,12 @@ A declarative agent entry failed before it could publish a live agent. Consumers * Consumers that buffer work for the configured identity use this * transient signal to reject that work instead of waiting forever. Normal * factory teardown suppresses failures from the cancelled startup attempt. - * @param payload.sessionId - exact shared agent/session identity that failed startup. - * @param payload.error - persistence, setup, or publication failure. + * @param sessionId - exact shared agent/session identity that failed startup. + * @param error - persistence, setup, or publication failure. * @mode emit */ -'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void +'agent-loop/config-start-failed'(sessionId: SessionId, error: unknown): void ``` -Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) +Source: [`packages/core/agent-loop/src/index.ts:157`](../../packages/core/agent-loop/src/index.ts) diff --git a/docs/subsystems/filesystem.i18n.yaml b/docs/subsystems/filesystem.i18n.yaml index cadc2b92d8..e57b2aef45 100644 --- a/docs/subsystems/filesystem.i18n.yaml +++ b/docs/subsystems/filesystem.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/filesystem.md -filesystem.md: 1999399254fa5d72ab222a42a2fa8f4b890da50a -filesystem.zh.md: 044a1c5be91ed8bec5d0442cc6f7edfde6870785 +filesystem.md: 591f1b4d4b1177ccbe7c2280d2054da5d4be8b99 +filesystem.zh.md: d599e48c1fba2d564a544f628ca3475b781e5cd5 diff --git a/docs/subsystems/filesystem.md b/docs/subsystems/filesystem.md index 1999399254..591f1b4d4b 100644 --- a/docs/subsystems/filesystem.md +++ b/docs/subsystems/filesystem.md @@ -256,6 +256,10 @@ type FsErrorCode = `FS_NOT_DIRECTORY`, `FS_PERMISSION_DENIED`, and `FS_IO_ERROR` are used by directory listing to distinguish an existing non-directory target, a denied listing, and an unexpected backend I/O failure. `FS_SANDBOX_DENIED` is a POLICY refusal from a sandbox-enforcing backend (`dsh-fs-sandbox`) — the mode fence denied a write/edit — distinct from `FS_PERMISSION_DENIED` (the host kernel refusing). `FS_NOT_OBSERVED` means the policy plugin has no prior-observation record for this owner (or a `createIfAbsent` hit an existing file). `FS_STALE_VERSION` means the backend version no longer matches the observed one (or an edit hit a missing target). Freshness authorization has no partial/full distinction, so there is no `FS_PARTIAL_OBSERVATION`. +## No timeouts on file IO + +`read`/`write`/`edit` take **no** `timeoutMs`, and the provider seam arms no deadline — unlike bash and web (which consume [`@deepseek-ai/dsh-timeout`](../../packages/util/timeout/README.md)) and the bash-backed `glob`/`grep` (whose declared `timeoutMs` is enforced by `@deepseek-ai/dsh-timeout-policy`): those are process-backed, where a deadline can really kill the work. A local syscall is best-effort-abortable at most — a timeout could not force an in-progress `fsync`/`rename` to stop, so a deadline here would be a knob that cannot deliver on its promise, and an implicit default in the exact place explicit-over-implicit forbids. Both reference agents (Claude Code, Codex) leave file IO untimed for the same reason; cancellation still propagates through the tool-execution signal for best-effort abort at syscall boundaries. + ## The service and the plugin `FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls (supplying `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED`) and records on `fs/observed`. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures. diff --git a/docs/subsystems/filesystem.zh.md b/docs/subsystems/filesystem.zh.md index 044a1c5be9..d599e48c1f 100644 --- a/docs/subsystems/filesystem.zh.md +++ b/docs/subsystems/filesystem.zh.md @@ -256,6 +256,10 @@ type FsErrorCode = 目录列表使用 `FS_NOT_DIRECTORY`、`FS_PERMISSION_DENIED` 与 `FS_IO_ERROR` 区分已存在但并非目录的目标、被拒绝的列表操作和意外的后端 I/O 失败。`FS_SANDBOX_DENIED` 是强制执行沙箱的后端(`dsh-fs-sandbox`)所作的策略拒绝——模式边界拒绝了写入/编辑——与 `FS_PERMISSION_DENIED`(宿主内核拒绝)不同。`FS_NOT_OBSERVED` 表示策略插件没有此所有者的先前观察记录(或 `createIfAbsent` 遇到了现有文件)。`FS_STALE_VERSION` 表示后端版本不再与观察到的版本匹配(或编辑操作遇到缺失目标)。新鲜度授权没有部分/完整之分,因此不存在 `FS_PARTIAL_OBSERVATION`。 +## 文件 IO 不设超时 + +`read`/`write`/`edit` **不**接受 `timeoutMs`,提供方 seam 也不设置截止时间——不同于 bash 与 web(它们消费 [`@deepseek-ai/dsh-timeout`](../../packages/util/timeout/README.md))以及 bash 支撑的 `glob`/`grep`(其声明的 `timeoutMs` 由 `@deepseek-ai/dsh-timeout-policy` 强制执行):那些是进程支撑的,截止时间可以真正终止工作。本地系统调用至多是尽力中止——超时无法迫使进行中的 `fsync`/`rename` 停下,因此这里的截止时间会成为无法兑现承诺的旋钮,而且恰好落在"显式优于隐式"禁止隐式默认值的位置。两个参照 agent(Claude Code、Codex)出于同样原因不给文件 IO 计时;取消仍通过工具执行 signal 传播,在系统调用边界尽力中止。 + ## 服务与插件 `FileSystem`(`ctx.fs`,abstract)拥有提供方原语:`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:对写入/编辑意图 waterfall 作出决策(提供 `createIfAbsent`/`replaceIfVersion`/`{ version }`,或抛出 `FS_NOT_OBSERVED`),并在 `fs/observed` 上记录。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall,并 emit 记录事件。下方生成的 [`ctx.fs` 小节](#ctxfs--filesystem-abstract-seam) 展示确切的 `ctx.fs` 签名。 diff --git a/docs/subsystems/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml index bd1d833f51..f6ce932ad9 100644 --- a/docs/subsystems/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/llm-streaming.md -llm-streaming.md: fe4a831660dd81b33a1cd7dc9a9d7026f3831991 -llm-streaming.zh.md: a10b696ec0887e139785fc7e95d8641e6496de79 +llm-streaming.md: b3f850f4d07ad9e36d0e0db71dd9e0498e468767 +llm-streaming.zh.md: bb31c14b9bd0453399d83968b1062f67819caca3 diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index fe4a831660..b3f850f4d0 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -2,10 +2,82 @@ English | [中文](llm-streaming.zh.md) -The wire-level streaming vocabulary of [dsh-llm](../../packages/llm/llm). [core.md](core.md) introduces `StreamChunk`, `Message`, and `ContentBlock`; this page owns the full chunk protocol, the adapter contract every adapter must obey, and the shared assembler. +The conversation and streaming vocabulary of [`packages/llm`](../../packages/llm/README.md): the `Message`/`ContentBlock` shapes every request and durable history share, the fully-assembled model request, the raw `StreamChunk` protocol, the adapter contract every adapter must obey, and the shared assembler. The [core spine](core.md) holds and logs these values on every turn; this page declares them. Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + + +## Content blocks and messages + +A conversation is `Message`s; a message is an array of typed **content blocks**. The block union derives from `ContentBlockMap`. + +Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +```ts type-equiv +/** + * Merge-extensible content blocks keyed by `type`. New core blocks must land + * with adapter, UI, and compaction support. + */ +interface ContentBlockMap { + 'text': TextBlock + 'reasoning': ReasoningBlock + 'tool-call': ToolCallBlock + 'tool-result': ToolResultBlock +} +``` + +The block interfaces (full fields in source): `TextBlock` (`text`), `ReasoningBlock` (thinking, distinct from visible text), `ToolCallBlock` (`id: CallId`, `name`, raw-JSON `arguments`), `ToolResultBlock` (`toolCallId`, nested `content: ContentBlock[]`, `isError?`). `ContentBlock = ContentBlockMap[ContentBlockType]`. The core set is limited to blocks every shipping path honors — multimodal content (images, audio, …) has no core block type; a feature that needs one adds it via the merge-extensible map together with the adapter/UI/compaction support that honors it. + +Source: [`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) + +A `Message` is one identified, immutable role/source/content value. Model-produced assistant messages carry provider/model ownership and optional adapter-private replay metadata in their source: + +```ts type-equiv +/** Provider ownership and adapter-private replay data for an assistant message. */ +interface AssistantProvenance { + /** Provider route that produced the message. */ + provider: string + /** Provider model id that produced the message. */ + model: string + /** + * Lossless-JSON adapter state needed to replay the provider response. + * `LlmService` exposes it to a target adapter only when that adapter instance + * currently owns both this historical provider and the target provider. + */ + replayState?: unknown +} +``` + +```ts type-equiv +/** One immutable message representation shared by delivery, durable history, and model requests. */ +interface Message { + /** Stable identity preserved across every representation boundary. */ + readonly id: MessageId + /** Provider-neutral conversation role. */ + readonly role: 'system' | 'user' | 'assistant' + /** Exact model-facing blocks. */ + readonly content: ContentBlock[] + /** Required producer provenance. */ + readonly source: MessageSource +} +``` + +Where a message came from is itself a merge-extensible sum type: + +```ts type-equiv +/** + * Where a message (or injected content) came from. + * Merge-extensible sum type — plugins add their own `kind`s. + */ +interface MessageSourceMap { + user: { kind: 'user' } + plugin: { kind: 'plugin'; plugin: string } + model: ModelMessageSource + tool: ToolMessageSource +} +``` + ## `StreamChunk` — the raw protocol A streaming response interleaves several typed blocks (text, reasoning, multiple tool calls). `index` ties each delta to its block; `block-end` carries the fully-assembled `ContentBlock` so consumers don't have to re-assemble deltas themselves. It is a **closed** discriminated union — a `switch` over `type` ends with `assertNever`, so adding a variant breaks compilation at every consumer that must handle it. @@ -15,9 +87,8 @@ A streaming response interleaves several typed blocks (text, reasoning, multiple * Raw streaming protocol emitted by adapters. * Block indexes correlate interleaved deltas, and `block-end` carries the * assembled block. Adapters emit usage before the terminal finish and nothing - * afterward; tool arguments remain raw JSON strings. An adapter implementation - * may throw, but `LlmService.stream()` normalizes that failure to a terminal - * `error` or `aborted` finish before exposing it to consumers. + * afterward; tool arguments remain raw JSON strings. Failures either throw or + * end with `error`/`aborted`, and consumers must handle both paths. */ type StreamChunk = | { type: 'block-start'; index: number; blockType: ContentBlockType } @@ -65,10 +136,10 @@ Every adapter MUST obey these, and every consumer may rely on them: - **Provider stalls are bounded at the transport.** Both shipping remote adapters expose positive finite `streamIdleTimeoutMs` with a five-minute default. The watchdog arms only while iterator `next()` is outstanding, uses one stable signal for the whole request, maps its own expiry to `TIMEOUT`, and keeps an earlier caller abort as `ABORTED`. - **Context overflow has one canonical code.** Both DeepSeek adapters classify explicit provider detail through `isContextWindowExceededError()` and surface `CONTEXT_WINDOW_EXCEEDED`, whether the failure arrives as a thrown HTTP `LlmError` or an in-band finish error. Consumers route on the code, never provider text. - **An empty completion is a retryable error, not a silent success.** Both adapters map a terminal `stop` finish that carried no content blocks to `finish {kind:'error'}` with the canonical `EMPTY_RESPONSE` code, and `dsh-llm-retry` retries it by default; see [empty model responses are retryable](../../.agents/notes/implemented/bug-fix/2026-07-24-empty-model-response-is-retryable.md). -- **Every provider HTTP request carries the app-attribution header.** Adapters send `attributionHeaders()` (below), the `User-Agent` baseline. +- **Every provider HTTP request carries the app-attribution header.** Adapters send `attributionHeaders()` (below) - the `User-Agent` baseline - and prove it with a wire-level test (mock server asserting the received header, or the library's header hook for a library-backed adapter). - **Replay state is adapter-owned.** A successful `finish` may carry lossless-JSON state needed to reconstruct a native provider response. The loop stores it with the assembled assistant message. On a later request, `LlmService` passes the state only when the historical provider and target provider are currently registered to the exact same adapter instance. That adapter validates the state and owns any cross-model or cross-provider conversion; other adapters receive the provider-neutral content and provenance without the private state. -Two independent implementations obey this contract: `dsh-llm-deepseek` uses direct fetch with SSE framing through `eventsource-parser`, while `dsh-llm-pi-ai` provides a generic multi-provider adapter through `@earendil-works/pi-ai`. Both carry cancellation and the idle watchdog to the provider request. +This contract is pinned down by two deliberately independent implementations: `dsh-llm-deepseek` (direct fetch, SSE framing via `eventsource-parser`) and `dsh-llm-pi-ai` (a generic multi-provider adapter through `@earendil-works/pi-ai`). The library-backed adapter exercises the finish-chunk error path, while transport-boundary tests prove each idle watchdog stops its actual request. ## `ResolvedRetryPolicy` @@ -142,9 +213,8 @@ declare class BlockAssembler { push(chunk: StreamChunk): void; /** * Assemble all blocks seen so far, in stream order. - * @returns one block per seen index, except that max-token truncation drops - * tool calls that cannot be executed safely; an open block assembles from - * its accumulated deltas (an unknown block type never closed by `block-end` throws). + * @returns one block per seen index; an open block assembles from its + * accumulated deltas (an unknown block type never closed by `block-end` throws). */ blocks(): ContentBlock[]; /** Usage from the `usage` chunk; undefined until one arrives. */ @@ -162,6 +232,263 @@ declare class BlockAssembler { } ``` + + +## The model request + +One model call is a fully-assembled `GenerateOptions`. The adapter answers with a raw [`StreamChunk`](#streamchunk--the-raw-protocol) stream; the consumer assembles it with [`BlockAssembler`](#blockassembler). + +Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +Provider and model discovery uses small provider-neutral descriptors. A model catalog is advisory: routing still keys on a registered provider, and an adapter may accept unlisted model ids. + +Registering an adapter returns a handle: the disposer, plus the atomic route replacement a plugin whose route set is user-configurable needs. + +```ts type-equiv +/** + * What {@link LlmService.registerAdapter} returns: the disposer, plus an + * atomic route replacement for the same adapter instance. + */ +interface AdapterRegistrationHandle { + /** Release every route this registration currently holds. */ + (): void + /** + * Replace this registration's routes with `providers`, keeping the same + * adapter instance. The candidate set is validated in full first — a + * conflict with another adapter, an invalid name, or bad provider metadata + * throws and leaves the current routes untouched — and the swap itself is + * one synchronous section, so no request can observe a gap. An empty array + * is legal here (a settings section that emptied holds zero routes while + * staying registered), unlike an empty initial registration. + * + * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration + * has been released: its routes are gone and its disposer has already run, + * so anything registered afterwards would have no owner left to release it. + * @param providers - the complete next route set for this registration. + */ + replace(providers: string[]): void +} +``` + +```ts type-equiv +/** Display metadata for one registered provider route. */ +interface LlmProviderInfo { + /** Provider route key used by {@link GenerateOptions.provider}. */ + id: string + /** Human-readable provider name for selectors and diagnostics. */ + name: string +} +``` + +Adapter plugins additionally declare which routes *could* run through `registerConfigurableProviders()`, addressing each one's user-settings section, so configuration surfaces can offer dormant providers before any route registers. + +```ts type-equiv +/** + * One provider route an adapter plugin can activate through configuration, + * whether or not the route is currently registered. Configuration surfaces + * merge this directory with `listProviders()` to offer every configurable + * provider alongside its live/dormant state. + */ +interface LlmConfigurableProvider { + /** Provider route key this entry activates when configured. */ + provider: string + /** Human-readable provider name for configuration surfaces. */ + displayName: string + /** User-settings namespace whose section configures this provider. */ + settingsNs: string + /** + * Path from that namespace's section root to this provider's profile + * object; empty when the whole section is the profile. + */ + settingsPath: readonly string[] +} +``` + +```ts type-equiv +/** One adapter-discovered model; catalog membership is advisory, not request validation. */ +interface LlmModelInfo { + /** Provider route that owns this model entry. */ + provider: string + /** Model id passed to {@link GenerateOptions.model}. */ + id: string + /** Human-readable model name for selectors. */ + name: string + /** Optional user-facing distinction from otherwise similar models. */ + description?: string +} +``` + +Correctness-sensitive metadata is resolved separately from the advisory catalog and is owned by the adapter serving the exact route. Context capacity, adapter call defaults, and reasoning choices share one exact-model result so consumers do not repeat authoritative model resolution. + +```ts type-equiv +/** Provider-owned context capacity for one exact provider/model route. */ +interface LlmModelContext { + /** Maximum combined request and response context in tokens. */ + contextWindow: number +} +``` + +Reasoning effort is another exact-route capability. The core brands identifiers but does not enumerate their values; each adapter owns the ordered set, display names, and optional deployment default. + +```ts type-equiv +/** Adapter-owned identifier for one model's selectable reasoning effort. */ +type ReasoningEffortId = Branded<'ReasoningEffortId'> +``` + +```ts type-equiv +/** Display metadata for one adapter-owned reasoning effort. */ +interface LlmReasoningEffortInfo { + /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ + id: ReasoningEffortId + /** Human-readable effort name for selectors and diagnostics. */ + name: string + /** Optional user-facing distinction from otherwise similar efforts. */ + description?: string +} +``` + +```ts type-equiv +/** Selectable reasoning efforts for one exact provider/model route. */ +interface LlmModelReasoningInfo { + /** Supported efforts in adapter-preferred display order. */ + efforts: readonly LlmReasoningEffortInfo[] + /** + * Adapter-configured default materialized into requests when callers omit + * an effort. Absence preserves the provider's own default. + */ + defaultEffort?: ReasoningEffortId +} +``` + +```ts type-equiv +/** Exact-route model metadata resolved by its owning adapter. */ +interface LlmResolvedModelInfo extends LlmModelInfo { + /** Provider-owned context capacity when known. */ + context?: LlmModelContext + /** Adapter-configured per-request output cap materialized when callers omit one. */ + defaultMaxTokens?: number + /** Adapter-owned selectable reasoning levels when exposed. */ + reasoning?: LlmModelReasoningInfo +} +``` + +```ts type-equiv +/** A single model request, fully assembled. */ +interface GenerateOptions { + /** Registered provider route selecting the adapter instance. */ + provider: string + model: string + /** Adapter-owned reasoning effort selected for this exact model. */ + reasoningEffort?: ReasoningEffortId + /** + * Ordered conversation messages, exactly as the provider sees them (after + * the `system` slot). A loop-built request assembles them as + * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. + */ + messages: Message[] + /** System prompt text (adapters map to the provider's system slot). */ + system?: string + /** Tool schemas (adapters map to the provider's `tools` field). */ + tools?: ToolSchema[] + temperature?: number + maxTokens?: number + /** + * Stop sequences: generation halts as soon as the model produces any one of + * these strings (adapters map to the provider's stop field, e.g. OpenAI + * `stop`). The stop string itself is not included in the output. + */ + stop?: string[] + signal?: AbortSignal + /** + * Session identity stamped by the loop for listener routing. Adapters ignore + * it; replay uses it to keep concurrent parent and child cursors independent. + */ + sessionId?: Branded<'SessionId'> + /** + * Provider-neutral classification for an auxiliary model call. Adapters may + * map the purpose to model-hidden transport metadata or purpose-specific + * generation policy. Ordinary conversation requests leave it unset. + */ + purpose?: 'compaction' | 'session-title' +} +``` + +Why a model response stopped is a merge-extensible reason. Terminal provider failures carry the streaming contract's [`LlmFailure`](#llmfailure): + +```ts type-equiv +/** + * Why a model response stopped. + * Merge-extensible so adapters can surface provider-specific reasons. + */ +interface FinishReasonMap { + 'stop': { kind: 'stop' } + 'tool-calls': { kind: 'tool-calls' } + 'max-tokens': { kind: 'max-tokens' } + 'aborted': { kind: 'aborted'; failure: LlmFailure } + 'error': { kind: 'error'; failure: LlmFailure } +} +``` + +`FinishReason = FinishReasonMap[keyof FinishReasonMap]`. `TokenUsage` (per-call accounting with disjoint cache fields) is detailed [below](#tokenusage). + +`GenerateOptions.tools` carries `ToolSchema` — the JSON-schema description of a tool, as sent to the model. It is declared in dsh-llm (not dsh-tools) precisely because it is part of the request the loop assembles every step: + +```ts type-equiv +/** + * JSON-schema description of a tool, as sent to the model. + * + * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; + * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import + * it from this package. + */ +interface ToolSchema { + name: string + description: string + /** JSON Schema object for the arguments. */ + parameters: Record +} +``` + +The model-facing `ToolSchema` is the wire shape; the registered `ToolDefinition` that produces it (schema + `execute`) is on [tools.md](tools.md). + +### The request envelope: `LlmCallConfig` and the logged header + +The loop builds each request from logged state. `EpochHeader` records call config, adapter-default provenance, rendered prompt, and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md). + +`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus provenance under the turn signal. The prepared call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests. + +On the wire, a loop-built request reads the `system` slot (the rendered prompt assembly) followed by the derived history — the boundary snapshot, whose tail is the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request. + +FIXME(call-config-shape): revisit which remaining fields are genuinely epoch-level for cache purposes (`model` and the model-owned reasoning effort are explicit; the sampling scalars sit here out of caution). + +```ts type-equiv +/** + * Provider, model, reasoning effort, and sampling scalars of one conversation's + * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; + * the loop builds requests from the logged header rather than accepting these + * per call. + */ +interface LlmCallConfig { + provider: string + model: string + reasoningEffort?: ReasoningEffortId + temperature?: number + maxTokens?: number + stop?: string[] +} +``` + +```ts type-equiv +/** + * Effective config fields supplied by exact-model adapter resolution rather + * than by the caller's request proposal. + */ +interface LlmCallConfigAdapterDefaults { + reasoningEffort?: true + maxTokens?: true +} +``` + ## The seam `LlmAdapter` is the provider seam: subclass, implement `stream()`, and register one adapter instance with `ctx.llm.registerAdapter(providers, adapter)`. `GenerateOptions.provider` selects the registered adapter; `GenerateOptions.model` is passed to that adapter and need not be registered at lifecycle start. Duplicate provider routes fail atomically. Optional `providerRetryPolicy()` is captured per route with normal defaults, while `providerInfo()` and asynchronous `listModels()` feed `LlmService.listProviders()` / `listModels()` with detached selector metadata. That catalog is advisory rather than a request whitelist: the adapter remains authoritative and may accept unlisted model ids. One asynchronous `resolveModel()` query returns exact model identity plus optional correctness-sensitive context capacity, an adapter-configured `defaultMaxTokens`, and ordered model-owned reasoning ids with an optional deployment default; absent fields mean unavailable metadata or provider-owned behavior, not invalid catalog membership. The resolver receives optional cancellation and must settle promptly after abort. `LlmService.resolveModelInfo()` validates and detaches the aggregate. At the final adapter boundary, `resolveCallConfig()` materializes the output default only when `maxTokens` is absent and validates and materializes reasoning, so direct calls cannot bypass either configured behavior; direct dispatch captures one registration before awaiting that resolution. The agent loop instead uses `prepareCall()` to keep the same registration across model resolution, durable header logging, and dispatch, retain detached context metadata from that exact lookup, and report which config fields the adapter defaulted. Adapter lookup happens at the terminal continuation of the `llm/stream` waterfall, so a listener may short-circuit the call or route a mutable one-shot request before lookup. AgentLoop observes a request attempt once the outer waterfall returns a stream handle; that limited boundary does not prove a lazy terminal adapter was constructed or began provider I/O. The `block-start` / `block-end` `index` correlation and the assembler together mean an adapter only has to emit well-formed chunks — block reassembly is not each adapter's problem. The consumer surface (`ctx.llm.stream()`) and the `llm/stream` waterfall are described in [architecture.md § Content blocks and streaming](../architecture.md#content-blocks-and-streaming-dsh-llm). @@ -171,8 +498,6 @@ declare class BlockAssembler { interface PreparedLlmCall { /** Detached, deep-frozen config with any adapter-owned default materialized. */ readonly config: LlmCallConfig - /** Immutable retry policy captured with the adapter registration. */ - readonly retryPolicy: ResolvedRetryPolicy /** Detached context metadata resolved with the registration-bound call. */ readonly context?: LlmModelContext /** Config fields materialized by the captured adapter rather than proposed by the caller. */ @@ -239,22 +564,7 @@ declare abstract class LlmAdapter { } ``` -`ContentBlockType` (the key set the `index`-correlated blocks carry) derives from `ContentBlockMap`: - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -See [core.md § Content blocks and messages](core.md#content-blocks-and-messages) for the block interfaces. +`ContentBlockType` (the key set the `index`-correlated blocks carry) derives from [`ContentBlockMap`](#content-blocks-and-messages) above. @@ -293,9 +603,9 @@ listProviders(): LlmProviderInfo[] * entry, or a provider already declared by any registration throws * `LlmError` without registering the rest. Disposed with the fiber. * @param entries - every configurable provider this plugin owns. - * @returns a handle that withdraws all of them, and can atomically replace them. + * @returns the disposer that withdraws all of them. */ -registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void /** * List every declared configurable provider, registered or dormant. @@ -303,29 +613,6 @@ registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): Dire */ listConfigurableProviders(): LlmConfigurableProvider[] -/** - * Offer to interrogate provider endpoints on behalf of the settings - * namespace this plugin owns. The namespace is the key because that is what - * a configuration surface already holds from the configurable-provider - * directory, and because a provider being *added* has no route to name yet. - * Disposed with the fiber. - * @param settingsNs - the namespace whose profiles this discovery serves. - * @param discover - interrogates one endpoint; must honor `request.signal`. - * @returns the disposer that withdraws the offer. - */ -registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void - -/** - * Interrogate one provider endpoint for the models it advertises. The - * request describes a draft, not a stored route, so nothing here reads or - * writes settings or credentials — the caller owns both, and the reply is - * candidate metadata a surface may offer for adoption. - * @param settingsNs - namespace whose registered discovery serves this draft. - * @param request - the endpoint, protocol, and one-shot credential to use. - * @returns the advertised models, deduplicated in endpoint order. - */ -async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise - /** * Resolve the retry policy captured when one provider route was registered. * @param provider - registered provider route to inspect. @@ -375,22 +662,22 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise /** - * Stream one model call as raw chunks (token-level deltas). Replay state is - * retained only when the same adapter instance owns its historical provider - * and the target provider. Final adapter selection remains fixed through - * asynchronous exact-model resolution and dispatch. Adapter selection, - * dispatch, and iteration failures become terminal `error` or `aborted` - * finish chunks; middleware, nested-call, cleanup, and consumer failures - * remain thrown. + * Stream one model call as raw chunks (token-level deltas). Throws + * `LlmError` with code `NO_ADAPTER` if no adapter is registered for + * `options.provider`. Replay state is retained only when the same adapter + * instance owns its historical provider and the target provider. Final + * adapter selection remains fixed through asynchronous exact-model resolution + * and dispatch. Selection, dispatch, and iteration failures retain their + * original Error identity and are tagged in a call-local scope for narrow + * agent-loop request recovery; middleware and nested-call failures remain + * untagged for the outer call. * @param options - the full request; `options.provider` selects the adapter. * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. */ stream(options: GenerateOptions): AsyncIterable ``` -Types: [AdapterRegistrationHandle](core.md) · [DirectoryRegistrationHandle](core.md) · [GenerateOptions](core.md) · [LlmCallConfig](core.md) · [LlmConfigurableProvider](core.md) · [LlmDiscoveredModel](core.md) · [LlmModelDiscoveryRequest](core.md) · [LlmModelInfo](core.md) · [LlmProviderInfo](core.md) · [LlmResolvedModelInfo](core.md) - -Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:232`](../../packages/llm/llm/src/index.ts) @@ -415,7 +702,7 @@ The provider topology changed: an adapter registered or unregistered routes, or 'llm/adapters-updated'(): void ``` -Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:71`](../../packages/llm/llm/src/index.ts) @@ -439,7 +726,5 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` -Types: [GenerateOptions](core.md) - -Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:60`](../../packages/llm/llm/src/index.ts) diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index a10b696ec0..bb31c14b9b 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -2,10 +2,84 @@ [English](llm-streaming.md) | 中文 -[dsh-llm](../../packages/llm/llm) 的协议格式(wire format)级流式输出词汇。[core.md](core.md) 介绍了 `StreamChunk`、`Message` 与 `ContentBlock`;本页拥有完整的分片协议、每个适配器必须遵守的适配器契约(adapter contract),以及共享的 assembler。 +[`packages/llm`](../../packages/llm/README.md) 的对话与流式输出词汇:每个请求与持久历史共享的 `Message`/`ContentBlock` 形状、完整组装的模型请求、原始 `StreamChunk` 协议、每个适配器必须遵守的适配器契约(adapter contract),以及共享的 assembler。[核心主干](core.md)在每个轮次持有并记录这些值;本页声明它们。 源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + + +## 内容块与消息 + +一段对话由 `Message` 组成;一条消息是一个类型化**内容块**的数组。块的联合类型从 `ContentBlockMap` 派生。 + +源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +```ts type-equiv +/** + * Merge-extensible content blocks keyed by `type`. New core blocks must land + * with adapter, UI, and compaction support. + */ +interface ContentBlockMap { + 'text': TextBlock + 'reasoning': ReasoningBlock + 'tool-call': ToolCallBlock + 'tool-result': ToolResultBlock +} +``` + +各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ToolCallBlock`(`id: CallId`、`name`、原始 JSON `arguments`)、`ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。核心集仅限于每条交付路径都尊重的块——多模态内容(图像、音频等)没有核心块类型;需要的功能通过可合并扩展的 map 添加,同时提供适配器/UI/压缩支持。 + +源码:[`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) + +`Message` 是一个带标识且不可变的角色/来源/内容值。模型产生的 assistant 消息会在其来源中携带提供方/模型所有权与可选的适配器私有回放元数据: + +```ts type-equiv +/** Provider ownership and adapter-private replay data for an assistant message. */ +interface AssistantProvenance { + /** Provider route that produced the message. */ + provider: string + /** Provider model id that produced the message. */ + model: string + /** + * Lossless-JSON adapter state needed to replay the provider response. + * `LlmService` exposes it to a target adapter only when that adapter instance + * currently owns both this historical provider and the target provider. + */ + replayState?: unknown +} +``` + +```ts type-equiv +/** One immutable message representation shared by delivery, durable history, and model requests. */ +interface Message { + /** Stable identity preserved across every representation boundary. */ + readonly id: MessageId + /** Provider-neutral conversation role. */ + readonly role: 'system' | 'user' | 'assistant' + /** Exact model-facing blocks. */ + readonly content: ContentBlock[] + /** Required producer provenance. */ + readonly source: MessageSource +} +``` + +消息来源本身也是一个可合并扩展的和类型: + +```ts type-equiv +/** + * Where a message (or injected content) came from. + * Merge-extensible sum type — plugins add their own `kind`s. + */ +interface MessageSourceMap { + user: { kind: 'user' } + plugin: { kind: 'plugin'; plugin: string } + model: ModelMessageSource + tool: ToolMessageSource +} +``` + + + ## `StreamChunk`:原始协议 一个流式响应交错包含多种类型的块(文本、推理(reasoning)、多个工具调用)。`index` 将每个 delta 关联到其所属块;`block-end` 携带完整组装好的 `ContentBlock`,消费方无需自行重新组装 delta。这是一个**封闭的**可辨识联合类型:对 `type` 的 `switch` 以 `assertNever` 结尾,因此新增变体会在每个必须处理它的消费方处触发编译错误。 @@ -15,9 +89,8 @@ * Raw streaming protocol emitted by adapters. * Block indexes correlate interleaved deltas, and `block-end` carries the * assembled block. Adapters emit usage before the terminal finish and nothing - * afterward; tool arguments remain raw JSON strings. An adapter implementation - * may throw, but `LlmService.stream()` normalizes that failure to a terminal - * `error` or `aborted` finish before exposing it to consumers. + * afterward; tool arguments remain raw JSON strings. Failures either throw or + * end with `error`/`aborted`, and consumers must handle both paths. */ type StreamChunk = | { type: 'block-start'; index: number; blockType: ContentBlockType } @@ -34,6 +107,8 @@ type StreamChunk = } ``` + + ## `LlmFailure` 每个抛出的失败或最终适配器的带内失败都会规范化为一种可序列化、提供方无关的 payload。`providerRetryAfterMs` 是经校验、由提供方请求的正数延迟,而不是重试决策;`ProviderRequestId` 是用于诊断的不透明品牌字符串。 @@ -65,10 +140,10 @@ interface LlmFailure { - **提供方停顿在传输层受到时限约束。** 两个已交付的远程适配器都暴露正数且有限的 `streamIdleTimeoutMs`,默认五分钟。watchdog 只在 iterator `next()` 尚未完成时启动,整个请求使用同一个稳定 signal,把自身到期映射为 `TIMEOUT`,并把更早发生的调用方中止保留为 `ABORTED`。 - **上下文溢出只有一个规范 code。** 两个 DeepSeek 适配器都通过 `isContextWindowExceededError()` 对提供方的显式细节分类并暴露 `CONTEXT_WINDOW_EXCEEDED`,无论失败以抛出的 HTTP `LlmError` 还是带内 finish error 到达。消费方按 code 路由,绝不依赖提供方文本。 - **空 completion 是可重试错误,而不是静默的成功结果。** 两个适配器都把没有携带任何内容块的终止性 `stop` 结束映射为携带规范 `EMPTY_RESPONSE` code 的 `finish {kind:'error'}`,`dsh-llm-retry` 默认会重试它;详见[空模型响应可重试](../../.agents/notes/implemented/bug-fix/2026-07-24-empty-model-response-is-retryable.md)。 -- **每个提供方 HTTP 请求都携带应用归属头。** 适配器发送下文的 `attributionHeaders()`,即 `User-Agent` 基线。 +- **每个提供方 HTTP 请求都携带应用归属头。** 适配器发送 `attributionHeaders()`(见下文)作为 `User-Agent` 基线,并通过协议级测试加以证明(mock 服务器断言收到的 header,或对基于库的适配器使用库的 header 钩子)。 - **回放状态归适配器所有。** 成功的 `finish` 可以携带重建提供方原生响应所需的无损 JSON 状态。循环会将其与组装后的 assistant 消息一起存储。后续请求中,仅当历史提供方与目标提供方当前注册到完全相同的适配器实例时,`LlmService` 才会传递该状态。该适配器负责校验状态并拥有所有跨模型或跨提供方转换;其他适配器只会收到提供方无关的内容与 provenance,不会收到私有状态。 -两个彼此独立的实现遵循该契约:`dsh-llm-deepseek` 使用直接 fetch,并通过 `eventsource-parser` 进行 SSE(Server-Sent Events)分帧;`dsh-llm-pi-ai` 则通过 `@earendil-works/pi-ai` 提供通用多提供方适配器。两者都会把取消与空闲 watchdog 传递至提供方请求。 +该契约由两个有意保持独立的实现锁定:`dsh-llm-deepseek`(直接 fetch,SSE(Server-Sent Events)分帧经由 `eventsource-parser`)和 `dsh-llm-pi-ai`(通过 `@earendil-works/pi-ai` 实现的通用多提供方适配器)。基于库的适配器覆盖 finish 分片错误路径,而传输边界测试证明每个空闲 watchdog 都会停止其实际请求。 ## `ResolvedRetryPolicy` @@ -76,7 +151,7 @@ interface LlmFailure { ## `AppIdentity`:应用归属 -每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该契约有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包 manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。设计理由见[强制 `User-Agent` 归属](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md)。 +每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该契约有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包(package) manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。设计理由见[强制 `User-Agent` 归属](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md)。 ```ts type-equiv /** @@ -96,6 +171,8 @@ interface AppIdentity { } ``` + + ## `TokenUsage` 逐调用 token 记账。各计数**互不重叠**:`inputTokens` 只包含未缓存输入;缓存输入单独报告,计费输入是三者之和。若提供方把缓存命中折入单一提示词总数(如 DeepSeek 的 `prompt_tokens`),适配器会再将其扣除。`reasoningTokens` 存在时只是信息性细节,已经包含在 `outputTokens` 中;汇总时不得重复相加。 @@ -118,6 +195,8 @@ interface TokenUsage { } ``` + + ## `BlockAssembler` `BlockAssembler`([`packages/llm/llm/src/assembler.ts`](../../packages/llm/llm/src/assembler.ts))是唯一的共享实现,负责把 `StreamChunk` 流折叠回 `ContentBlock`、usage、结束原因与回放状态。循环在记录原始分片的同时,把同一批分片送入 assembler,再将组装后的 assistant 内容连同其提供方/模型 provenance 一起存储。需要组装结果、又不想重新实现 fold 的消费方使用它。 @@ -142,9 +221,8 @@ declare class BlockAssembler { push(chunk: StreamChunk): void; /** * Assemble all blocks seen so far, in stream order. - * @returns one block per seen index, except that max-token truncation drops - * tool calls that cannot be executed safely; an open block assembles from - * its accumulated deltas (an unknown block type never closed by `block-end` throws). + * @returns one block per seen index; an open block assembles from its + * accumulated deltas (an unknown block type never closed by `block-end` throws). */ blocks(): ContentBlock[]; /** Usage from the `usage` chunk; undefined until one arrives. */ @@ -162,6 +240,263 @@ declare class BlockAssembler { } ``` + + +## 模型请求 + +一次模型调用是一个完全组装好的 `GenerateOptions`。适配器以原始 [`StreamChunk`](#streamchunk--the-raw-protocol) 流作答;消费方用 [`BlockAssembler`](#blockassembler) 组装它。 + +源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +提供方与模型发现使用小型、提供方无关的描述符。模型目录仅供参考:路由仍以已注册提供方为键,适配器也可以接受未列出的模型 id。 + +注册适配器会返回一个句柄:既是释放器,也带有原子的路由替换——路由集合由用户配置决定的插件正需要它。 + +```ts type-equiv +/** + * What {@link LlmService.registerAdapter} returns: the disposer, plus an + * atomic route replacement for the same adapter instance. + */ +interface AdapterRegistrationHandle { + /** Release every route this registration currently holds. */ + (): void + /** + * Replace this registration's routes with `providers`, keeping the same + * adapter instance. The candidate set is validated in full first — a + * conflict with another adapter, an invalid name, or bad provider metadata + * throws and leaves the current routes untouched — and the swap itself is + * one synchronous section, so no request can observe a gap. An empty array + * is legal here (a settings section that emptied holds zero routes while + * staying registered), unlike an empty initial registration. + * + * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration + * has been released: its routes are gone and its disposer has already run, + * so anything registered afterwards would have no owner left to release it. + * @param providers - the complete next route set for this registration. + */ + replace(providers: string[]): void +} +``` + +```ts type-equiv +/** Display metadata for one registered provider route. */ +interface LlmProviderInfo { + /** Provider route key used by {@link GenerateOptions.provider}. */ + id: string + /** Human-readable provider name for selectors and diagnostics. */ + name: string +} +``` + +适配器插件还会通过 `registerConfigurableProviders()` 声明哪些路由*可以*运行,并指明每条路由的用户设置分节,使配置界面能在任何路由注册之前就呈现休眠的提供方。 + +```ts type-equiv +/** + * One provider route an adapter plugin can activate through configuration, + * whether or not the route is currently registered. Configuration surfaces + * merge this directory with `listProviders()` to offer every configurable + * provider alongside its live/dormant state. + */ +interface LlmConfigurableProvider { + /** Provider route key this entry activates when configured. */ + provider: string + /** Human-readable provider name for configuration surfaces. */ + displayName: string + /** User-settings namespace whose section configures this provider. */ + settingsNs: string + /** + * Path from that namespace's section root to this provider's profile + * object; empty when the whole section is the profile. + */ + settingsPath: readonly string[] +} +``` + +```ts type-equiv +/** One adapter-discovered model; catalog membership is advisory, not request validation. */ +interface LlmModelInfo { + /** Provider route that owns this model entry. */ + provider: string + /** Model id passed to {@link GenerateOptions.model}. */ + id: string + /** Human-readable model name for selectors. */ + name: string + /** Optional user-facing distinction from otherwise similar models. */ + description?: string +} +``` + +对正确性敏感的元数据与参考目录分开解析,并归服务该确切路由的适配器所有。上下文容量、适配器调用默认值和推理选项共用同一个确切模型结果,消费方因而无需重复执行权威模型解析。 + +```ts type-equiv +/** Provider-owned context capacity for one exact provider/model route. */ +interface LlmModelContext { + /** Maximum combined request and response context in tokens. */ + contextWindow: number +} +``` + +推理强度是另一项针对确切路由的能力。核心为标识符添加品牌类型,但不枚举其值;有序集合、展示名称和可选的部署默认值均由各适配器持有。 + +```ts type-equiv +/** Adapter-owned identifier for one model's selectable reasoning effort. */ +type ReasoningEffortId = Branded<'ReasoningEffortId'> +``` + +```ts type-equiv +/** Display metadata for one adapter-owned reasoning effort. */ +interface LlmReasoningEffortInfo { + /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ + id: ReasoningEffortId + /** Human-readable effort name for selectors and diagnostics. */ + name: string + /** Optional user-facing distinction from otherwise similar efforts. */ + description?: string +} +``` + +```ts type-equiv +/** Selectable reasoning efforts for one exact provider/model route. */ +interface LlmModelReasoningInfo { + /** Supported efforts in adapter-preferred display order. */ + efforts: readonly LlmReasoningEffortInfo[] + /** + * Adapter-configured default materialized into requests when callers omit + * an effort. Absence preserves the provider's own default. + */ + defaultEffort?: ReasoningEffortId +} +``` + +```ts type-equiv +/** Exact-route model metadata resolved by its owning adapter. */ +interface LlmResolvedModelInfo extends LlmModelInfo { + /** Provider-owned context capacity when known. */ + context?: LlmModelContext + /** Adapter-configured per-request output cap materialized when callers omit one. */ + defaultMaxTokens?: number + /** Adapter-owned selectable reasoning levels when exposed. */ + reasoning?: LlmModelReasoningInfo +} +``` + +```ts type-equiv +/** A single model request, fully assembled. */ +interface GenerateOptions { + /** Registered provider route selecting the adapter instance. */ + provider: string + model: string + /** Adapter-owned reasoning effort selected for this exact model. */ + reasoningEffort?: ReasoningEffortId + /** + * Ordered conversation messages, exactly as the provider sees them (after + * the `system` slot). A loop-built request assembles them as + * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. + */ + messages: Message[] + /** System prompt text (adapters map to the provider's system slot). */ + system?: string + /** Tool schemas (adapters map to the provider's `tools` field). */ + tools?: ToolSchema[] + temperature?: number + maxTokens?: number + /** + * Stop sequences: generation halts as soon as the model produces any one of + * these strings (adapters map to the provider's stop field, e.g. OpenAI + * `stop`). The stop string itself is not included in the output. + */ + stop?: string[] + signal?: AbortSignal + /** + * Session identity stamped by the loop for listener routing. Adapters ignore + * it; replay uses it to keep concurrent parent and child cursors independent. + */ + sessionId?: Branded<'SessionId'> + /** + * Provider-neutral classification for an auxiliary model call. Adapters may + * map the purpose to model-hidden transport metadata or purpose-specific + * generation policy. Ordinary conversation requests leave it unset. + */ + purpose?: 'compaction' | 'session-title' +} +``` + +模型响应为何停止由可合并扩展的原因表示。提供方终态失败携带流式契约的 [`LlmFailure`](#llmfailure): + +```ts type-equiv +/** + * Why a model response stopped. + * Merge-extensible so adapters can surface provider-specific reasons. + */ +interface FinishReasonMap { + 'stop': { kind: 'stop' } + 'tool-calls': { kind: 'tool-calls' } + 'max-tokens': { kind: 'max-tokens' } + 'aborted': { kind: 'aborted'; failure: LlmFailure } + 'error': { kind: 'error'; failure: LlmFailure } +} +``` + +`FinishReason = FinishReasonMap[keyof FinishReasonMap]`。`TokenUsage`(逐调用计量,含不相交的缓存字段)详见[下文](#tokenusage)。 + +`GenerateOptions.tools` 携带 `ToolSchema`——工具的 JSON Schema 描述,发送给模型。它声明在 dsh-llm(而非 dsh-tools)中,正是因为它是循环每一步组装请求的一部分: + +```ts type-equiv +/** + * JSON-schema description of a tool, as sent to the model. + * + * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; + * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import + * it from this package. + */ +interface ToolSchema { + name: string + description: string + /** JSON Schema object for the arguments. */ + parameters: Record +} +``` + +面向模型的 `ToolSchema` 是协议格式;产出它的已注册 `ToolDefinition`(schema + `execute`)在 [tools.md](tools.md) 中。 + +### 请求信封:`LlmCallConfig` 与记录的 header + +循环从已记录状态构建每个请求。`EpochHeader` 通过完整的 `request/header` 快照记录调用配置、适配器默认值来源、渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。 + +`agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall 开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置及其来源。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。 + +在协议格式上,循环构建的请求先读取 `system` 槽位(渲染后的提示词组装),再读取派生历史——边界快照,其尾部在轮次首步是最新的 `user/message`,在后续步骤是上一步的工具结果。开发不变式针对每个循环构建的请求精确重算此等式。 + +FIXME(call-config-shape):重新审视其余哪些字段出于缓存目的确实属于 epoch 层级(`model` 和模型持有的推理强度已明确属于;采样标量目前出于谨慎保留在此)。 + +```ts type-equiv +/** + * Provider, model, reasoning effort, and sampling scalars of one conversation's + * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; + * the loop builds requests from the logged header rather than accepting these + * per call. + */ +interface LlmCallConfig { + provider: string + model: string + reasoningEffort?: ReasoningEffortId + temperature?: number + maxTokens?: number + stop?: string[] +} +``` + +```ts type-equiv +/** + * Effective config fields supplied by exact-model adapter resolution rather + * than by the caller's request proposal. + */ +interface LlmCallConfigAdapterDefaults { + reasoningEffort?: true + maxTokens?: true +} +``` + ## seam `LlmAdapter` 是提供方 seam:创建子类、实现 `stream()`,再用 `ctx.llm.registerAdapter(providers, adapter)` 注册一个适配器实例。`GenerateOptions.provider` 选择已注册适配器;`GenerateOptions.model` 会传给该适配器,无需在生命周期启动时注册。重复提供方路由会原子失败。可选的 `providerRetryPolicy()` 会按路由捕获并填入 normal 默认值,`providerInfo()` 与异步 `listModels()` 方法则为 `LlmService.listProviders()` / `listModels()` 提供分离的 selector 元数据。该目录仅供参考,不是请求白名单:适配器仍是权威,并可接受未列出的模型 id。单次异步 `resolveModel()` 查询返回确切模型身份,以及可选的对正确性敏感的上下文容量、适配器配置的 `defaultMaxTokens`、由模型持有的有序推理强度 ID 和部署默认值;字段缺失表示元数据不可用或保留提供方持有的行为,而不表示目录成员关系无效。解析器会接收可选的取消信号,并且必须在信号中止后迅速完成结算。`LlmService.resolveModelInfo()` 会校验聚合结果并返回分离值。在最终适配器边界,`resolveCallConfig()` 仅在 `maxTokens` 缺失时填入输出默认值,并校验和填入推理强度,因此直接调用也无法绕过任何一项已配置行为;直接分派会在等待解析前捕获一项适配器注册。agent loop 则使用 `prepareCall()`,使模型解析、请求头持久记录和分派全程使用同一项注册,保留来自同一次查询的分离上下文元数据,并报告适配器填入的配置字段。适配器查找发生在 `llm/stream` waterfall(瀑布式事件)的终端 continuation,因此 listener 可以在查找前短路调用,或路由一个可变的一次性请求。AgentLoop 在外层 waterfall 返回流句柄时观察到一次请求尝试;这个有限边界不能证明惰性终端适配器已构造完成或开始提供方 I/O。`block-start` / `block-end` 的 `index` 关联与 assembler 共同意味着适配器只需 emit 格式正确的分片——块重组不是每个适配器各自的问题。消费方 surface(`ctx.llm.stream()`)与 `llm/stream` waterfall 见 [architecture.md § 内容块与流式传输](../architecture.md#content-blocks-and-streaming-dsh-llm)。 @@ -171,8 +506,6 @@ declare class BlockAssembler { interface PreparedLlmCall { /** Detached, deep-frozen config with any adapter-owned default materialized. */ readonly config: LlmCallConfig - /** Immutable retry policy captured with the adapter registration. */ - readonly retryPolicy: ResolvedRetryPolicy /** Detached context metadata resolved with the registration-bound call. */ readonly context?: LlmModelContext /** Config fields materialized by the captured adapter rather than proposed by the caller. */ @@ -239,22 +572,7 @@ declare abstract class LlmAdapter { } ``` -`ContentBlockType`(`index` 关联块所携带的键集合)派生自 `ContentBlockMap`: - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -块接口详见 [core.md § Content blocks and messages](core.md#content-blocks-and-messages)。 +`ContentBlockType`(带 `index` 关联的块所携带的键集合)从上文的 [`ContentBlockMap`](#content-blocks-and-messages) 派生。 @@ -293,9 +611,9 @@ listProviders(): LlmProviderInfo[] * entry, or a provider already declared by any registration throws * `LlmError` without registering the rest. Disposed with the fiber. * @param entries - every configurable provider this plugin owns. - * @returns a handle that withdraws all of them, and can atomically replace them. + * @returns the disposer that withdraws all of them. */ -registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void /** * List every declared configurable provider, registered or dormant. @@ -303,29 +621,6 @@ registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): Dire */ listConfigurableProviders(): LlmConfigurableProvider[] -/** - * Offer to interrogate provider endpoints on behalf of the settings - * namespace this plugin owns. The namespace is the key because that is what - * a configuration surface already holds from the configurable-provider - * directory, and because a provider being *added* has no route to name yet. - * Disposed with the fiber. - * @param settingsNs - the namespace whose profiles this discovery serves. - * @param discover - interrogates one endpoint; must honor `request.signal`. - * @returns the disposer that withdraws the offer. - */ -registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void - -/** - * Interrogate one provider endpoint for the models it advertises. The - * request describes a draft, not a stored route, so nothing here reads or - * writes settings or credentials — the caller owns both, and the reply is - * candidate metadata a surface may offer for adoption. - * @param settingsNs - namespace whose registered discovery serves this draft. - * @param request - the endpoint, protocol, and one-shot credential to use. - * @returns the advertised models, deduplicated in endpoint order. - */ -async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise - /** * Resolve the retry policy captured when one provider route was registered. * @param provider - registered provider route to inspect. @@ -375,22 +670,22 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise /** - * Stream one model call as raw chunks (token-level deltas). Replay state is - * retained only when the same adapter instance owns its historical provider - * and the target provider. Final adapter selection remains fixed through - * asynchronous exact-model resolution and dispatch. Adapter selection, - * dispatch, and iteration failures become terminal `error` or `aborted` - * finish chunks; middleware, nested-call, cleanup, and consumer failures - * remain thrown. + * Stream one model call as raw chunks (token-level deltas). Throws + * `LlmError` with code `NO_ADAPTER` if no adapter is registered for + * `options.provider`. Replay state is retained only when the same adapter + * instance owns its historical provider and the target provider. Final + * adapter selection remains fixed through asynchronous exact-model resolution + * and dispatch. Selection, dispatch, and iteration failures retain their + * original Error identity and are tagged in a call-local scope for narrow + * agent-loop request recovery; middleware and nested-call failures remain + * untagged for the outer call. * @param options - the full request; `options.provider` selects the adapter. * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. */ stream(options: GenerateOptions): AsyncIterable ``` -Types: [AdapterRegistrationHandle](core.md) · [DirectoryRegistrationHandle](core.md) · [GenerateOptions](core.md) · [LlmCallConfig](core.md) · [LlmConfigurableProvider](core.md) · [LlmDiscoveredModel](core.md) · [LlmModelDiscoveryRequest](core.md) · [LlmModelInfo](core.md) · [LlmProviderInfo](core.md) · [LlmResolvedModelInfo](core.md) - -Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:232`](../../packages/llm/llm/src/index.ts) @@ -415,7 +710,7 @@ The provider topology changed: an adapter registered or unregistered routes, or 'llm/adapters-updated'(): void ``` -Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:71`](../../packages/llm/llm/src/index.ts) @@ -439,7 +734,5 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` -Types: [GenerateOptions](core.md) - -Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:60`](../../packages/llm/llm/src/index.ts) diff --git a/docs/subsystems/session-reference.i18n.yaml b/docs/subsystems/session-reference.i18n.yaml index 980c1a1a2d..c1466bdcfc 100644 --- a/docs/subsystems/session-reference.i18n.yaml +++ b/docs/subsystems/session-reference.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/session-reference.md -session-reference.md: 60e29c56ea372e100009b94caedea5327308432a -session-reference.zh.md: 3ae9264abc15848646e5397dfcbeff764c038918 +session-reference.md: f539a59b8d26182aff9746b6d6a39a86ba15cb45 +session-reference.zh.md: 82b6a6c5ec5c91372fc6a43b73ad1572fccf5b3a diff --git a/docs/subsystems/session-reference.md b/docs/subsystems/session-reference.md index 60e29c56ea..f539a59b8d 100644 --- a/docs/subsystems/session-reference.md +++ b/docs/subsystems/session-reference.md @@ -102,7 +102,7 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) diff --git a/docs/subsystems/session-reference.zh.md b/docs/subsystems/session-reference.zh.md index 3ae9264abc..82b6a6c5ec 100644 --- a/docs/subsystems/session-reference.zh.md +++ b/docs/subsystems/session-reference.zh.md @@ -102,7 +102,7 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index e6ead80f87..a58335b9f1 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/subagent.md -subagent.md: cbf9043b36a5cb2ab37bb99c6dbbf5a18d1e0288 -subagent.zh.md: ffd8fefd8b9d128c21e1b91ee0ba1fb706638fa9 +subagent.md: c132c073459642b133e0d7e662ee98a8c9dfb694 +subagent.zh.md: e46f9aa98389842918ae37dda1477ef1b565e774 diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index cbf9043b36..c132c07345 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -611,7 +611,7 @@ list(): string[] async start(name: string, request: SubagentStartRequest): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) · [MessageId](core.md) · [SessionId](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [MessageId](llm-streaming.md) · [SessionId](core.md) Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index ffd8fefd8b..e46f9aa983 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -613,7 +613,7 @@ list(): string[] async start(name: string, request: SubagentStartRequest): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) · [MessageId](core.md) · [SessionId](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [MessageId](llm-streaming.md) · [SessionId](core.md) Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) diff --git a/docs/subsystems/token-meter.i18n.yaml b/docs/subsystems/token-meter.i18n.yaml index e348aca7a0..17bbb937cc 100644 --- a/docs/subsystems/token-meter.i18n.yaml +++ b/docs/subsystems/token-meter.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/token-meter.md -token-meter.md: 0f53955b3e6afce84520314f4de453edaa897158 -token-meter.zh.md: 3ba22c54d599982d3f7bdbcb9160b6ffc3255e07 +token-meter.md: 8a01980191bf29b1fbc0f8b1d0b33d1fb149319c +token-meter.zh.md: 7411ce26cb75697607a706469d7923d46ff3f421 diff --git a/docs/subsystems/token-meter.md b/docs/subsystems/token-meter.md index 0f53955b3e..8a01980191 100644 --- a/docs/subsystems/token-meter.md +++ b/docs/subsystems/token-meter.md @@ -84,7 +84,7 @@ measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement estimateMessage(message: Message): number ``` -Types: [EpochHeader](session.md) · [Message](core.md) · [Session](session.md) +Types: [EpochHeader](session.md) · [Message](llm-streaming.md) · [Session](session.md) Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) diff --git a/docs/subsystems/token-meter.zh.md b/docs/subsystems/token-meter.zh.md index 3ba22c54d5..7411ce26cb 100644 --- a/docs/subsystems/token-meter.zh.md +++ b/docs/subsystems/token-meter.zh.md @@ -84,7 +84,7 @@ measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement estimateMessage(message: Message): number ``` -Types: [EpochHeader](session.md) · [Message](core.md) · [Session](session.md) +Types: [EpochHeader](session.md) · [Message](llm-streaming.md) · [Session](session.md) Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) diff --git a/docs/subsystems/tools.i18n.yaml b/docs/subsystems/tools.i18n.yaml index 8b0aa47c9a..003e175399 100644 --- a/docs/subsystems/tools.i18n.yaml +++ b/docs/subsystems/tools.i18n.yaml @@ -2,5 +2,5 @@ # 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 docs/subsystems/tools.md -tools.md: 5d72bd54f0dc5995218087030f24c467c8ccbb38 -tools.zh.md: 2340d4c2d92c51aa9cc89f4694b626c76d05140b +tools.md: b63014d1c2058c8fc7e96817d4bca858a1954482 +tools.zh.md: 2dc5802efce25a732a60c660191cd584ede623bb diff --git a/docs/subsystems/tools.md b/docs/subsystems/tools.md index 5d72bd54f0..b63014d1c2 100644 --- a/docs/subsystems/tools.md +++ b/docs/subsystems/tools.md @@ -596,7 +596,7 @@ Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bri 'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise ``` -Types: [ContentBlock](core.md) · [Scoped](scope.md) +Types: [ContentBlock](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) diff --git a/docs/subsystems/tools.zh.md b/docs/subsystems/tools.zh.md index 2340d4c2d9..2dc5802efc 100644 --- a/docs/subsystems/tools.zh.md +++ b/docs/subsystems/tools.zh.md @@ -596,7 +596,7 @@ Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bri 'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise ``` -Types: [ContentBlock](core.md) · [Scoped](scope.md) +Types: [ContentBlock](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) diff --git a/packages/acp/README.i18n.yaml b/packages/acp/README.i18n.yaml index ca4ec66f67..cd4555752b 100644 --- a/packages/acp/README.i18n.yaml +++ b/packages/acp/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/acp/README.md -README.md: 3ba247598f29f2244456061fe7f9a3282086148f -README.zh.md: c13bc05b12fff2ef97b4d547936aa14a6556430a +README.md: 97af6d164b265bf0e98e3c9f5a444cffad4face5 +README.zh.md: 266b108c108b9f543d93cbb727ddbc01ab4bef36 diff --git a/packages/acp/README.md b/packages/acp/README.md index 3ba247598f..97af6d164b 100644 --- a/packages/acp/README.md +++ b/packages/acp/README.md @@ -2,10 +2,10 @@ English | [中文](README.zh.md) -The ACP group exposes harness agents to programmatic clients. It is an interoperability transport, not a presentation or human-interaction layer. +The ACP group exposes harness agents to programmatic clients over the Agent Client Protocol. It is an interoperability transport, not a presentation or human-interaction layer; the matching out-of-process subagent *client* lives in [`subagent/subagent-acp`](../subagent/subagent-acp/README.md) because it implements the subagent provider interface. | Package | Role | |---|---| | [`acp/`](acp/README.md) | Automation-only ACP server. | -The matching out-of-process subagent client remains in [`subagent/subagent-acp`](../subagent/subagent-acp/README.md) because it implements the subagent provider interface; arbitrary ACP clients may drive the same server contract. +The server contract is documented in [`acp/README.md`](acp/README.md). diff --git a/packages/acp/README.zh.md b/packages/acp/README.zh.md index c13bc05b12..266b108c10 100644 --- a/packages/acp/README.zh.md +++ b/packages/acp/README.zh.md @@ -2,10 +2,10 @@ [English](README.md) | 中文 -ACP(Agent Client Protocol)组将 harness 中的 agent(智能体)公开给程序化客户端。它是互操作传输层,而非展示层或人机交互层。 +ACP 组通过 Agent Client Protocol 把 harness agent 暴露给编程客户端。它是互操作传输层,不是展示或人机交互层;配对的进程外 subagent *客户端*在 [`subagent/subagent-acp`](../subagent/subagent-acp/README.md),因为它实现的是 subagent 提供方接口。 | 包 | 职责 | |---|---| | [`acp/`](acp/README.md) | 仅面向自动化的 ACP 服务器。 | -与之匹配的进程外 subagent 客户端仍位于 [`subagent/subagent-acp`](../subagent/subagent-acp/README.md),因为它实现 subagent 提供方接口;任意 ACP 客户端都可以按照同一服务器契约驱动该服务器。 +服务器契约见 [`acp/README.md`](acp/README.md)。 diff --git a/packages/bash/README.i18n.yaml b/packages/bash/README.i18n.yaml index 4c4a2c6180..00321c8ab0 100644 --- a/packages/bash/README.i18n.yaml +++ b/packages/bash/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/bash/README.md -README.md: 601782caad24d3555a206365a3f1954d81af1cf0 -README.zh.md: 8b96c4f80ba8776bfd8bdde178178cea946ff36c +README.md: 7395a051742af24b96842966b71297dc01853dca +README.zh.md: 1d5825e077c46284dbd4895e285cda8f39c252a3 diff --git a/packages/bash/README.md b/packages/bash/README.md index 601782caad..7395a05174 100644 --- a/packages/bash/README.md +++ b/packages/bash/README.md @@ -15,3 +15,5 @@ The capability family spans the canonical executor seam, its implementations, th | [`tool-pwsh/`](tool-pwsh/README.md) | Exposes PowerShell execution to the model. | (registers on `ctx.tools`) | A leaf `cordis.yml` selects one executor implementation and the model-facing tools it needs. A sandboxed composition also selects a `ctx.sandbox` provider; the [ACP example](../../examples/acp-agent/) shows one complete wiring. + +The subsystem reference — request/spec vocabulary, results, background processes, the service, and events — is [docs/subsystems/bash.md](../../docs/subsystems/bash.md). diff --git a/packages/bash/README.zh.md b/packages/bash/README.zh.md index 8b96c4f80b..1d5825e077 100644 --- a/packages/bash/README.zh.md +++ b/packages/bash/README.zh.md @@ -15,3 +15,5 @@ | [`tool-pwsh/`](tool-pwsh/README.md) | 向模型公开 PowerShell 执行。 | (注册到 `ctx.tools`) | 叶节点 `cordis.yml` 选择一个执行器实现和所需的面向模型工具。沙箱化组合还会选择一个 `ctx.sandbox` 提供方;[ACP(Agent Client Protocol)示例](../../examples/acp-agent/)展示一套完整接线。 + +子系统参考——请求/spec 词汇、结果、后台进程、服务与事件——见 [docs/subsystems/bash.md](../../docs/subsystems/bash.md)。 diff --git a/packages/boot/README.i18n.yaml b/packages/boot/README.i18n.yaml index 151e5e5c08..8f691dd089 100644 --- a/packages/boot/README.i18n.yaml +++ b/packages/boot/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/boot/README.md -README.md: 7727f4fbab62a930051fd3b92d04513ac9f5d218 -README.zh.md: aa9a56619ee55c3be8feb58881f77c460861b1ac +README.md: 5e4e483b60adab0b22ddb5279f4cd8fb699b9c35 +README.zh.md: d49afa45b9fb93f6fa83944167057f0c933cd05d diff --git a/packages/boot/README.md b/packages/boot/README.md index 7727f4fbab..5e4e483b60 100644 --- a/packages/boot/README.md +++ b/packages/boot/README.md @@ -2,8 +2,10 @@ English | [中文](README.zh.md) -The channel-neutral boot library the app bins share. A role-complete single-package group: it belongs to no channel and no assembly — `apps/cli`, the [`scaffold/`](../scaffold/README.md) launcher, and the [`examples/`](../examples/README.md) demo bins all consume it. +The channel-neutral boot library the app bins share: `apps/cli`, the [`scaffold/`](../scaffold/README.md) launcher, and the [`examples/`](../examples/README.md) demo bins all consume it. | Package | Role | ctx key | |---|---|---| | `app-boot/` | Shared boot glue for the app bins: `.env` loading, fail-loud Loader guards, snapshot-aware config resolution, the settle-the-tree boot sequence | (library for the bins) | + +The boot sequence and personal-config contract are documented in [`app-boot/README.md`](app-boot/README.md). diff --git a/packages/boot/README.zh.md b/packages/boot/README.zh.md index aa9a56619e..d49afa45b9 100644 --- a/packages/boot/README.zh.md +++ b/packages/boot/README.zh.md @@ -2,8 +2,10 @@ [English](README.md) | 中文 -各 app bin 共享、与通道无关的启动库。一个角色完备的单包组:它不归属任何通道也不归属任何组装——`apps/cli`、[`scaffold/`](../scaffold/README.md) 启动器和 [`examples/`](../examples/README.md) 各演示 bin 都消费它。 +各 app bin 共享的、与渠道无关的启动库:`apps/cli`、[`scaffold/`](../scaffold/README.md) 启动器与 [`examples/`](../examples/README.md) demo bin 都消费它。 | 包 | 职责 | ctx 键 | |---|---|---| | `app-boot/` | app bin 的共享启动粘合层:加载 `.env`、会明确报错的 Loader 保护机制、感知快照的配置解析,以及等待整棵树停稳的启动序列 | (供各 bin 使用的库) | + +启动序列与个人配置契约见 [`app-boot/README.md`](app-boot/README.md)。 diff --git a/packages/client/README.i18n.yaml b/packages/client/README.i18n.yaml index e65d5246a4..2ce3e44fa7 100644 --- a/packages/client/README.i18n.yaml +++ b/packages/client/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/client/README.md -README.md: b6fa426fbe541e2b22d2bf5f19d4397361cf0899 -README.zh.md: 5a55bb8c2c31b5215fc73e75e1c4f3aca79add64 +README.md: 56b9363cc724515ecbd11127ea4c13aba84283df +README.zh.md: edd378bf80b63e9ab2481ab555320ed18de85139 diff --git a/packages/client/README.md b/packages/client/README.md index b6fa426fbe..56b9363cc7 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -38,3 +38,5 @@ The browser side of the dsh web GUI: shell boot, browser-host communication, sha | [`ui-models/`](ui-models/README.md) | Provides model-provider configuration and DeepSeek onboarding. | Each child reference owns its contract and detailed behavior. The [slot system standard](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md) and [web client architecture note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) own the cross-package composition and loading decisions. + +The subsystem reference is [client-modules.md](../../docs/subsystems/client-modules.md); the [slot system standard](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md) is the definitive slot model, and the [web client architecture note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) owns the loading chain and object layer. diff --git a/packages/client/README.zh.md b/packages/client/README.zh.md index 5a55bb8c2c..edd378bf80 100644 --- a/packages/client/README.zh.md +++ b/packages/client/README.zh.md @@ -38,3 +38,5 @@ dsh web GUI 的浏览器侧:shell 启动、浏览器与宿主通信、共享 U | [`ui-models/`](ui-models/README.md) | 提供模型提供方配置与 DeepSeek 配置引导。 | 每个子文档负责自身的契约和详细行为。[slot 系统标准](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md)与 [Web 客户端架构 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md)负责跨包组合与加载决策。 + +子系统参考是 [client-modules.md](../../docs/subsystems/client-modules.md);[slot 系统标准](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md)是权威 slot 模型,[web 客户端架构说明](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md)拥有加载链与对象层。 diff --git a/packages/code-runtime/README.i18n.yaml b/packages/code-runtime/README.i18n.yaml index 3eb462d513..1f9b94c59b 100644 --- a/packages/code-runtime/README.i18n.yaml +++ b/packages/code-runtime/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/code-runtime/README.md -README.md: f20a287419b94b1a9dc1d8da7303fc4d3032cfd3 -README.zh.md: f5cd4c9949f2bd7a7d6d7cd078144910712a3819 +README.md: 45dd55b8609fd33b7116f6b2ea36df3b3332e419 +README.zh.md: ebf288a49f87ad01b1ec80c3843c87c91206af26 diff --git a/packages/code-runtime/README.md b/packages/code-runtime/README.md index f20a287419..45dd55b860 100644 --- a/packages/code-runtime/README.md +++ b/packages/code-runtime/README.md @@ -10,3 +10,5 @@ The code-execution capability seam (see [capability seams](../../.agents/notes/i | [`code-runtime-worker/`](code-runtime-worker/README.md) | Worker-thread backend | registers `ctx.codeRuntime` | Backends register the seam without changing its consumer. The child READMEs own language, isolation, and execution-budget details. + +The subsystem reference — run requests/results, binding namespaces, the failure taxonomy — is [docs/subsystems/code-runtime.md](../../docs/subsystems/code-runtime.md). diff --git a/packages/code-runtime/README.zh.md b/packages/code-runtime/README.zh.md index f5cd4c9949..ebf288a49f 100644 --- a/packages/code-runtime/README.zh.md +++ b/packages/code-runtime/README.zh.md @@ -10,3 +10,5 @@ | [`code-runtime-worker/`](code-runtime-worker/README.md) | Worker 线程后端 | 注册 `ctx.codeRuntime` | 后端在不改变消费方的情况下注册该 seam。子 README 负责语言、隔离和执行预算细节。 + +子系统参考——运行请求/结果、绑定命名空间、失败分类体系——见 [docs/subsystems/code-runtime.md](../../docs/subsystems/code-runtime.md)。 diff --git a/packages/compact/README.i18n.yaml b/packages/compact/README.i18n.yaml index 17357bea7b..bb5922ee75 100644 --- a/packages/compact/README.i18n.yaml +++ b/packages/compact/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/compact/README.md -README.md: 509ea2764250f42e492f787deba959a8dd9967b7 -README.zh.md: adc52690dbe9e904d8f24199cb78493dab3333a4 +README.md: 0a83b87f57e4c948ee6df9e622f17964aff00f0f +README.zh.md: cba53ee7bb917a981a0fd4b77d833b8fb7dd2c3f diff --git a/packages/compact/README.md b/packages/compact/README.md index 509ea27642..0a83b87f57 100644 --- a/packages/compact/README.md +++ b/packages/compact/README.md @@ -12,3 +12,5 @@ A compaction capability family (see [capability seams](../../.agents/notes/imple | [`command-compact/`](command-compact/README.md) | Human compaction command | registers on `ctx.commands` | The backend, optional pruner, and human command compose through the seam; token measurement remains a separate LLM-family service. The [compaction capability-seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) owns the dependency rationale. + +The subsystem reference — the `compact/*` events, `CompactionResult`, the service, pruning outcomes — is [docs/subsystems/compaction.md](../../docs/subsystems/compaction.md); the seam's deliberate `dsh-session`/`dsh-llm` dependency is recorded in the [compaction capability-seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md). diff --git a/packages/compact/README.zh.md b/packages/compact/README.zh.md index adc52690db..cba53ee7bb 100644 --- a/packages/compact/README.zh.md +++ b/packages/compact/README.zh.md @@ -12,3 +12,5 @@ | [`command-compact/`](command-compact/README.md) | 用户压缩命令 | 注册到 `ctx.commands` | 后端、可选修剪器和用户命令通过该 seam 组合;token 测量仍是独立的 LLM(大语言模型)家族服务。[压缩能力 seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)负责依赖关系的设计原理。 + +子系统参考——`compact/*` 事件、`CompactionResult`、服务、剪除结果——见 [docs/subsystems/compaction.md](../../docs/subsystems/compaction.md);seam 有意依赖 `dsh-session`/`dsh-llm` 的决定记录在[压缩能力 seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)。 diff --git a/packages/context/README.i18n.yaml b/packages/context/README.i18n.yaml index f3acd7f2bf..efb18426a4 100644 --- a/packages/context/README.i18n.yaml +++ b/packages/context/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/context/README.md -README.md: a0b5eef51740b3aa67b9e158a00357f849b8bf57 -README.zh.md: d6ed8050f3a64f740a951883f0dc321ac08e978a +README.md: 237f6191329f89834ec9bd9f72c0a1fd1d94651e +README.zh.md: 7c55dd91dbf7e3fca222e4b6f6bed2769f5c253c diff --git a/packages/context/README.md b/packages/context/README.md index a0b5eef517..237f619132 100644 --- a/packages/context/README.md +++ b/packages/context/README.md @@ -12,3 +12,5 @@ Product plugins that add model-visible request context without defining a tool. | [`workspace-context/`](workspace-context/README.md) | Workspace-instruction context | — | The [`workspace-context` decision record](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) explains its per-agent/session isolation and lifecycle split. + +Session references are documented in [docs/subsystems/session-reference.md](../../docs/subsystems/session-reference.md); the [`workspace-context` decision record](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) owns its per-agent/session isolation and lifecycle split. diff --git a/packages/context/README.zh.md b/packages/context/README.zh.md index d6ed8050f3..7c55dd91db 100644 --- a/packages/context/README.zh.md +++ b/packages/context/README.zh.md @@ -12,3 +12,5 @@ | [`workspace-context/`](workspace-context/README.md) | workspace 指令上下文 | — | [`workspace-context` 决策记录](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)解释其逐 agent(智能体)/会话隔离和生命周期拆分。 + +会话引用见 [docs/subsystems/session-reference.md](../../docs/subsystems/session-reference.md);[`workspace-context` 决策记录](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)拥有其按 agent/会话隔离与生命周期拆分。 diff --git a/packages/core/README.i18n.yaml b/packages/core/README.i18n.yaml index d43caa011e..aa542fe885 100644 --- a/packages/core/README.i18n.yaml +++ b/packages/core/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/core/README.md -README.md: d51ef73f7f545920f8cd527b2b0d597305ae86d0 -README.zh.md: 729f41fafb6b9585a66e58fe8d6ac63c1f235acc +README.md: 40a888183a9338805d83c86bab9ae16f1c7bc04f +README.zh.md: 496d547a173a63ac9d8d2ce0f005877dab92d358 diff --git a/packages/core/README.md b/packages/core/README.md index d51ef73f7f..40a888183a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -16,3 +16,5 @@ The session log, system-prompt assembly, tool registry, agent vocabulary, and co `scope` supplies the shared scoping primitive. `agent` owns the public seam, while `agent-loop` is its default implementation; extension plugins depend on the seam so the driver remains swappable. Runnable compositions belong to [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md); this group owns only the swappable spine pieces. + +The subsystem reference — the package-by-package loop map, the `Agent` handle and its delivery/interception contracts — is [docs/subsystems/core.md](../../docs/subsystems/core.md); the default runnable composition is [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md). diff --git a/packages/core/README.zh.md b/packages/core/README.zh.md index 729f41fafb..496d547a17 100644 --- a/packages/core/README.zh.md +++ b/packages/core/README.zh.md @@ -16,3 +16,5 @@ `scope` 提供共享作用域原语。`agent` 负责公开 seam,`agent-loop` 是其默认实现;扩展插件依赖该 seam,从而保持驱动器可替换。 可运行组合属于 [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md);该分组只负责可替换的主干组件。 + +子系统参考——逐包循环地图、`Agent` 句柄及其投递/拦截契约——见 [docs/subsystems/core.md](../../docs/subsystems/core.md);默认可运行组合是 [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md)。 diff --git a/packages/credentials/README.i18n.yaml b/packages/credentials/README.i18n.yaml index 04f14e8e75..38cbbb2968 100644 --- a/packages/credentials/README.i18n.yaml +++ b/packages/credentials/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/credentials/README.md -README.md: c08831c90333f8515bf86a5af717c38c50b50817 -README.zh.md: c9756c010af5d2db0fc41f3108eabdc7f1161293 +README.md: 75e3941ff6421ca7e41a71ab2bfe8b30209ce598 +README.zh.md: d7c958689fdffc82830a82b04e2a06802bc81634 diff --git a/packages/credentials/README.md b/packages/credentials/README.md index c08831c903..75e3941ff6 100644 --- a/packages/credentials/README.md +++ b/packages/credentials/README.md @@ -10,3 +10,5 @@ The credential capability family separates reference resolution from its provide | [`credentials-local/`](credentials-local/README.md) | Environment and local-file provider | registers `ctx.credentials` | Configuration carries references, not secret values. Consumers resolve those references at their operation boundary; the child READMEs own mutation, precedence, and storage semantics. + +The subsystem reference — `CredentialRef`, per-operation resolution, UI-safe `CredentialInfo`, provider layers — is [docs/subsystems/credentials.md](../../docs/subsystems/credentials.md). diff --git a/packages/credentials/README.zh.md b/packages/credentials/README.zh.md index c9756c010a..d7c958689f 100644 --- a/packages/credentials/README.zh.md +++ b/packages/credentials/README.zh.md @@ -10,3 +10,5 @@ | [`credentials-local/`](credentials-local/README.md) | 环境与本地文件提供方 | 注册 `ctx.credentials` | 配置携带引用而非机密值。消费方在其操作边界解析这些引用;变更、优先级与存储语义由子级 README 负责。 + +子系统参考——`CredentialRef`、按操作解析、对 UI 安全的 `CredentialInfo`、提供方层——见 [docs/subsystems/credentials.md](../../docs/subsystems/credentials.md)。 diff --git a/packages/fs/README.i18n.yaml b/packages/fs/README.i18n.yaml index f4719447aa..b1e493f3c8 100644 --- a/packages/fs/README.i18n.yaml +++ b/packages/fs/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/fs/README.md -README.md: b15012e882b60847e1ad22edf08d1202ba64fe5b -README.zh.md: 628f6c74894bc67559d49f7cf5d1378d0ece2382 +README.md: 162e2c75e92bc4ea6aba377c14db62e8ce83fbb9 +README.zh.md: b2929b17be6ce90051f0808c2cdf4bbf643f8f13 diff --git a/packages/fs/README.md b/packages/fs/README.md index b15012e882..162e2c75e9 100644 --- a/packages/fs/README.md +++ b/packages/fs/README.md @@ -19,3 +19,5 @@ The interface lives at `fs/fs/`. A sandboxed, remote, or project-scoped filesyst ## No timeouts on file IO `read`/`write`/`edit` take **no** `timeoutMs`, and the provider seam arms no deadline — unlike bash and web (which consume [`@deepseek-ai/dsh-timeout`](../util/timeout/README.md)) and the bash-backed `glob`/`grep` (whose declared `timeoutMs` is enforced by `@deepseek-ai/dsh-timeout-policy`): those are process-backed, where a deadline can really kill the work. A local syscall is best-effort-abortable at most: a timeout could not force an in-progress `fsync`/`rename` to stop, so a deadline here would be a knob that cannot deliver on its promise. Adding one would also be an implicit default in the exact place explicit-over-implicit forbids. Both reference agents (Claude Code, Codex) leave file IO untimed for the same reason; cancellation still propagates through the tool-execution signal for best-effort abort at syscall boundaries. + +The subsystem reference — targets, outcomes, guards, policy events, the error taxonomy, and why file IO takes no timeout — is [docs/subsystems/filesystem.md](../../docs/subsystems/filesystem.md); the sandbox fence in the [cross-family fs sandbox Agent Note](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md). diff --git a/packages/fs/README.zh.md b/packages/fs/README.zh.md index 628f6c7489..b2929b17be 100644 --- a/packages/fs/README.zh.md +++ b/packages/fs/README.zh.md @@ -19,3 +19,5 @@ ## 文件 I/O 不设超时 `read`/`write`/`edit` **不** 接受 `timeoutMs`,提供方 seam 也不启动 deadline。这与 bash 和 web(两者使用 [`@deepseek-ai/dsh-timeout`](../util/timeout/README.md))及基于 bash 的 `glob`/`grep` 不同(它们声明的 `timeoutMs` 由 `@deepseek-ai/dsh-timeout-policy` 强制执行):这些工作由进程支持,deadline 可以实际终止工作。本地系统调用至多只能尽力中止:超时无法强制正在进行的 `fsync`/`rename` 停止,因此这里的 deadline 会成为无法兑现承诺的配置项。在此添加 deadline 还会在「显式优于隐式」明确禁止的地方引入隐式默认值。两个参考 agent(Claude Code、Codex)出于同一原因都不为文件 I/O 计时;取消仍通过工具执行信号传播,在系统调用边界尽力中止。 + +子系统参考——目标、结果、防护、策略事件、错误分类体系,以及文件 IO 为何不设超时——见 [docs/subsystems/filesystem.md](../../docs/subsystems/filesystem.md);沙箱围栏见[跨家族 fs 沙箱 Agent Note](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md)。 diff --git a/packages/goal/README.i18n.yaml b/packages/goal/README.i18n.yaml index 4238bac3c4..d2a6bf0248 100644 --- a/packages/goal/README.i18n.yaml +++ b/packages/goal/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/goal/README.md -README.md: 9fc6b0c18b1862a8be08275785ea3b6185e9bdbc -README.zh.md: 08f15bcc4e405e25d4dfd5981bbc99408833403c +README.md: 9d307278b39ecee61df9feace9fec4235b45d794 +README.zh.md: 7217c174ba5382c53204ae3678be42375fc63db5 diff --git a/packages/goal/README.md b/packages/goal/README.md index 9fc6b0c18b..9d307278b3 100644 --- a/packages/goal/README.md +++ b/packages/goal/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The goal family owns durable objective state independently of the model-facing tools and continuation policy that consume it. +Durable objective state for an agent session, owned independently of the model-facing tools and continuation policy that consume it. Goal state is part of the owning session log; consumers depend on `dsh-goal`, never on the concrete agent loop. | Package | Role | ctx key | |---|---|---| @@ -11,4 +11,4 @@ The goal family owns durable objective state independently of the model-facing t | [`tool-goal/`](tool-goal/README.md) | Model-facing goal tools | — | | [`command-goal/`](command-goal/README.md) | Human-facing goal command | — | -Goal state is part of the owning session log. Consumers depend on `dsh-goal`, not on the concrete agent loop; continuation behavior belongs in a separate plugin on the public agent seams. +The subsystem reference — goal identity, lifecycle snapshots, activation, change records — is [docs/subsystems/goal.md](../../docs/subsystems/goal.md). diff --git a/packages/goal/README.zh.md b/packages/goal/README.zh.md index 08f15bcc4e..7217c174ba 100644 --- a/packages/goal/README.zh.md +++ b/packages/goal/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -goal 家族负责持久目标状态,与消费该状态的面向模型工具和续行策略相互独立。 +agent 会话的持久目标状态,独立于消费它的模型工具与续跑策略。goal 状态是所属会话日志的一部分;消费方依赖 `dsh-goal`,绝不依赖具体 agent loop。 | 包 | 职责 | ctx 键 | |---|---|---| @@ -11,4 +11,4 @@ goal 家族负责持久目标状态,与消费该状态的面向模型工具和 | [`tool-goal/`](tool-goal/README.md) | 面向模型的目标工具 | 无 | | [`command-goal/`](command-goal/README.md) | 面向用户的目标命令 | 无 | -目标状态是其所属会话日志的一部分。消费方依赖 `dsh-goal`,而不是具体的 agent loop(智能体循环);续行行为由基于公开 agent seam 的独立插件负责。 +子系统参考——goal 标识、生命周期快照、激活、变更记录——见 [docs/subsystems/goal.md](../../docs/subsystems/goal.md)。 diff --git a/packages/guard/README.i18n.yaml b/packages/guard/README.i18n.yaml index 8251a76ed5..b99cec734b 100644 --- a/packages/guard/README.i18n.yaml +++ b/packages/guard/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/guard/README.md -README.md: 5791474a2fd6aeda728d4c335c930069ade6e023 -README.zh.md: 82d779d2f1834317672b8fb03f2a4a875bb566fc +README.md: f0a80b1f497ee5b2459bebb55f886b42d74e4323 +README.zh.md: c7747e2febcb1656925fd556743eb2dd28090e37 diff --git a/packages/guard/README.md b/packages/guard/README.md index 5791474a2f..f0a80b1f49 100644 --- a/packages/guard/README.md +++ b/packages/guard/README.md @@ -8,3 +8,5 @@ Behavioral guard plugins watch the agent loop for unproductive patterns and enfo |---|---|---| | [`repeat-tool-guard/`](repeat-tool-guard/README.md) | Advisory reminders for repeated tool calls | listens on tool and agent events | | [`timeout-policy/`](timeout-policy/README.md) | Arms per-call tool deadlines as deployment policy | registers a `tools/execute` listener | + +Reminders travel as `additionalContexts` on the `tools/post-execute` decision and are appended as logged plugin-sourced `user/message` events ([tools](../../docs/subsystems/tools.md)); the timeout split across `dsh-timeout`, capability termination, and this policy layer is recorded in the [timeout-library Agent Note](../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md). diff --git a/packages/guard/README.zh.md b/packages/guard/README.zh.md index 82d779d2f1..c7747e2feb 100644 --- a/packages/guard/README.zh.md +++ b/packages/guard/README.zh.md @@ -8,3 +8,5 @@ |---|---|---| | [`repeat-tool-guard/`](repeat-tool-guard/README.md) | 针对重复工具调用的建议性提醒 | 监听工具和 agent 事件 | | [`timeout-policy/`](timeout-policy/README.md) | 以部署策略形式设置单次工具调用截止时间 | 注册 `tools/execute` 监听器 | + +提醒作为 `additionalContexts` 随 `tools/post-execute` 决策传递,并以插件来源的 `user/message` 事件记录([工具](../../docs/subsystems/tools.md));跨 `dsh-timeout`、能力方终止与本策略层的超时拆分记录在[超时库 Agent Note](../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)。 diff --git a/packages/host/README.i18n.yaml b/packages/host/README.i18n.yaml index 1aaacd7ecb..0859a2571d 100644 --- a/packages/host/README.i18n.yaml +++ b/packages/host/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/host/README.md -README.md: 269a27f51c842f13bc11c175916b7be22db72bd2 -README.zh.md: 559bf785eb45d59a30f676b98c14143c69d57edd +README.md: 926cb0b6b87a8ee76cb2dab745a31f620f4e7f5c +README.zh.md: dc617084c756b80716f9a02bd2d22ba6954030f6 diff --git a/packages/host/README.md b/packages/host/README.md index 269a27f51c..926cb0b6b8 100644 --- a/packages/host/README.md +++ b/packages/host/README.md @@ -15,3 +15,5 @@ The host side of the dsh web GUI: the API gateway every client shape shares, and | [`directory-picker-auto/`](directory-picker-auto/README.md) | Host-adaptive picker composition | mounts a backend | `apiproxy` remains transport-independent; [`client/connection`](../client/connection/README.md) supplies the browser/HTTP carrier. Picker implementations replace one another behind the shared seam. + +The subsystem references: [http-server.md](../../docs/subsystems/http-server.md) and [workspace.md](../../docs/subsystems/workspace.md) (the picker seam). diff --git a/packages/host/README.zh.md b/packages/host/README.zh.md index 559bf785eb..dc617084c7 100644 --- a/packages/host/README.zh.md +++ b/packages/host/README.zh.md @@ -15,3 +15,5 @@ dsh Web GUI 的宿主侧:所有客户端形态共享的 API 网关,以及承 | [`directory-picker-auto/`](directory-picker-auto/README.md) | 宿主自适应选择器组合 | 挂载一个后端 | `apiproxy` 保持传输无关;[`client/connection`](../client/connection/README.md) 提供浏览器/HTTP 载体。选择器实现可在共享 seam 后互相替换。 + +子系统参考:[http-server.md](../../docs/subsystems/http-server.md) 与 [workspace.md](../../docs/subsystems/workspace.md)(挑选 seam)。 diff --git a/packages/interaction/README.i18n.yaml b/packages/interaction/README.i18n.yaml index 2555e896a1..77539b60fb 100644 --- a/packages/interaction/README.i18n.yaml +++ b/packages/interaction/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/interaction/README.md -README.md: 506b47753fc9250f9f7bd971265421ad46a106b2 -README.zh.md: 34d336f3d3c10deab166d5c13bb86c13ee9bced0 +README.md: 31a306cb08fe69297b0d0843b57b5e491c0bbe44 +README.zh.md: 6eafe8726ba59edd3a6bf739deacc037a9b56b76 diff --git a/packages/interaction/README.md b/packages/interaction/README.md index 506b47753f..31a306cb08 100644 --- a/packages/interaction/README.md +++ b/packages/interaction/README.md @@ -13,3 +13,5 @@ The seams through which a human collaborates with a running agent — questions, | [`tool-ask-user/`](tool-ask-user/README.md) | Exposes human questions to the model. | (registers on `ctx.tools`) | These packages integrate through existing agent and session contracts rather than changing the loop. Interactive applications provide the concrete command, approval, and question adapters; automation uses [`acp/`](../acp/README.md), and runnable demo bundles live under [`examples/`](../examples/README.md). The product [`dsh`](../../apps/cli/README.md) CLI composes these packages directly. + +The subsystem references: [approval.md](../../docs/subsystems/approval.md), [permission.md](../../docs/subsystems/permission.md), [user-interaction.md](../../docs/subsystems/user-interaction.md), [commands.md](../../docs/subsystems/commands.md), and [tui.md](../../docs/subsystems/tui.md). The automation-only ACP transport is [`acp/`](../acp/README.md), the SDK's JSON-RPC server half [`scaffold/server`](../scaffold/README.md), and the shared bin boot glue [`boot/`](../boot/README.md). diff --git a/packages/interaction/README.zh.md b/packages/interaction/README.zh.md index 34d336f3d3..6eafe8726b 100644 --- a/packages/interaction/README.zh.md +++ b/packages/interaction/README.zh.md @@ -13,3 +13,5 @@ | [`tool-ask-user/`](tool-ask-user/README.md) | 向模型公开用户问题。 | (注册到 `ctx.tools`) | 这些包通过现有的 agent(智能体)和会话契约集成,而不改变循环。交互式应用提供具体的命令、审批和提问适配器;自动化使用 [`acp/`](../acp/README.md),可运行的演示组合包位于 [`examples/`](../examples/README.md)。产品 [`dsh`](../../apps/cli/README.md) CLI(命令行界面)直接组合这些包。 + +子系统参考:[approval.md](../../docs/subsystems/approval.md)、[permission.md](../../docs/subsystems/permission.md)、[user-interaction.md](../../docs/subsystems/user-interaction.md)、[commands.md](../../docs/subsystems/commands.md) 与 [tui.md](../../docs/subsystems/tui.md)。仅自动化的 ACP 传输在 [`acp/`](../acp/README.md),SDK 的 JSON-RPC 服务器一半在 [`scaffold/server`](../scaffold/README.md),共享 bin 启动胶水在 [`boot/`](../boot/README.md)。 diff --git a/packages/llm/README.i18n.yaml b/packages/llm/README.i18n.yaml index 3c4ae19900..a35bf870d4 100644 --- a/packages/llm/README.i18n.yaml +++ b/packages/llm/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/llm/README.md -README.md: 92d9fbfa2b8c8db4700562009db49229b2189ab3 -README.zh.md: 5c6e7aad1db6511bdb660b86e257652128db131f +README.md: 077ae600c89cff80cd71830e37bda00707c1d837 +README.zh.md: e2bdad7fd0b33116498b50bfa6527974fa140fc9 diff --git a/packages/llm/README.md b/packages/llm/README.md index 92d9fbfa2b..077ae600c8 100644 --- a/packages/llm/README.md +++ b/packages/llm/README.md @@ -13,3 +13,5 @@ The LLM seam and its provider adapters. The interface package (`llm`) owns the a | [`llm-pi-ai/`](llm-pi-ai/README.md) | Multi-provider pi-ai adapter | registers on `ctx.llm` | Adapters register provider routes on the seam; retry and token measurement remain separate consumers. The child READMEs own routing, metadata, replay, and provider-wire details; the [LLM architecture decisions](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md) own the rationale. + +The subsystem reference — messages and blocks, the model request, the `StreamChunk` protocol, the adapter contract — is [docs/subsystems/llm-streaming.md](../../docs/subsystems/llm-streaming.md) (token measurement: [token-meter.md](../../docs/subsystems/token-meter.md)); see the [twin adapters](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md), [replay token meter](../../.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.md), and [routed model context](../../.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md) Agent Notes. diff --git a/packages/llm/README.zh.md b/packages/llm/README.zh.md index 5c6e7aad1d..e2bdad7fd0 100644 --- a/packages/llm/README.zh.md +++ b/packages/llm/README.zh.md @@ -13,3 +13,5 @@ LLM(大语言模型)seam 及其提供方适配器。接口包(`llm`)负 | [`llm-pi-ai/`](llm-pi-ai/README.md) | 多提供方 pi-ai 适配器 | 注册到 `ctx.llm` | 适配器在 seam 上注册提供方路由;重试与 token 测量仍是独立消费方。子 README 负责路由、元数据、回放和提供方协议细节;[LLM 架构决策](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md)负责设计原理。 + +子系统参考——消息与内容块、模型请求、`StreamChunk` 协议、适配器契约——见 [docs/subsystems/llm-streaming.md](../../docs/subsystems/llm-streaming.md)(token 计量:[token-meter.md](../../docs/subsystems/token-meter.md));另见[孪生适配器](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md)、[回放 token 计量](../../.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.md)与[按路由模型上下文](../../.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md) Agent Note。 diff --git a/packages/lsp/README.i18n.yaml b/packages/lsp/README.i18n.yaml index 9919cdaba5..be1dc838ff 100644 --- a/packages/lsp/README.i18n.yaml +++ b/packages/lsp/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/lsp/README.md -README.md: 7fbdf071735673fb0158f6fa66148be1c644a433 -README.zh.md: e059dbd80b7e38c0e447e54178162316dfd127c7 +README.md: 01c464c9993571990e0542df9fadb2848a6c9a79 +README.zh.md: fa7dddeb011bd34c1f9cbe3a9dbf746d7f163897 diff --git a/packages/lsp/README.md b/packages/lsp/README.md index 7fbdf07173..01c464c999 100644 --- a/packages/lsp/README.md +++ b/packages/lsp/README.md @@ -13,3 +13,5 @@ The language-server capability seam: an abstract LSP interface, a generic stdio The interface lives at `lsp/lsp/`. The seam exposes exactly four semantic operations — `goToDefinition`, `findReferences`, `goToImplementation`, `hover` — and no generic JSON-RPC escape hatch, so a provider swap does not change how the model asks for navigation and no protocol payload or unreviewed mutation reaches the model contract. Providers register **capabilities**, not tools; `tool-lsp` is the only owner of the model-facing name, schema, prompt guidance, and presentation. See the [LSP capability seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md) for the design rationale, including why documents open transiently per query, why the stdio host consumes the shared filesystem/subprocess execution world, and why extension ownership is exclusive within one runtime. + +The subsystem reference — operations, coordinates, requests/results, `LspError` — is [docs/subsystems/lsp.md](../../docs/subsystems/lsp.md); design rationale in the [LSP capability seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md). diff --git a/packages/lsp/README.zh.md b/packages/lsp/README.zh.md index e059dbd80b..fa7dddeb01 100644 --- a/packages/lsp/README.zh.md +++ b/packages/lsp/README.zh.md @@ -13,3 +13,5 @@ 接口位于 `lsp/lsp/`。该 seam 恰好公开四种语义操作:`goToDefinition`、`findReferences`、`goToImplementation`、`hover`,且不提供通用 JSON-RPC 逃生口;因此,替换提供方不会改变模型请求导航的方式,也不会让协议载荷或未经评审的修改进入模型契约。提供方注册的是**能力** 而非工具;`tool-lsp` 是面向模型名称、schema、提示词指引和呈现的唯一 owner。 设计原理见 [LSP 能力 seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md),其中也解释了文档为何在每次查询时临时打开、stdio 主机为何使用共享的文件系统/子进程执行环境,以及扩展名归属为何在同一运行时内互斥。 + +子系统参考——操作、坐标、请求/结果、`LspError`——见 [docs/subsystems/lsp.md](../../docs/subsystems/lsp.md);设计依据见 [LSP 能力 seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md)。 diff --git a/packages/plan/README.i18n.yaml b/packages/plan/README.i18n.yaml index 3ae6755be6..4e34bac80c 100644 --- a/packages/plan/README.i18n.yaml +++ b/packages/plan/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/plan/README.md -README.md: 598974e105aaeaf3c35418aec0655de2a5ba6888 -README.zh.md: 0f54299af5d34555d86d82e83b92d185045e770f +README.md: 535a2b02e297d7c3bc68538fa1d03226089372e0 +README.zh.md: f068bf37b44daa0d724fbb85ad85a4d62883e847 diff --git a/packages/plan/README.md b/packages/plan/README.md index 598974e105..535a2b02e2 100644 --- a/packages/plan/README.md +++ b/packages/plan/README.md @@ -9,3 +9,5 @@ Plan mode is logged, per-agent collaboration state rather than a generic mode re | [`plan-mode/`](plan-mode/README.md) | Owns plan-mode state, guidance, commands, and review flow | `ctx.planMode` | The [plan-specific collaboration state](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md) decision records the family design. + +The subsystem reference — the `plan/mode` fold, the step-boundary flush, configuration, the exit tool — is [docs/subsystems/plan.md](../../docs/subsystems/plan.md); design in [plan-specific collaboration state](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md). diff --git a/packages/plan/README.zh.md b/packages/plan/README.zh.md index 0f54299af5..f068bf37b4 100644 --- a/packages/plan/README.zh.md +++ b/packages/plan/README.zh.md @@ -9,3 +9,5 @@ Plan mode 是按 agent(智能体)记录的协作状态,而不是通用模 | [`plan-mode/`](plan-mode/README.md) | 负责 plan mode 状态、指引、命令和评审流程 | `ctx.planMode` | [plan 专用协作状态](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)决策记录了该家族的设计。 + +子系统参考——`plan/mode` 折叠、步边界冲刷、配置、退出工具——见 [docs/subsystems/plan.md](../../docs/subsystems/plan.md);设计见[计划专属协作状态](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)。 diff --git a/packages/pty/README.i18n.yaml b/packages/pty/README.i18n.yaml index 65d541a7c1..7f4c7f1f21 100644 --- a/packages/pty/README.i18n.yaml +++ b/packages/pty/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/pty/README.md -README.md: a4f743056b4a524be9623b0f700f37e0534b463f -README.zh.md: c84ad3f1b59afcdbbd111f1b82c57c56aa24fdcf +README.md: 2afb3cd61ef4bfba460716549d41f799e419ff46 +README.zh.md: 57a5c93047ccb970f499f02c0168db7e9f71b622 diff --git a/packages/pty/README.md b/packages/pty/README.md index a4f743056b..2afb3cd61e 100644 --- a/packages/pty/README.md +++ b/packages/pty/README.md @@ -11,3 +11,5 @@ English | [中文](README.zh.md) | `tool-pty` (`@deepseek-ai/dsh-tool-pty`) | Six model-facing tools and generic task integration for background sends | registers on `ctx.tools` | The design and deferred boundaries live in the [persistent PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md). + +The subsystem reference — ids, backend/session contracts, send readiness, bounded reads — is [docs/subsystems/pty.md](../../docs/subsystems/pty.md); design and deferred boundaries in the [persistent PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md). diff --git a/packages/pty/README.zh.md b/packages/pty/README.zh.md index c84ad3f1b5..57a5c93047 100644 --- a/packages/pty/README.zh.md +++ b/packages/pty/README.zh.md @@ -11,3 +11,5 @@ | `tool-pty`(`@deepseek-ai/dsh-tool-pty`) | 6 个面向模型的工具,并为后台发送集成通用任务 | 注册到 `ctx.tools` | 设计与暂缓边界记录在[持久 PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md) 中。 + +子系统参考——id、后端/会话契约、发送就绪、有界读取——见 [docs/subsystems/pty.md](../../docs/subsystems/pty.md);设计与延后边界见[持久 PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md)。 diff --git a/packages/sandbox/README.i18n.yaml b/packages/sandbox/README.i18n.yaml index c851d420ae..ab79d37170 100644 --- a/packages/sandbox/README.i18n.yaml +++ b/packages/sandbox/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/sandbox/README.md -README.md: 1ba56d22330c26302c9283b2319d983ebf902219 -README.zh.md: 5daf830854297fe14c6560dbcde8a380ad079a0e +README.md: 61127118c895cce728ab0b4eed387f51054a83f1 +README.zh.md: 984a677aad6ab97579abf861ea8b99f149c07d08 diff --git a/packages/sandbox/README.md b/packages/sandbox/README.md index 1ba56d2233..61127118c8 100644 --- a/packages/sandbox/README.md +++ b/packages/sandbox/README.md @@ -11,3 +11,5 @@ This family applies per-session confinement policy to process execution. It cove | [`sandbox-policy/`](sandbox-policy/README.md) | Resolves durable per-session sandbox policy | `ctx.sandboxPolicy` | See the [sandbox decision](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) for the capability boundary and the [filesystem integration decision](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) for cross-family policy use. + +The subsystem reference — modes and enforcement, per-call policy, wrapped-argv dialects, fail-closed errors — is [docs/subsystems/sandbox.md](../../docs/subsystems/sandbox.md); the boundary and the cross-family phase live in the [sandbox](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) and [cross-family fs sandbox](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) Agent Notes. diff --git a/packages/sandbox/README.zh.md b/packages/sandbox/README.zh.md index 5daf830854..984a677aad 100644 --- a/packages/sandbox/README.zh.md +++ b/packages/sandbox/README.zh.md @@ -11,3 +11,5 @@ | [`sandbox-policy/`](sandbox-policy/README.md) | 解析持久的逐会话沙箱策略 | `ctx.sandboxPolicy` | [沙箱决策](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)记录了能力边界,[文件系统集成决策](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md)记录了跨家族策略的使用方式。 + +子系统参考——模式与强制执行、按调用策略、包装 argv 方言、故障关闭错误——见 [docs/subsystems/sandbox.md](../../docs/subsystems/sandbox.md);边界与跨家族阶段见[沙箱](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)与[跨家族 fs 沙箱](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) Agent Note。 diff --git a/packages/self-modification/README.i18n.yaml b/packages/self-modification/README.i18n.yaml index 9a6a070b95..53bbf9cdbb 100644 --- a/packages/self-modification/README.i18n.yaml +++ b/packages/self-modification/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/self-modification/README.md README.md: 53f700993cce8df8729fba5a92a9498fff2412cd -README.zh.md: 9874ead263e9860b94992cb176be3c6f8c454ece +README.zh.md: 90030cda33f47d128041b7619546ac1309b8465a diff --git a/packages/self-modification/README.zh.md b/packages/self-modification/README.zh.md index 9874ead263..90030cda33 100644 --- a/packages/self-modification/README.zh.md +++ b/packages/self-modification/README.zh.md @@ -2,9 +2,11 @@ [English](README.md) | 中文 -这些面向模型的工具作用于 agent(智能体)自身所在的实时 Cordis 运行时,可检查已加载的插件和服务接口、挂载模型编写的插件,并将其 dispose(资源释放)——外加受限 repository Plugin 运行时。该组也是未来自我修改类包的落点。设计说明见[工具集 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。 +agent 修改自身运行时:检查已加载的插件与服务接口、挂载模型编写的插件并再次 dispose,外加受限 repository Plugin 运行时。该组是未来自我修改类包的落点。设计居所:[工具集 Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。 | 包(package) | 角色 | ctx 键 | |---|---|---| | [`tool-cordis/`](tool-cordis/README.md) | `cordis_inspect`/`cordis_mount`/`cordis_unmount` 工具:读取当前进程运行时,并在一个自有分组 fiber 下管理内存中的临时插件 | 注册到 `ctx.tools` | | [`repository-plugin/`](repository-plugin/README.md) | 通过 DSH 自有子 Plugin 准备并挂载静态 repository skills 与通用 `.mcp.json` server | 注册一个 Loader builtin | + +工具契约见 [`tool-cordis/README.md`](tool-cordis/README.md) 与 [`repository-plugin/README.md`](repository-plugin/README.md)。 diff --git a/packages/session-query/README.i18n.yaml b/packages/session-query/README.i18n.yaml index 3911bf76a0..2922f4eb88 100644 --- a/packages/session-query/README.i18n.yaml +++ b/packages/session-query/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/session-query/README.md -README.md: a9c9f33e2dc67e837c2be728d2b1a937ce50f13d -README.zh.md: a29dedba09271baa5836cb2d9d57c753ee41c660 +README.md: 3d5db8da74cb825c701fe50cf518505e9e6bcad9 +README.zh.md: fc430cbdc4130f77eb8857c4d88a2435ad0cc97f diff --git a/packages/session-query/README.md b/packages/session-query/README.md index a9c9f33e2d..3d5db8da74 100644 --- a/packages/session-query/README.md +++ b/packages/session-query/README.md @@ -9,3 +9,5 @@ This family provides authorized retrieval over live and durable session logs, in | [`session-query/`](session-query/README.md) | Defines trusted reads, relationship queries, and search operations | `ctx.sessionQuery` | | [`session-query-sqlite/`](session-query-sqlite/README.md) | Implements session queries with SQLite full-text search | `ctx.sessionQuery` | | [`tool-session-query/`](tool-session-query/README.md) | Exposes workspace-authorized session queries to the model | registers on `ctx.tools` | + +The subsystem reference — logical records, bounded reads, traces, filters, result pages — is [docs/subsystems/session-query.md](../../docs/subsystems/session-query.md). diff --git a/packages/session-query/README.zh.md b/packages/session-query/README.zh.md index a29dedba09..fc430cbdc4 100644 --- a/packages/session-query/README.zh.md +++ b/packages/session-query/README.zh.md @@ -9,3 +9,5 @@ | [`session-query/`](session-query/README.md) | 定义可信读取、关系查询和搜索操作 | `ctx.sessionQuery` | | [`session-query-sqlite/`](session-query-sqlite/README.md) | 使用 SQLite 全文搜索实现会话查询 | `ctx.sessionQuery` | | [`tool-session-query/`](tool-session-query/README.md) | 向模型公开经过工作区授权的会话查询 | 注册到 `ctx.tools` | + +子系统参考——逻辑记录、有界读取、追踪、筛选器、结果页——见 [docs/subsystems/session-query.md](../../docs/subsystems/session-query.md)。 diff --git a/packages/session/README.i18n.yaml b/packages/session/README.i18n.yaml index b500a64611..3829d946f0 100644 --- a/packages/session/README.i18n.yaml +++ b/packages/session/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/session/README.md -README.md: 45eff16ad57ec1ccc8ebdd55db82f41b5adfc111 -README.zh.md: 2d67e28ef169f4e4376b2a644a3b842e5835df04 +README.md: 586d1be0286a0de935b0b08313e6965452b85376 +README.zh.md: 60e58e6d471a48d3ced03a518e96aeeae79110e8 diff --git a/packages/session/README.md b/packages/session/README.md index 45eff16ad5..586d1be028 100644 --- a/packages/session/README.md +++ b/packages/session/README.md @@ -47,3 +47,5 @@ Projects session activity into outbound telemetry and delegates delivery to a co |---|---| | [`session-telemetry/`](session-telemetry/README.md) | Defines capture, redaction, projection, and live or on-demand backend delivery. | | [`session-telemetry-otel/`](session-telemetry-otel/README.md) | Delivers telemetry through OpenTelemetry logs in `FULL`, `FEEDBACK_ONLY`, or `DISABLED` mode. | + +The subsystem references: [persistence.md](../../docs/subsystems/persistence.md), [session-projection.md](../../docs/subsystems/session-projection.md), [session-title.md](../../docs/subsystems/session-title.md), and [telemetry.md](../../docs/subsystems/telemetry.md). Only one title provider may register at a time; the demo spine mounts the fallback service and leaves both model providers out of default composition. diff --git a/packages/session/README.zh.md b/packages/session/README.zh.md index 2d67e28ef1..60e58e6d47 100644 --- a/packages/session/README.zh.md +++ b/packages/session/README.zh.md @@ -47,3 +47,5 @@ |---|---| | [`session-telemetry/`](session-telemetry/README.md) | 定义捕获、脱敏、投影,以及实时或按需后端投递。 | | [`session-telemetry-otel/`](session-telemetry-otel/README.md) | 通过 OpenTelemetry 日志以 `FULL`、`FEEDBACK_ONLY` 或 `DISABLED` 模式投递遥测。 | + +子系统参考:[persistence.md](../../docs/subsystems/persistence.md)、[session-projection.md](../../docs/subsystems/session-projection.md)、[session-title.md](../../docs/subsystems/session-title.md) 与 [telemetry.md](../../docs/subsystems/telemetry.md)。同一时间只允许一个标题提供方注册;demo 主干挂载回退服务,两个模型提供方都留在默认组合之外。 diff --git a/packages/settings/README.i18n.yaml b/packages/settings/README.i18n.yaml index ec2d2124a3..146f0ad32e 100644 --- a/packages/settings/README.i18n.yaml +++ b/packages/settings/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/settings/README.md -README.md: 3f43647f0558abf53c373ef7d97af16c0e17d2fe -README.zh.md: b5779bfe5da4cae148bcc6515ef13790f78bca7a +README.md: 6f3719c58564a90fec3bd9f305cfaeb2b7bf0bf4 +README.zh.md: 0efb16dd4e5d5faa5f16814b94d55a20dc4c06c3 diff --git a/packages/settings/README.md b/packages/settings/README.md index 3f43647f05..6f3719c585 100644 --- a/packages/settings/README.md +++ b/packages/settings/README.md @@ -8,3 +8,5 @@ This family resolves user-editable configuration through registered namespaces a |---|---|---| | [`settings/`](settings/README.md) | Defines namespace registration, layered resolution, and commits | `ctx.settings` | | [`settings-local/`](settings-local/README.md) | Stores settings in a local file and observes external edits | registers on `ctx.settings` | + +The subsystem reference — namespaces, owner scopes, resolution order, hot commits — is [docs/subsystems/settings.md](../../docs/subsystems/settings.md). diff --git a/packages/settings/README.zh.md b/packages/settings/README.zh.md index b5779bfe5d..0efb16dd4e 100644 --- a/packages/settings/README.zh.md +++ b/packages/settings/README.zh.md @@ -8,3 +8,5 @@ |---|---|---| | [`settings/`](settings/README.md) | 定义命名空间注册、分层解析与提交 | `ctx.settings` | | [`settings-local/`](settings-local/README.md) | 在本地文件中存储设置并观察外部编辑 | 注册到 `ctx.settings` | + +子系统参考——命名空间、owner scope、解析顺序、热提交——见 [docs/subsystems/settings.md](../../docs/subsystems/settings.md)。 diff --git a/packages/skill/README.i18n.yaml b/packages/skill/README.i18n.yaml index 74875f2aa3..758d0552f9 100644 --- a/packages/skill/README.i18n.yaml +++ b/packages/skill/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/skill/README.md -README.md: 533904859ad998de4f371a073fde98b68660097b -README.zh.md: 1fad581cc61a05251f671577dcb7edab37281283 +README.md: cd606e778335249bf8ecc876d3b1a4e5feb79448 +README.zh.md: 4db7e2524596f4c62144ec7eabe7c6af63e2e270 diff --git a/packages/skill/README.md b/packages/skill/README.md index 533904859a..cd606e7783 100644 --- a/packages/skill/README.md +++ b/packages/skill/README.md @@ -12,3 +12,5 @@ This family discovers reusable agent instructions and exposes them to the model | [`tool-skill/`](tool-skill/README.md) | Publishes the skill catalog and model-facing loader | registers on `ctx.tools` | This capability remains outside the core control spine and can use local, embedded, or remote providers without changing the model-facing contract. + +The subsystem reference — discovery priority, catalog snapshots, the `skill` loader — is [docs/subsystems/skills.md](../../docs/subsystems/skills.md). diff --git a/packages/skill/README.zh.md b/packages/skill/README.zh.md index 1fad581cc6..4db7e25245 100644 --- a/packages/skill/README.zh.md +++ b/packages/skill/README.zh.md @@ -12,3 +12,5 @@ | [`tool-skill/`](tool-skill/README.md) | 发布 skill 目录和面向模型的 loader | 注册到 `ctx.tools` | 该能力位于核心控制主干之外,可以使用本地、嵌入式或远程提供方,而无需更改面向模型的契约。 + +子系统参考——发现优先级、目录快照、`skill` 加载器——见 [docs/subsystems/skills.md](../../docs/subsystems/skills.md)。 diff --git a/packages/spill/README.i18n.yaml b/packages/spill/README.i18n.yaml index 95800ac03e..62ed26a59b 100644 --- a/packages/spill/README.i18n.yaml +++ b/packages/spill/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/spill/README.md -README.md: b96375b1a332cd6e3e4da9c8b1c98011aa97c8bc -README.zh.md: c8495068edf1035937c99198dfdb5db65c9a9e59 +README.md: 74fd4837cb23767754174df168f2f4ba71c2b938 +README.zh.md: 16d677719a9c3f4245a2f446017641ff6b50ffa8 diff --git a/packages/spill/README.md b/packages/spill/README.md index b96375b1a3..74fd4837cb 100644 --- a/packages/spill/README.md +++ b/packages/spill/README.md @@ -11,3 +11,5 @@ This family persists oversized tool output and replaces the inline result with a | [`spill-policy/`](spill-policy/README.md) | Applies the post-execution spill policy | listens on `ctx.tools` | See the [tool-output spill decision](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md) for the boundary between storage, retention, and tool-owned output handling. + +The subsystem reference — `SaveTextSpill`, owners/sources, the branded locator — is [docs/subsystems/spill.md](../../docs/subsystems/spill.md); rationale in the [tool output spill Agent Note](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md). diff --git a/packages/spill/README.zh.md b/packages/spill/README.zh.md index c8495068ed..16d677719a 100644 --- a/packages/spill/README.zh.md +++ b/packages/spill/README.zh.md @@ -11,3 +11,5 @@ | [`spill-policy/`](spill-policy/README.md) | 应用执行后 spill 策略 | 监听 `ctx.tools` | 参见[工具输出 spill 决策](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md),其中记录了存储、保留和工具自有输出处理之间的边界。 + +子系统参考——`SaveTextSpill`、owner/来源、品牌化定位符——见 [docs/subsystems/spill.md](../../docs/subsystems/spill.md);依据见[工具输出 spill Agent Note](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md)。 diff --git a/packages/storage/README.i18n.yaml b/packages/storage/README.i18n.yaml index 58fc19a4f8..ef32481991 100644 --- a/packages/storage/README.i18n.yaml +++ b/packages/storage/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/storage/README.md -README.md: a5f9d0204b93445699d28b1285a4a18f0408930e -README.zh.md: 0710dfb57f96011755b253ad95a0f9d2c0923a70 +README.md: a178495fca5cc433182b8e75f20d479cb7c5bc99 +README.zh.md: 1a08f1676e5c699a55f9956a42059d1f5acf7e8d diff --git a/packages/storage/README.md b/packages/storage/README.md index a5f9d0204b..a178495fca 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -12,3 +12,5 @@ This family persists application data other than session event logs through name | [`storage-domain/`](storage-domain/README.md) | Provides validated domain-record storage | `ctx.storageDomain` | Consumers use a data form rather than accessing a backend directly. The [domain storage decision](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md) records the family design. + +The subsystem reference — the backend seam, `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` — is [docs/subsystems/storage.md](../../docs/subsystems/storage.md). diff --git a/packages/storage/README.zh.md b/packages/storage/README.zh.md index 0710dfb57f..1a08f1676e 100644 --- a/packages/storage/README.zh.md +++ b/packages/storage/README.zh.md @@ -12,3 +12,5 @@ | [`storage-domain/`](storage-domain/README.md) | 提供经过验证的领域记录存储 | `ctx.storageDomain` | 消费方使用数据形式,而不是直接访问后端。[领域存储决策](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)记录了该家族的设计。 + +子系统参考——后端 seam、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed`——见 [docs/subsystems/storage.md](../../docs/subsystems/storage.md)。 diff --git a/packages/subagent/README.i18n.yaml b/packages/subagent/README.i18n.yaml index 11357c76d2..fe76e74a77 100644 --- a/packages/subagent/README.i18n.yaml +++ b/packages/subagent/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/subagent/README.md -README.md: 0a342569e66539e4987710b2e56f2946c97b1ac1 -README.zh.md: 5d2f7beef478b8bfd27b4772c7a951ea62cb10ef +README.md: c8969ad313fc673456d2740b40d90c6d8db794ac +README.zh.md: fb18afba5d8680408f0f37ab75056a3fb0ef660a diff --git a/packages/subagent/README.md b/packages/subagent/README.md index 0a342569e6..c8969ad313 100644 --- a/packages/subagent/README.md +++ b/packages/subagent/README.md @@ -19,3 +19,5 @@ This family lets an agent delegate work to child agents. Multiple named provider | [`tool-subagent-report/`](tool-subagent-report/README.md) | Provides the child-to-parent report channel | registers in child scopes | See the decisions for the [capability family](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), [continuable children](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md), and [control tools](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md). + +The subsystem reference — start requests, results, live runs, the provider seam, continuable background children — is [docs/subsystems/subagent.md](../../docs/subsystems/subagent.md); design rationale in the [subagent capability seam](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), [continuable background subagents](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md), and [merged subagent control service](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md) Agent Notes. diff --git a/packages/subagent/README.zh.md b/packages/subagent/README.zh.md index 5d2f7beef4..fb18afba5d 100644 --- a/packages/subagent/README.zh.md +++ b/packages/subagent/README.zh.md @@ -19,3 +19,5 @@ | [`tool-subagent-report/`](tool-subagent-report/README.md) | 提供从子级到父级的报告通道 | 注册到子级作用域 | 参见[能力家族](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)、[可继续执行的子级](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md)和[控制工具](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md)决策。 + +子系统参考——启动请求、结果、实时运行、提供方 seam、可续跑后台子 agent——见 [docs/subsystems/subagent.md](../../docs/subsystems/subagent.md);设计依据见 [subagent 能力 seam](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)、[可续跑后台 subagent](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md) 与[合并 subagent 控制服务](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md) Agent Note。 diff --git a/packages/subprocess/README.i18n.yaml b/packages/subprocess/README.i18n.yaml index 0ebb5bd4af..2088cbdb6b 100644 --- a/packages/subprocess/README.i18n.yaml +++ b/packages/subprocess/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/subprocess/README.md -README.md: f2b19436da40feb14d067e2cfc706222625680b5 -README.zh.md: 938312448dd5c0a691ed07ddc9843cf2c4445637 +README.md: 9a8628e2b6d16852895be33ae516c8b33b078502 +README.zh.md: d2c539fb1c66a914aa5f758d928fba01b5600d15 diff --git a/packages/subprocess/README.md b/packages/subprocess/README.md index f2b19436da..9a8628e2b6 100644 --- a/packages/subprocess/README.md +++ b/packages/subprocess/README.md @@ -10,3 +10,5 @@ The shared process substrate for one execution world: executable lookup, fully-s | [`subprocess-local`](subprocess-local/README.md) (`@deepseek-ai/dsh-subprocess-local`) | — | The local implementation: detached process trees, bounded collection/spill, `node-pty`, foreground/session inspection, tree signalling, and terminate-and-join disposal | The service owns process lifetime across consumer reloads; consumers own what a process means (a bash command, a future non-shell runner) and every default that shapes one. + +The subsystem reference — spawn specs, output readers, outcomes, the `DSH_*` environment — is [docs/subsystems/subprocess.md](../../docs/subsystems/subprocess.md); the seam decision in the [subprocess seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md). diff --git a/packages/subprocess/README.zh.md b/packages/subprocess/README.zh.md index 938312448d..d2c539fb1c 100644 --- a/packages/subprocess/README.zh.md +++ b/packages/subprocess/README.zh.md @@ -10,3 +10,5 @@ | [`subprocess-local`](subprocess-local/README.md)(`@deepseek-ai/dsh-subprocess-local`) | 无 | 本地实现:detached 进程树、有界收集/spill、`node-pty`、前台/会话检查、进程树信号发送,以及先终止再等待退出的资源释放 | 即使消费方重载,进程生命周期仍由服务负责管理;消费方负责定义进程的含义(一条 bash 命令、未来的非 shell 运行器),以及决定塑造该进程的每一项默认值。 + +子系统参考——spawn spec、输出读取器、结果、`DSH_*` 环境——见 [docs/subsystems/subprocess.md](../../docs/subsystems/subprocess.md);seam 决定见 [subprocess seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md)。 diff --git a/packages/support/README.i18n.yaml b/packages/support/README.i18n.yaml index 1b392a1729..824f20ae7e 100644 --- a/packages/support/README.i18n.yaml +++ b/packages/support/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/support/README.md -README.md: 15cb82d3d76a1241af8b40e1c8536292618409a9 -README.zh.md: ff8595b47f1a1220912dc77a072463adf74f7bf9 +README.md: bf0bba1ea7d21700a6743195ec966512c20e01bc +README.zh.md: 47eba4d51c66914f388ba4a5925323173d5ed7e2 diff --git a/packages/support/README.md b/packages/support/README.md index 15cb82d3d7..bf0bba1ea7 100644 --- a/packages/support/README.md +++ b/packages/support/README.md @@ -14,3 +14,5 @@ These packages support repository development, tests, and examples rather than p | [`llm-replay/`](llm-replay/README.md) | Replays recorded model responses for keyless tests and demos | A package moves out of `support/` when it gains a product contract and product consumers. + +The invariants contract is documented in [docs/subsystems/invariants.md](../../docs/subsystems/invariants.md). diff --git a/packages/support/README.zh.md b/packages/support/README.zh.md index ff8595b47f..47eba4d51c 100644 --- a/packages/support/README.zh.md +++ b/packages/support/README.zh.md @@ -14,3 +14,5 @@ | [`llm-replay/`](llm-replay/README.md) | 为无密钥测试和演示回放已记录的模型响应 | 当一个包获得产品契约和产品消费方时,它会移出 `support/`。 + +不变式契约记录在 [docs/subsystems/invariants.md](../../docs/subsystems/invariants.md)。 diff --git a/packages/tasks/README.i18n.yaml b/packages/tasks/README.i18n.yaml index 38cd47299c..d5df91be62 100644 --- a/packages/tasks/README.i18n.yaml +++ b/packages/tasks/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/tasks/README.md -README.md: 05ed9c439684337c45abe11f03c861a1b795b2ac -README.zh.md: 16fe6c0a2b183b719cb6aa8ac068e34d51832a2b +README.md: e0b6769cd1d28cc7159310a6ec6f5c8d94a81a24 +README.zh.md: d030081470a503fb52fd72dcc6f89f4c535dc38c diff --git a/packages/tasks/README.md b/packages/tasks/README.md index 05ed9c4396..e0b6769cd1 100644 --- a/packages/tasks/README.md +++ b/packages/tasks/README.md @@ -11,3 +11,5 @@ This family gives long-running tools one owner-isolated background-task protocol | [`tool-tasks/`](tool-tasks/README.md) | Exposes task control and completion notices to the model | registers on `ctx.tools` | See the [background-task runtime](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) and [task-registry](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md) decisions. + +The subsystem reference — the id scheme, the owner-fenced contract, snapshots — is [docs/subsystems/tasks.md](../../docs/subsystems/tasks.md); design in the [background-task runtime](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) and [task-registry seam](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md) Agent Notes. diff --git a/packages/tasks/README.zh.md b/packages/tasks/README.zh.md index 16fe6c0a2b..d030081470 100644 --- a/packages/tasks/README.zh.md +++ b/packages/tasks/README.zh.md @@ -11,3 +11,5 @@ | [`tool-tasks/`](tool-tasks/README.md) | 向模型公开任务控制和完成通知 | 注册到 `ctx.tools` | 参见[后台任务运行时](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)和[任务注册表](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md)决策。 + +子系统参考——id 方案、拥有者围栏契约、快照——见 [docs/subsystems/tasks.md](../../docs/subsystems/tasks.md);设计见[后台任务运行时](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)与[任务注册表 seam](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md) Agent Note。 diff --git a/packages/todo/README.i18n.yaml b/packages/todo/README.i18n.yaml index c75e09350a..57a1538b78 100644 --- a/packages/todo/README.i18n.yaml +++ b/packages/todo/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/todo/README.md -README.md: 66abf18131ee53d87e933757c40117841e94b07f -README.zh.md: 38a6b3c22653b8e22cc0b693eb666620513a3d8c +README.md: 6c7dccbbfb007737593550cea91444f8f735cf81 +README.zh.md: 3c1c35187b5111f1fdb607d70b980be57e3e79c1 diff --git a/packages/todo/README.md b/packages/todo/README.md index 66abf18131..6c7dccbbfb 100644 --- a/packages/todo/README.md +++ b/packages/todo/README.md @@ -9,3 +9,5 @@ The model-facing todo capability. It is a single **product** package because one | [`tool-todo/`](tool-todo/README.md) | Stores and exposes the session's todo list. | (registers on `ctx.tools`) | The child README owns the tool, persistence, and rendering contract. + +The event payload is documented on [docs/subsystems/session.md](../../docs/subsystems/session.md). diff --git a/packages/todo/README.zh.md b/packages/todo/README.zh.md index 38a6b3c226..3c1c35187b 100644 --- a/packages/todo/README.zh.md +++ b/packages/todo/README.zh.md @@ -9,3 +9,5 @@ | [`tool-todo/`](tool-todo/README.md) | 存储并公开会话的 todo 列表。 | (注册到 `ctx.tools`) | 子级 README 负责工具、持久化和渲染契约。 + +事件载荷记录在 [docs/subsystems/session.md](../../docs/subsystems/session.md)。 diff --git a/packages/web/README.i18n.yaml b/packages/web/README.i18n.yaml index cd412d1a99..87177273da 100644 --- a/packages/web/README.i18n.yaml +++ b/packages/web/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/web/README.md -README.md: 65b3dde1e2f9c35c308a10ca80245064f7a0361e -README.zh.md: 43b8b7d9c53336e984d9c61fa9b612fc1733e31f +README.md: 811c8b3439a8962116df0e8b3ab4f6e282ecb3d2 +README.zh.md: e328447d5d026c0abc815b269179ba6c4cd9acfc diff --git a/packages/web/README.md b/packages/web/README.md index 65b3dde1e2..811c8b3439 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -14,3 +14,5 @@ This family provides provider-neutral web search and fetch operations plus the m | [`tool-web/`](tool-web/README.md) | Exposes web search and fetch to the model | registers on `ctx.tools` | The [web capability decision](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md) records why search and fetch share one provider-selection service. + +The subsystem reference — search/fetch requests and results, availability, `WebError` — is [docs/subsystems/web.md](../../docs/subsystems/web.md); rationale (including deferred SSRF protection) in the [web capability seam Agent Note](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md). diff --git a/packages/web/README.zh.md b/packages/web/README.zh.md index 43b8b7d9c5..e328447d5d 100644 --- a/packages/web/README.zh.md +++ b/packages/web/README.zh.md @@ -14,3 +14,5 @@ | [`tool-web/`](tool-web/README.md) | 向模型公开 web 搜索和抓取 | 注册到 `ctx.tools` | [web 能力决策](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md)记录了搜索和抓取共用一项提供方选择服务的原因。 + +子系统参考——搜索/抓取请求与结果、可用性、`WebError`——见 [docs/subsystems/web.md](../../docs/subsystems/web.md);依据(含延后的 SSRF 防护)见 [web 能力 seam Agent Note](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md)。 diff --git a/packages/workflow/README.i18n.yaml b/packages/workflow/README.i18n.yaml index f02915a31d..05ad673c61 100644 --- a/packages/workflow/README.i18n.yaml +++ b/packages/workflow/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/workflow/README.md -README.md: 2416e26e73340a6d8624b8e77681dfc9f55fe7c5 -README.zh.md: a5c94a0a9799d78e5cca72394385b38d2fb2ba70 +README.md: 7b19cfd37f9b6870825d5e0761aed92908206b3a +README.zh.md: 33b3f26780da1bf4efcd78242b2cfb212cef4c22 diff --git a/packages/workflow/README.md b/packages/workflow/README.md index 2416e26e73..7b19cfd37f 100644 --- a/packages/workflow/README.md +++ b/packages/workflow/README.md @@ -12,3 +12,5 @@ This family runs model-authored orchestration workflows over subagents and expos | [`tool-ralph/`](tool-ralph/README.md) | Exposes the fixed fresh-agent Ralph workflow | registers on `ctx.tools` | Worker threads isolate workflow execution from the host event loop but are not a security boundary. See the [dynamic-workflow](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) and [Ralph tool](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md) decisions. + +The subsystem reference — start requests, `WorkflowMeta`, results, live runs, `workflow/*` events — is [docs/subsystems/workflow.md](../../docs/subsystems/workflow.md); decisions in the [dynamic-workflows](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) and [Ralph consumer](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Notes. diff --git a/packages/workflow/README.zh.md b/packages/workflow/README.zh.md index a5c94a0a97..33b3f26780 100644 --- a/packages/workflow/README.zh.md +++ b/packages/workflow/README.zh.md @@ -12,3 +12,5 @@ | [`tool-ralph/`](tool-ralph/README.md) | 公开使用全新 agent(智能体)的固定 Ralph 工作流 | 注册到 `ctx.tools` | worker thread 将工作流执行与宿主事件循环隔离,但不构成安全边界。参见[动态工作流](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md)和 [Ralph 工具](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md)决策。 + +子系统参考——启动请求、`WorkflowMeta`、结果、实时运行、`workflow/*` 事件——见 [docs/subsystems/workflow.md](../../docs/subsystems/workflow.md);决策见[动态工作流](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md)与 [Ralph 消费方](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Note。 diff --git a/packages/workspace/README.i18n.yaml b/packages/workspace/README.i18n.yaml index 93dea4b3f0..c7723f6c56 100644 --- a/packages/workspace/README.i18n.yaml +++ b/packages/workspace/README.i18n.yaml @@ -2,5 +2,5 @@ # 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 packages/workspace/README.md -README.md: 9f6ead776c1e5d36af5b6917ad8f27725b11e6a0 -README.zh.md: aa691ec681112f4c25a6391c33eabd2711c7abfc +README.md: 7e30f26a9854031b4907ad3e7edb35da5986d5d9 +README.zh.md: 6315ccb6b3a8b86161728e8296746efd0c334a29 diff --git a/packages/workspace/README.md b/packages/workspace/README.md index 9f6ead776c..7e30f26a98 100644 --- a/packages/workspace/README.md +++ b/packages/workspace/README.md @@ -9,3 +9,5 @@ This family owns persistent workspaces: user directories with titles and ordered | [`workspace/`](workspace/README.md) | Registers workspaces and accounts for their sessions | `ctx.workspace` | The [workspace package reference](workspace/README.md) owns lifecycle, persistence, and deletion semantics. + +The subsystem reference — the entity, realpath canon, registration/resolution — is [docs/subsystems/workspace.md](../../docs/subsystems/workspace.md); storage design in the [domain KV storage Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md). diff --git a/packages/workspace/README.zh.md b/packages/workspace/README.zh.md index aa691ec681..6315ccb6b3 100644 --- a/packages/workspace/README.zh.md +++ b/packages/workspace/README.zh.md @@ -9,3 +9,5 @@ | [`workspace/`](workspace/README.md) | 注册 workspace 并记录其会话归属 | `ctx.workspace` | [workspace 包参考](workspace/README.md)负责生命周期、持久化和删除语义。 + +子系统参考——实体、realpath 规范、注册/解析——见 [docs/subsystems/workspace.md](../../docs/subsystems/workspace.md);存储设计见 [domain KV 存储 Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)。 diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 120904d4b5..35fef7beaf 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -156,30 +156,30 @@ export const LINK_MAP: Readonly> = { AgentCancelCause: 'core.md', AgentOptions: 'core.md', AgentStatus: 'core.md', - ContentBlock: 'core.md', + ContentBlock: 'llm-streaming.md', ContinuationDecision: 'core.md', ContinuationStop: 'core.md', - GenerateOptions: 'core.md', + GenerateOptions: 'llm-streaming.md', InboxItem: 'core.md', InboxPlacement: 'core.md', - MessageId: 'core.md', + MessageId: 'llm-streaming.md', HookContext: 'core.md', SettleReason: 'core.md', - AdapterRegistrationHandle: 'core.md', - DirectoryRegistrationHandle: 'core.md', - LlmCallConfig: 'core.md', - LlmModelContext: 'core.md', - LlmModelReasoningInfo: 'core.md', - LlmResolvedModelInfo: 'core.md', + AdapterRegistrationHandle: 'llm-streaming.md', + DirectoryRegistrationHandle: 'llm-streaming.md', + LlmCallConfig: 'llm-streaming.md', + LlmModelContext: 'llm-streaming.md', + LlmModelReasoningInfo: 'llm-streaming.md', + LlmResolvedModelInfo: 'llm-streaming.md', LlmFailure: 'llm-streaming.md', - LlmModelInfo: 'core.md', - LlmProviderInfo: 'core.md', - LlmConfigurableProvider: 'core.md', - LlmModelDiscoveryRequest: 'core.md', - LlmDiscoveredModel: 'core.md', + LlmModelInfo: 'llm-streaming.md', + LlmProviderInfo: 'llm-streaming.md', + LlmConfigurableProvider: 'llm-streaming.md', + LlmModelDiscoveryRequest: 'llm-streaming.md', + LlmDiscoveredModel: 'llm-streaming.md', ResolvedRetryPolicy: 'llm-streaming.md', - Message: 'core.md', - MessageSource: 'core.md', + Message: 'llm-streaming.md', + MessageSource: 'llm-streaming.md', UserMessage: 'session.md', PreStepDecision: 'core.md', PreStepContext: 'core.md', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 317ddec122..b3bf8eaec4 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -7,22 +7,17 @@ "source": "packages/util/brand/src/index.ts" }, { - "doc": "docs/subsystems/core.md", - "symbol": "ContentBlockMap", - "source": "packages/llm/llm/src/types.ts" - }, - { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "AssistantProvenance", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "Message", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "MessageSourceMap", "source": "packages/llm/llm/src/message.ts" }, @@ -42,22 +37,22 @@ "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "FinishReasonMap", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "AdapterRegistrationHandle", "source": "packages/llm/llm/src/index.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmProviderInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmModelInfo", "source": "packages/llm/llm/src/types.ts" }, @@ -72,47 +67,47 @@ "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmModelContext", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ReasoningEffortId", "source": "packages/llm/llm/src/brand.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmReasoningEffortInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmModelReasoningInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmResolvedModelInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "GenerateOptions", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ToolSchema", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmCallConfig", "source": "packages/llm/llm/src/call-config.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmCallConfigAdapterDefaults", "source": "packages/llm/llm/src/call-config.ts" }, @@ -1491,7 +1486,7 @@ "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmConfigurableProvider", "source": "packages/llm/llm/src/types.ts" }, diff --git a/website/docs.ts b/website/docs.ts index 9a28aeea4d..1f7fa316a5 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -259,7 +259,7 @@ const cordisPrimerReference = pairedPages([ const subsystemsReference = pairedPages(([ ['README.md', '子系统', 'Subsystems', 0], - ['core.md', '核心数据结构', 'Core data structures', 1], + ['core.md', '核心', 'Core', 1], ['scope.md', '作用域', 'Scopes', 2], ['session.md', '会话', 'Sessions', 3], ['session-query.md', '会话查询', 'Session query', 4],