2. 开始学习前需要掌握的概念
2.1 概念分层
必须先掌握
- Cordis 的
Context、Service、inject、ctx.effect()、typed events。 Service Definition / Service Provider / Consumer能力三件套。SessionEventappend-only log 与deriveMessages()。Agent、turn、step、inbox 和followup/steer/inject。- profile / bundle /
cordis.patch.yml的层叠组合。
可以边读边学
surface与SurfaceOp的 append/replace。SystemPromptsection/context/variable/tool provider。ToolRuntime的 pre-execute/execute/post-execute/result 管线。LlmAdapter、prepareCall()、StreamChunk。- session persistence / projection / telemetry。
进阶阶段再学习
dsh-scope的 scoped registration 与 agent-scope 隔离。- Typert 类型图生成与类型安全 RPC。
- Web 客户端 slot 系统、React-free object layer、
useSyncExternalStore。 - 子智能体(subagent)、工作流(workflow)、程序化工具调用(PTC)、上下文压缩(compaction)、钩子(hooks) 的具体实现。
2.2 概念依赖关系
2.3 Cordis Context、Service、inject、effect
- 定义:
Context是运行时服务仓库;Service是声明了稳定ctx.<key>的插件;inject声明依赖;ctx.effect()创建可撤销注册。 - 为什么需要:如果插件直接
import具体实现,就无法替换模型、沙箱、驱动器或 UI 组件。 - 项目体现:
AgentLoop的static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt'],见packages/core/agent-loop/src/index.ts:297。 - 源码位置:
docs/cordis-primer.md、packages/core/agent-loop/src/index.ts:296-350。 - 例子:一个工具插件只依赖
ctx.tools,不依赖dsh-agent-loop。 - 前端类比:
Context类似前端 DI 容器或 React Context;inject类似构造函数依赖注入。 - 类比局限:Cordis 的依赖解析和 fiber 生命周期更强,服务会随插件卸载和热更新一起撤销,不只是“共享数据”。
- 常见误区:把
ctx.effect()当成普通副作用函数。它必须能撤销,热更新和卸载才能干净。
2.4 Typed events 和四种派发模式
- 定义:通过 TypeScript declaration merging 声明事件名,运行时分
emit、waterfall、parallel、serial派发。 - 为什么需要:循环和工具管线需要让策略插件在不改核心代码的情况下介入。
- 项目体现:
agent/pre-step、agent/request、llm/stream、tools/*都是 waterfall,见packages/core/agent/src/runtime-types.ts:231-278和packages/core/tools/src/index.ts:152-197。 - 源码位置:
docs/cordis-primer.md#dispatch-modes、packages/core/agent/src/runtime-types.ts:146-291。 - 例子:
llm/stream的 listener 调用next()得到 adapter 原始流,也可以直接返回自己的 async iterable 短路。 - 前端类比:waterfall 很接近 Koa / Redux middleware / server-side onion model。
- 类比局限:Cordis waterfall 是服务级事件合同,返回值和派发模式都写进类型;不是任意全局事件总线。
- 常见误区:waterfall listener 忘记调用
next(),会短路后续监听器。仓库明确要求必须调用next()才能委托。
2.5 Capability Seam:定义、提供、消费
- 定义:一个可替换能力由 Service Definition、Service Provider、Consumer 三部分组成。
- 为什么需要:如果只有接口或只有实现,替换能力时仍会耦合到具体包。
- 项目体现:
dsh-llm同时定义LlmRuntime和 adapter 接口;dsh-llm-deepseek是 provider;dsh-agent-loop是 consumer。 - 源码位置:
docs/architecture.md#capability-seams、packages/llm/llm/src/index.ts:180-284。 - 例子:
ctx.llm.registerAdapter(['deepseek-official'], adapter)注册 provider;循环只调用ctx.llm.prepareCall(),不 import DeepSeek。 - 前端类比:类似“接口 + 实现 + 调用方”,也像浏览器里的
fetch被不同网络层实现。 - 类比局限:seam 还包含事件、生命周期和 disposer;不是单纯接口继承。
- 常见误区:只写一个 Service Definition 就认为能力完成。仓库规定完整 seam 至少要三个角色,拆分只在角色独立演进时进行。
2.6 Session Log 与 Event Sourcing
- 定义:会话是只追加、连续 seq 的
SessionEvent数组;消息历史由 log 投影得到。 - 为什么需要:持久化、回放、分叉、标题、遥测和 UI 都需要同一个真相来源。
- 项目体现:
Session.deriveMessages()只遍历surface.nodes,见packages/core/session/src/index.ts:726-747。 - 源码位置:
packages/core/session/src/index.ts:417-758、packages/core/session/src/surface.ts:83-114。 - 例子:
user/message、assistant/message、tool/result是 surface event;turn/start和assistant/chunk不直接进入历史。 - 前端类比:像 Redux 的 action log 或 CRDT/event log;当前 UI 是 log 的投影。
- 类比局限:这里不是简单状态 reducer,而是同时记录原始流、边界、来源和 surface 操作,模型上下文需要精确重建。
- 常见误区:以为日志里每个事件都会发给模型。实际上只有带
surfaceOp的 message-producing 事件进入deriveMessages()。
2.7 Model-visible 等价于 Logged
- 定义:任何送到模型请求的内容,都必须能从 Session log 重建。
- 为什么需要:防止 UI 和回放看到的内容与模型实际看到的内容不一致。
- 项目体现:
docs/architecture.md写明Model-visible means logged;packages/core/agent-loop/src/invariant.ts:39检查session.deriveMessages()。 - 源码位置:
docs/architecture.md#session-log、packages/core/agent-loop/src/invariant.ts:21-64。 - 例子:文件变化通知通过
agent.inject()成为user/message后,才可能进入模型上下文。 - 前端类比:类似“服务端渲染的数据必须能由 store/URL 恢复”,不能依赖组件局部状态拼请求。
- 类比局限:这里的约束是运行时 invariant 和持久化格式,不只是代码风格。
- 常见误区:把只用于 UI 的临时变量直接塞进
agent/request。这样做会破坏回放和审计。
2.8 Agent、Turn、Step、Inbox
- 定义:
Agent是公开生命周期接口;turn是 0 或多个 step 的执行区间;step是一次模型请求加它触发的工具执行。 - 为什么需要:模型会连续调用工具,必须有一个明确的执行边界和输入队列。
- 项目体现:
ReactLoopAgent的turn()和step()在packages/core/agent-loop/src/agent.ts:246-401。 - 源码位置:
packages/core/agent/src/runtime-types.ts:64-144、packages/core/agent-loop/src/agent.ts:210-401。 - 例子:
followup()追加到next-turn并唤醒;steer()追加到next-step并唤醒;inject()追加到next-step但不唤醒。 - 前端类比:
turn像用户一次提交对应的“任务批次”,step像任务中的一次模型调用。 - 类比局限:模型工具调用会连续产生 step,用户不会直接感知每个 step。
- 常见误区:认为一次用户输入一定等于一次模型请求。工具调用会继续当前 turn,可能产生多个 step。
2.9 SystemPrompt 与 Tool Schema 装配
- 定义:
SystemPrompt按顺序收集 section、context、variable 和 tool schema,最后渲染成 prompt。 - 为什么需要:不同插件可以贡献 persona、工具指引、AGENTS.md、时间上下文,而不需要知道彼此。
- 项目体现:
SystemPrompt.assemble()收集全局和 scoped layer,见packages/core/system-prompt/src/index.ts:467-530。 - 源码位置:
packages/core/system-prompt/src/index.ts:52-217。 - 例子:工具插件调用
ctx.systemPrompt.tools(...)贡献 schema;tool guidance 自动进入请求。 - 前端类比:类似多个 provider 拼页面内容,或 CSS layers 的合并。
- 类比局限:这里有严格 name/order/scope/complete 规则,变量错误会让装配失败。
- 常见误区:以为系统提示词是一个写死的字符串。它是由多个插件动态组装并参与请求日志的。
2.10 Tool Registry 与执行管线
- 定义:
ctx.tools保存 ToolDefinition,执行时经过策略事件,最终产生 lossless JSON 结果。 - 为什么需要:工具调用不能直接信任模型参数,必须有 schema、权限、超时、取消和审计。
- 项目体现:
ToolRuntime.execute()入口在packages/core/tools/src/index.ts:1342,内部调度器在 796-801。 - 源码位置:
packages/core/tools/src/index.ts:222-288、packages/core/tools/src/index.ts:787-1362。 - 例子:模型请求
bash工具后,先tools/pre-execute决定 allow/deny/ask,再tools/execute执行,最后tools/result观察冻结结果。 - 前端类比:像表单校验 + 请求拦截器 + 响应拦截器。
- 类比局限:工具输出要回到模型,因此结果是 lossless JSON、不可变且必须可回放。
- 常见误区:在
tools/result中尝试修改结果。它是 observe-only,修改应在post-execute或finalizeContent。
2.11 LLM Adapter 与 Prepared Call
- 定义:
LlmAdapter只负责把GenerateOptions转成StreamChunk;LlmRuntime负责路由、默认值、准备调用和llm/streamwaterfall。 - 为什么需要:模型厂商协议和重试策略应封装在 provider,loop 不应知道 HTTP 细节。
- 项目体现:
LlmRuntime.prepareCall()在packages/llm/llm/src/index.ts:779-814返回一次性PreparedLlmCall。 - 源码位置:
packages/llm/llm/src/index.ts:180-233、packages/llm/llm/src/index.ts:730-928。 - 例子:
agent-loop调用ctx.llm.prepareCall(config),拿到stream()后逐 chunk 写assistant/chunk。 - 前端类比:类似
fetch的适配层 + streaming response parser。 - 类比局限:prepared call 会固定 adapter registration,防止 HMR 混用不同 adapter 的能力结果。
- 常见误区:直接手写 provider HTTP 请求,而不是实现
LlmAdapter,会失去重试、取消、流式协议和路由能力。
2.12 Profile、Bundle、Patch Layer
- 定义:profile 是命名的插件树组合;bundle 是发布为 npm 包的一组 patch;patch 按 id 覆盖行配置。
- 为什么需要:同一个 base bundle 可以生成
web和headless,用户还能在不改 bundle 的情况下覆盖配置。 - 项目体现:
PROFILE_TEMPLATES定义web和headless的 bundle 顺序,见packages/boot/app-boot/src/profile.ts:114-117。 - 源码位置:
packages/boot/app-boot/src/profile.ts:35-125、apps/cli/src/profile-boot.ts:142-171。 - 例子:
dsh-base作为第一层;dsh-web-app或dsh-headless第二层;用户 profilecordis.patch.yml最后覆盖。 - 前端类比:类似 CSS cascade、环境配置覆盖、或 Docker layer。
- 类比局限:patch 替换整行
config,不是深度 merge。 - 常见误区:以为 patch 会 merge 字段。文档明确说明它替换目标行完整 config,覆盖时必须重述保留字段。
2.13 Web Client 的 React-free Object Layer、Slots、uSES
- 定义:浏览器里业务数据在 React 外对象层,React 只订阅不可变快照;UI 通过 slot 注册组合。
- 为什么需要:流式 token 高频更新时,避免整棵 React 树抖动;也避免业务状态绑死 React。
- 项目体现:
Session与Notifier在packages/api/session-controller/src/client/sessions/;ConversationNodeAssembler在packages/client/ui-conversation/src/client/conversation/assembler.ts;useSyncExternalStore在packages/client/ui-renderer/src/client/scoped-slots.tsx。 - 源码位置:
packages/api/session-controller/src/client/sessions/session.ts:81、packages/client/ui-conversation/src/client/conversation/assembler.ts:158、packages/client/ui-renderer/src/client/scoped-slots.tsx:287。 - 例子:
Session.getSnapshot()返回缓存快照;useSyncExternalStore(subscribe, getSnapshot)驱动组件。 - 前端类比:像 zustand/observable store,但业务状态机和流式累积完全不依赖 React。
- 类比局限:这里不只是一个全局 store,而是每个 Session 有事件窗口、重连状态和不可变投影。
- 常见误区:把所有共享 UI 状态塞进 React Context。项目明确区分业务数据、共享 store、owner props、component local state。