[ { "id": "m01", "number": "01", "tag": "tutorial-m01", "previousTag": "71ddc78", "scenario": "ordinary-loop", "sourcePath": "src/agent.ts", "lessonPath": "docs/lessons/m01.md", "shortTitle": "Agent Loop", "title": "模型怎样在动作与反馈之间循环", "question": "DSH 的 Agent Loop 是什么样的?", "sourceRange": { "start": 53, "end": 113 }, "codeGuide": { "title": "让模型动作进入可验证的执行边界", "description": "先建立一条最小基线:Step 固定当前输入,模型提出工具调用,运行时验证并执行,再把结果交给下一步。", "observations": [ { "title": "请求先固定当前输入", "text": "每个 Step 在开始时复制消息、工具规则和本步说明。模型收到的是一份确定的输入,后续状态变化不会改写已经发出的请求。", "lines": [53, 76] }, { "title": "工具结果决定是否进入下一步", "text": "模型回复先进入历史。存在工具调用时,运行时逐个执行并追加结果;没有工具调用时,当前 Turn 结束。", "lines": [73, 95] }, { "title": "所有失败走回反馈通道", "text": "未知工具、参数校验失败和执行异常都会转换为结构化结果。下一次请求可以读取原因,并据此修正后续动作。", "lines": [98, 113] } ], "fills": [ { "label": "接口声明与 Agent 结构", "kind": "skeleton", "ranges": [[1,40],[115,116]] }, { "label": "构造 Agent 的固定执行边界", "kind": "body", "ranges": [[41,52]] }, { "label": "runTurn:请求、工具往返与停止", "kind": "body", "ranges": [[53,97]] }, { "label": "工具执行:校验与异常包装", "kind": "body", "ranges": [[98,114]] } ] }, "changeStory": { "title": "先建立可验证的执行基线", "summary": "Nano DSH 用一个 Turn 包住连续的 Step:模型提出工具调用,运行时校验并执行,再把结果加入下一次请求。这个最小闭环能完成工具往返,同时保留了工具、历史和记录仍然固定在内存中的限制。", "harnessRole": "执行基线:把模型动作转换为经过校验的结果", "connection": "第一章先证明模型、工具和反馈能够闭环。第二章控制每次请求看到的历史,第三章让工具和规则成为可组装能力,第四章为所有事实建立共同记录。", "outcomes": [ "能沿 Agent.runTurn() 说明请求、工具调用、工具结果和下一步的执行顺序", "能解释模型提出动作后,运行时为何需要负责工具查找、参数校验和异常包装", "能区分 Step、Turn 与 maxSteps,并指出这条基线尚未解决的运行时问题" ] } }, { "id": "m02", "number": "02", "tag": "tutorial-m02", "previousTag": "tutorial-m01", "scenario": "projected-context", "sourcePath": "src/context.ts", "lessonPath": "docs/lessons/m02.md", "shortTitle": "上下文与缓存复用", "title": "让稳定前缀尽可能长", "question": "上下文是怎样组织的,为缓存复用做了什么优化?", "sourceRange": { "start": 38, "end": 137 }, "codeGuide": { "title": "从完整事实生成一次可控的模型视图", "description": "上下文模块保留原始消息,为当前请求裁剪过长的工具结果,再把稳定内容、累积历史和本步说明按变化频率排列。", "observations": [ { "title": "投影不改写原始结果", "text": "代码只裁剪模型视图中的长工具结果,保留头尾和省略长度。原始消息继续存在,第四章会把它们放进 Session Log。", "lines": [38, 67] }, { "title": "请求顺序表达变化频率", "text": "系统规则和工具放在前面,历史按顺序追加,本步说明最后加入。新增内容因此尽量靠近请求末尾。", "lines": [70, 99] }, { "title": "前缀比较只说明结构", "text": "两份请求进行确定性序列化后从开头比较。页面展示共享长度和首次变化位置,实际缓存命中仍以模型服务返回的数据为准。", "lines": [101, 137] } ], "fills": [ { "label": "投影规则、请求部件与比较结果", "kind": "skeleton", "ranges": [[1,37]] }, { "label": "生成模型视图:保留事实,裁剪长结果", "kind": "body", "ranges": [[38,69]] }, { "label": "按变化频率组织请求部件", "kind": "body", "ranges": [[70,100]] }, { "label": "比较稳定前缀并完成辅助计算", "kind": "body", "ranges": [[101,174]] } ] }, "changeStory": { "title": "完整事实和模型输入各有用途", "summary": "Nano DSH 保留原始消息,再由 projectMessages() 生成当前请求的模型视图。工具长结果先裁剪,系统规则和工具前置,历史追加,本步说明后置。完整 DSH 从 Session Log 派生模型视图,并在上下文压力下继续处理工具结果和较早历史。", "harnessRole": "输入投影层:决定当前 Step 交给模型的内容", "connection": "第一章需要一份请求,本章定义请求怎样从完整事实投影而来。第三章会让工具和规则变成可组装能力,第四章会让完整事实进入唯一的事件来源。", "outcomes": [ "能区分完整记录、模型视图和模型服务最终请求的职责", "能解释 clipToolResult() 只修改模型视图的原因", "能从 system、tools、history、dynamicContext 的顺序判断变化从哪里开始影响前缀" ] } }, { "id": "m03", "number": "03", "tag": "tutorial-m03", "previousTag": "tutorial-m02", "scenario": "plugin-kernel", "sourcePath": "src/runtime.ts", "lessonPath": "docs/lessons/m03.md", "shortTitle": "一切皆插件", "title": "用插件统一运行能力", "question": "如何实现“一切皆插件”?", "sourceRange": { "start": 45, "end": 123 }, "codeGuide": { "title": "让能力拥有来源、依赖和退出路径", "description": "Nano DSH 用 Context 安装插件。插件提供服务、工具、提示词和监听器时,Context 记录来源,并将相应清理函数加入同一条 effect 栈。", "observations": [ { "title": "安装前先建立归属", "text": "Context 在 setup 前创建安装记录,并把当前插件设为各项贡献的来源。setup 抛错时,已登记的清理函数会逆序执行。", "lines": [45, 60] }, { "title": "退出复用安装时的清理路径", "text": "成功挂载返回只生效一次的卸载函数。它先撤销贡献,再清除插件和依赖关系,重复调用不会重复执行清理。", "lines": [62, 83] }, { "title": "每次注册同时登记撤销动作", "text": "插件注册服务、工具、提示词或监听器时,Context 同时记录来源插件和对应的清理函数。", "lines": [86, 123] } ], "fills": [ { "label": "插件契约与 Context 持有的运行时状态", "kind": "skeleton", "ranges": [[1,44],[194,215]] }, { "label": "mount:安装、回滚与卸载", "kind": "body", "ranges": [[45,78]] }, { "label": "effect、provide 与 use", "kind": "body", "ranges": [[79,106]] }, { "label": "登记贡献、读取状态与统一清理", "kind": "body", "ranges": [[107,193]] } ] }, "changeStory": { "title": "运行时能力可以组装,也可以完整退出", "summary": "完整 DSH 用 Cordis 插件树组装模型适配器、工具、会话和 Agent Loop。Nano DSH 保留统一注册和可逆 effect:插件贡献服务、工具、提示词和监听器,安装失败或主动卸载时,Context 沿同一条清理路径撤销这些贡献。", "harnessRole": "运行时组装层:决定当前 Agent 拥有哪些能力", "connection": "第一章消费工具列表,第二章把工具和规则放进请求,本章说明这些能力怎样由插件提供。第四章再把插件变化与工具往返写入共同日志。", "outcomes": [ "能区分 Tool 这个模型动作与 Plugin 这个能力安装单元", "能沿 Context.mount()、effect() 与 disposer(资源释放函数)说明安装、失败回滚和卸载", "能根据 inspect() 返回的来源记录和服务关系说明当前能力从哪里来" ] } }, { "id": "m04", "number": "04", "tag": "tutorial-m04", "previousTag": "tutorial-m03", "scenario": "event-history", "sourcePath": "src/session.ts", "lessonPath": "docs/lessons/m04.md", "shortTitle": "让运行有迹可循", "title": "从日志重建执行过程", "question": "DSH 怎么记录和保存 Agent 执行过程?", "sourceRange": { "start": 84, "end": 194 }, "codeGuide": { "title": "一条事件日志怎样支撑请求和回放", "description": "Nano DSH 先按编号追加 SessionEvent,再用目标 Step 的请求头、上下文检查点和此前事件重建模型输入与执行过程。", "observations": [ { "title": "事实按顺序追加", "text": "append 复制事件、分配递增编号并放到末尾。上下文检查点也是新事件,它指向被概括的既有历史。", "lines": [84, 108] }, { "title": "请求头保存当时的边界", "text": "代码按 stepId 找到完整 request/header;该事件之前的内容构成历史,最近的上下文检查点决定从哪里继续投影。", "lines": [130, 153] }, { "title": "同一批事件派生模型消息", "text": "工具调用先按 Step 分组,再与 assistant message 配对;用户、模型和工具结果按原顺序还原,最后应用当时的投影设置。", "lines": [155, 194] } ], "fills": [ { "label": "会话事件的完整类型与日志轮廓", "kind": "skeleton", "ranges": [[1,92]] }, { "label": "追加事实、编号与上下文检查点", "kind": "body", "ranges": [[93,109]] }, { "label": "把 Session Log 接入运行时插件", "kind": "body", "ranges": [[111,129]] }, { "label": "从事件确定请求边界并重建消息", "kind": "body", "ranges": [[130,194]] }, { "label": "从同一事件流派生 Trace", "kind": "body", "ranges": [[196,267]] } ] }, "changeStory": { "title": "模型输入、回放和展示共享事实来源", "summary": "完整 DSH 将交互事实追加为 SessionEvent,并通过 SessionPersistence 写入 JSONL 或 SQLite。Nano DSH 用同一条 Session Log 记录消息、工具往返、请求头和运行时变化;buildRequest() 与 replayTrace() 从这些事件分别生成模型输入和执行过程。", "harnessRole": "记录层:为请求重建、回放和会话恢复提供共同来源", "connection": "第一章产生工具往返,第二章定义模型视图,第三章产生能力变化;本章把三类信息写入同一条 Session Log。第五章和第六章继续在这条记录上增加能力与 Goal 状态。", "outcomes": [ "能说明 Nano DSH 为什么需要为每个 Step 保存完整的 request/header", "能解释 context/checkpoint(上下文检查点)怎样缩短模型视图并保留原始 SessionEvent", "能区分 Session Log、模型请求和 Trace 三种由同一事实派生的数据形态" ] } }, { "id": "m05", "number": "05", "tag": "tutorial-m05", "previousTag": "tutorial-m04", "scenario": "dynamic-plugin-experiment", "sourcePath": "src/runtime-tools.ts", "sourceMode": "worktree", "lessonPath": "docs/lessons/m05.md", "shortTitle": "运行时自进化", "title": "编写并运行新的 Cordis 插件", "question": "DSH 是如何持续自进化的?", "sourceRange": { "start": 13, "end": 127 }, "codeGuide": { "title": "能力缺口怎样经过检查、挂载和释放被关闭", "description": "Runtime Tools 先让 Agent 检查当前 Context,再登记和运行新的 Cordis 插件;动态插件沿用普通 Context 挂载与卸载流程贡献工具和提示词。", "observations": [ { "title": "定义表区分代码和运行状态", "text": "Runtime Tools 为每个动态插件保存标识、用途、代码和卸载函数;自身退出时会停止仍在运行的插件并清空定义。", "lines": [13, 28] }, { "title": "定义阶段不改变当前能力", "text": "cordis_define 检查并保存 Agent 提交的 JavaScript 插件代码。cordis_run 取回定义,得到 Plugin,再通过 context.mount() 使其生效。", "lines": [29, 75] }, { "title": "释放回到第三章的生命周期", "text": "cordis_stop 执行卸载函数但保留定义,cordis_undefine 停止插件并删除定义;工具和提示词随 Context 生命周期撤销。", "lines": [76, 102] } ], "fills": [ { "label": "动态插件定义与 Runtime Tools 的入口", "kind": "skeleton", "ranges": [[1,16],[104,105]] }, { "label": "登记定义、清理动作与检查接口", "kind": "body", "ranges": [[17,34]] }, { "label": "定义并挂载新的 Cordis 插件", "kind": "body", "ranges": [[35,75]] }, { "label": "停止、删除与加载插件代码", "kind": "body", "ranges": [[76,145]] } ] }, "changeStory": { "title": "面对能力缺口,先检查、再挂载、最后验证", "summary": "Nano DSH 将 cordis_inspect、cordis_define、cordis_run、cordis_stop 和 cordis_undefine 作为普通 Tool。Agent 先确认缺少的能力,提交并挂载 Cordis 插件,调用新增工具验证结果,再停止或删除定义。工具和提示词从后续请求开始生效。", "harnessRole": "能力演化层:在任务过程中补齐并释放运行时能力", "connection": "这条路径同时使用前四章:Agent Loop 调用 Cordis 工具,请求投影读取当前 Tool 和 Prompt,插件生命周期管理挂载与释放,Session Log 记录定义、调用和变化。", "outcomes": [ "能按照检查、定义、挂载、验证、释放的顺序说明一次能力变更", "能说明插件挂载成功和新增工具完成任务之间的差别", "能指出新 Tool 和 Prompt 从哪次请求开始可见,以及释放后怎样验证贡献已退出" ] } }, { "id": "m06", "number": "06", "tag": "tutorial-m06", "previousTag": "tutorial-m05", "scenario": "long-task", "sourcePath": "src/long-task.ts", "lessonPath": "docs/lessons/m06.md", "shortTitle": "长程任务续行", "title": "用 Goal 与 Round 推进长程任务", "question": "DSH 是如何持续完成长程任务的?", "sourceRange": { "start": 33, "end": 114 }, "codeGuide": { "title": "Goal 怎样在 Turn 之外决定是否续行", "description": "LongTaskRunner 保存目标并依次启动 Round。每个 Round 复用普通 Agent Loop,外层根据结构化结果决定继续、完成、受阻或到达上限。", "observations": [ { "title": "Goal 集中保存跨轮状态", "text": "构造器固定 objective、status、轮数与原因;run 开始时先把 Goal 创建事件写入 Session Log。", "lines": [33, 60] }, { "title": "Round 为下一次 Turn 提供边界", "text": "LongTaskRunner 只在 Goal 为 active 时继续。它检查轮数和阶段定义,记录 Round 开始,再调用 runRound 启动普通 Agent Turn。", "lines": [62, 82] }, { "title": "续行依赖可观察的进展", "text": "completed、blocked、无进展和 max-rounds 都是显式出口;只有取得进展且仍未完成时,状态才保持 active 并进入下一轮。", "lines": [83, 114] } ], "fills": [ { "label": "类型声明与 LongTaskRunner 结构", "kind": "skeleton", "ranges": [[1,33],[115,116]] }, { "label": "初始化 Goal 并写入创建事实", "kind": "body", "ranges": [[34,61]] }, { "label": "启动下一轮并交给普通 Agent Loop", "kind": "body", "ranges": [[62,82]] }, { "label": "依据结果结束或继续 Goal", "kind": "body", "ranges": [[83,114]] } ] }, "changeStory": { "title": "Goal 而非单个 Turn 决定任务是否继续", "summary": "Nano DSH 在 Agent Loop 外保存 Goal,并以 Round 为单位多次启动普通 Turn。每轮结束后,LongTaskRunner 依据 progressed、completed 与 blockedReason 决定继续、完成、受阻或到达上限。完整 DeepSeek Harness 由持久 Goal 与 Goal Round Driver 在同一个 Session 中驱动续行。", "harnessRole": "长程协调层:让多个有限 Turn 共同推进一个 Goal", "connection": "每个 Round 都复用前五章的 Harness:第一章执行 Step,第二章构造模型输入,第三章管理能力生命周期,第四章延续共同记录,第五章允许任务中补齐能力。第六章再用 Goal 和 Round 协调多个普通 Turn。", "outcomes": [ "能按照 Goal、Round、Turn、Step 的顺序解释四层控制结构", "能说明协调器怎样根据可观察结果决定是否续行", "能指出 completed、blocked、no progress 与 max-rounds 的明确出口" ] } } ]