news 2026/9/15 12:03:27

12. 完整重演:一句话请求的完整旅程 + 动手练习

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
12. 完整重演:一句话请求的完整旅程 + 动手练习

你在哪:终点。前面十一篇把八个角色逐个拆开了,这一篇把它们缝回一条连续的时间线——同一个示例,这次带着全部深度。

读完你会知道:这一分钟里每一毫秒发生了什么、每一个部件在第几步上场、以及每一个都可以怎么被换掉。文末有七个动手练习和一页速查卡。


一、先把场景重述一遍

cd~/my-project npx @deepseek-ai/dsh web

浏览器打开http://127.0.0.1:3080→ Settings → Models 填 DeepSeek API key → Choose workspace 选中~/my-project→ 新建会话(preset 默认standard标准模式)→ 发出:

“读一下 package.json,给 README 补一个 Quick Start 小节,然后跑一次pnpm lint确认没问题。”

二、带注解的协作图

这是第 3 篇那张总览图,把这次示例的具体数据标在每一跳上:

turn 1 · 三个 step

会话期 · 一次

启动期 · 一次

约 100 行 entry

每 step 一次

3 个段落
+ 12 个工具 schema

deriveMessages()

read_file → 编辑 → bash

第 3 次调用触发 ask

allowed-once

每个模型可见事实

组装层
web profile
= dsh-base + dsh-web-app

Cordis 内核
并发挂载 + 两道审计

standard preset
bash / fs / subagent
/ skill / plan ...

Agent Loop

系统提示装配

LLM 接缝
deepseek 路由

工具注册表

fs · shell · sandbox
· approval

会话日志
seq 1 → 100

三、时间线:从按下回车到看见总结

T+0ms · 启动早已完成(第 1 步)

进程起来的时候发生过这些(第 4、5 篇):

resolveDshHome() → ~/.dsh profiles/web/package.json → dsh.profile.bundles = [dsh-base, dsh-web-app] composeEntries() → 空列表 ← dsh-base/cordis.patch.yml (llm, session, agent, sandbox, approval, tool-bash, tool-fs, ...) ← dsh-web-app/cordis.patch.yml (webserver, api-gateway, client-runtime, ...) ← profiles/web/cordis.patch.yml (空) ← ~/.dsh/cordis.patch.yml (空) mountRootInclude() → Loader 并发挂载 assertEntriesLoaded() ✓ assertEntriesActivated() ✓ → http://127.0.0.1:3080

想看你自己机器上这棵树:

dsh--profileweb --dump-config

T+0ms · 会话已建好(第 2 步)

agent-presets.mount(agentCtx, 'standard')在 agent 工厂的setup()窗口里跑过了:标准模式子树本进程挂载一次,你的 agent 作用域 parent 到它。于是bashstr_replace_editorread_filegrepsubagent…… 在你这个会话上可见(第 5 篇)。

T+5ms · 你按下回车(第 3 步)

agent.followup({ role: 'user', content: [...], source: 'human' }) → agent/inbox/spliced (UI 收到) → agent/inbox/inserted (UI 收到) → 唤醒驱动 → agent/status: running → 日志 seq 1: turn/start { turn: 1 }

T+8ms · 认领与拦截(第 4 步)

claim('next-turn') → 你这一条消息 + next-step 里的一切(空) → agent/inbox/claimed (UI 收到,每条一次) agent/pre-step (waterfall) → dsh-compaction-basic 监听器:看上下文压力 → 无压力 → next() → 返回 enter([你的消息]) → 日志 seq 2: step/start { turn: 1, step: 1 } → 日志 seq 3: user/message { source: 'human', content: [...] }

T+12ms · 装配前缀(第 5 步)

systemPrompt.assemble({ scope: <你的 agent> }) 收集: section order -100 harness 身份 section order 0 persona({{model}} {{cwd}} 待插值) section order 100+ 工具指引 context 工作区 / 权限策略 / 时间 tools read_file, str_replace_editor, bash, grep, glob, todo_write, subagent, ask_user, ... → system-prompt/assemble waterfall(作用域过滤) → renderPrompt():{{model}} → deepseek-chat {{cwd}} → /Users/you/my-project

T+15ms · 发出请求(第 6 步)

prepareCall() → 捕获 deepseek 路由的注册 + 重试策略(默认 5 次) → 解析模型:上下文容量、defaultMaxTokens 日志 seq 4: request/header { header: <完整前缀>, reason: ... } agent/request (waterfall) → 无人拦截 llm/stream (waterfall) → 无人拦截 → 终端续延此时才查找适配器 DeepSeek 适配器 → HTTP POST(带 attributionHeaders()) 空闲看门狗上膛(5 分钟)

T+400ms–2s · 流回来了(第 7 步)

日志 seq 5..40: assistant/chunk ← 每一个原始分片,token 级保真 block-start(text) text-delta "我先看一下 package.json" block-end block-start(tool_use) argumentsDelta '{"pa' 'th":"pack' 'age.json"}' block-end 日志 seq 41: assistant/message { message: <text + tool_use>, usage: { input: 3187, output: 42 }, sourceEventSeqs: [5..40] }

arguments至此仍是原样字符串(第 9 篇契约 2)。

T+2s · 第一次工具调用(第 8 步)

日志 seq 42: tool/call { callId: 'c1', name: 'read_file', arguments: '{"path":"package.json"}' } ctx.tools.execute({ callId: 'c1', name: 'read_file', arguments: <解析并深冻结>, agent, signal }) ① tools/pre-execute → 读工作区内文件 → next() → allow ② 单调守卫 → 通过 ③ tools/execute → 超时策略包一层 ④ execute() → ctx.fs.read('package.json') ⑤ output.render() → ContentBlock[] ⑥ tools/post-execute → finalizeContent → tools/result(冻结) 日志 seq 43: tool/result { callId: 'c1', message: <文件内容> } 日志 seq 44: step/end { turn: 1, step: 1 }

T+2.1s · 同一个 turn 的第二步(第 9 步)

判定:“tools owe another request”—— 模型必须看到工具结果才能继续。turn 不关

日志 seq 45: step/start { turn: 1, step: 2 } (装配又跑一次:section 一字节没变 → KV Cache 命中) (历史长了;动态上下文没变 → 不追加新快照) ... 日志 seq 77: tool/call { callId: 'c2', name: 'str_replace_editor', ... } 日志 seq 78: tool/result { callId: 'c2', message: <编辑成功>, meta: { diff: '@@ -1,3 +1,12 @@ ...' } } ↑ UI 那张 diff 卡片的数据来源 日志 seq 79: step/end { turn: 1, step: 2 }

meta必须可 JSON 序列化,否则Session.append在源头就拒绝(第 6 篇)。因为它在日志里,所以关掉浏览器再打开,卡片一模一样——presentResult()是纯函数。

T+2.2s · 第三步:审批与沙箱(第 10–11 步)

日志 seq 96: tool/call { callId: 'c3', name: 'bash', arguments: '{"command":"pnpm lint"}' } ① tools/pre-execute 权限监听器:会产生副作用的命令 + 会话策略 ask → 返回 ask,短路,不调 next() 注册表 → ctx.approval.request({ callId: 'c3', ... }) ↑ 故意不带参数,靠 callId 挂到已呈现的调用上 日志(审计): approval/asked { requestId } Web UI 答复者 → 弹窗 ─────────── 你点了「允许」 ─────────── 日志(审计): approval/decided { requestId, outcome: 'allowed-once' } → 只有 allowed-once 才继续 ② 守卫 → ③ tools/execute(超时包装) ④ execute() → ctx.shell.run('pnpm lint') ctx.sandboxPolicy.resolve({ session }) → { mode: 'workspace-write', workspaceRoot: '/Users/you/my-project', ← 先解 symlink 再规范化 sessionId: <本会话> } ctx.sandbox.wrap(argv, policy) → macOS Seatbelt 包装;enforcement: 'full' ctx.subprocess.spawn(受限 argv) → 退出码 0,stdout "✔ no problems" ⑤ output.render → ⑥ finalizeContent(bash 施加自己的长度上限) 日志 seq 97: tool/result { callId: 'c3', message: <lint 输出> } 日志 seq 99: step/end { turn: 1, step: 3 }

T+4s · 收尾(第 12 步)

模型给出总结文本,没有新的工具调用 next-step 队列为空 → 自然停止条件满足 agent/turn-stopping (serial, 无 next()) → 目标插件:没有活跃目标 → 不接 日志 seq 100: turn/end { turn: 1, reason: <正常完成> } agent/status: idle

四、这一分钟里,每个角色干了什么

角色干的活换掉它意味着
Cordis 内核挂载约 100 行 entry,解析依赖,两道启动审计换不了也不需要换——它对业务一无所知
组装层决定这 100 行是哪 100 行;决定这个会话有哪 12 个工具改一份 YAML
会话日志写下 100 条事件;三次派生模型历史换持久化后端(jsonl → 数据库)
Agent Loop1 个 turn、3 个 step、1 次终止检查点写一个新 driver 顶掉ctx.agentLoop
系统提示装配3 次装配;插值{{model}}{{cwd}}加/删/改提示段落,或注册 waterfall 监听器
LLM 接缝3 次请求;~120 个分片;1 次 usage 记账换适配器(换厂商 / 换自建端点 / 录制回放)
工具注册表3 次调用过五段管线;1 次 ask 决策加工具、加策略监听器、加超时包装
能力接缝1 次文件读、1 次文件写、1 次审批、1 次沙箱 spawn换两个 provider,整个执行世界搬到远程

五、回到最初的问题

第 1 篇说,那两百行 demo 和一个能用的 Agent 之间隔着三堵墙:

第一堵墙(边界情况把两百行变成六千行)——这一分钟里,你看到了它们各自被安置在哪:截断在finalizeContent,超时在tools/execute包装器,并发在执行模式分类,取消在AgentCancelCause,上下文超限在agent/pre-step+CONTEXT_WINDOW_EXCEEDED,中途打断在assistant/messageinterrupted: true它们不再纠缠在一个大循环里,各自有名字、有归属、有契约。

第二堵墙(能力被焊死)——上面那张表最后一列就是答案。包括循环本身core/agent声明接口,core/agent-loop只是"默认实现"。

第三堵墙(黑盒无法审计)——那 100 条日志事件就是答案。不是"日志里有这次请求",而是"模型看见的每一个字节都是一条可重放的事件",并且有运行时不变量在守着这条规矩。seq 4那条request/header让你能重建"第一次请求时模型看到的完整前缀";minimalpreset 让你能把变量按死,只比较模型本身。


六、七个动手练习

每个练习验证一篇文章的一个具体论断。建议按顺序做。

练习 1 · 看见那棵树(验证第 5 篇)

npx @deepseek-ai/dsh--profileweb --dump-config>tree.ymlwc-ltree.ymlgrep-n'# =='tree.yml|head# 每段的来源标注grep-n'id: sandbox\|id: approval\|id: llm'tree.yml

要观察的# ==注释标出了每一段行来自哪个文件、叠了哪些补丁层;整份输出仍然是一个可加载的 YAML 文档。

练习 2 · 改一行配置,不改一行代码(验证第 4、5 篇)

~/.dsh/cordis.patch.yml里按 id 打一个补丁,改掉某个工具给模型看的description,然后重启并再 dump 一次确认生效。

要观察的:补丁是整体替换 config(没有深合并)——这一行原本的其他字段你必须重抄。抄漏一个试试,看会怎样。

练习 3 · 对比两个 preset 的系统提示(验证第 8 篇)

同一个问题,分别在标准模式极简模式下跑一次,然后对比两条会话的request/header事件。

要观察的:极简模式的人格段落标了complete: true,所以它独占整个系统提示includeRuntimeContext: false让动态上下文也不出现。工具 schema 从十几个变成两个。这就是评测所需的"把变量按死"。

练习 4 · 数一数 turn 和 step(验证第 6、7 篇)

发一个需要三四次工具调用的请求,然后翻会话日志,数turn/startstep/start各有几条。

要观察的:一个 turn 里有多个 step;turn/start出现在认领输入之前。再试一次中途点「停止」,看turn/end的 reason 变成什么,以及assistant/message上是否出现了interrupted: true

练习 5 · 让审批失败(验证第 10、11 篇)

把会话的审批策略切到never,然后让 agent 跑一条命令。

要观察的:它得到的是确定性的rejected,没有任何答复者被派发。这正是 CI / 无人值守场景要的姿态。再想一想:如果审批服务本身崩了会怎样?(答案:unavailable→ 拒绝,而不是放行。)

练习 6 · 写一个tools/pre-execute监听器(验证第 4、10 篇)

写一个最小插件,监听tools/pre-execute,对任何触碰某个目录的编辑一律返回ask。挂到你的 profile 补丁里。

要观察的:你没有改任何核心代码;你的监听器是"策略型"的——拥有决定时不调next()(第 4 篇)。顺手验证一下:不小心忘了next()的观察型监听器会发生什么。

练习 7 · 读一次源码路径(验证第 3 篇的整张地图)

打开仓库,按这个顺序读,每个文件只看导出的类型:

docs/architecture.md ← 先读这个(约 100 行,信息密度极高) packages/core/session/src/types.ts ← SessionEventMap packages/core/agent/src/types.ts ← Agent 接口 packages/core/tools/src/index.ts ← ToolDefinition + 五段管线 packages/llm/*/src/ ← LlmAdapter apps/cli/config/agent-presets/*/agent.cordis.yml ← 四个 preset 的差异

要观察的:这四个类型文件加起来就是第 3 篇那张概念地图。以及——每个包的 README 都有一节 “KV Cache effect”,说明该插件对提示缓存的影响。这个纪律本身就值得抄走。


七、速查卡

核心服务 key

key拥有
ctx.sessions只追加会话日志,唯一真相源
ctx.systemPrompt提示段落 / 动态上下文 / 变量 / 工具 schema 装配
ctx.tools工具注册表 + 五段执行管线
ctx.agentsAgent 接口与注册表(扩展只依赖它
ctx.agentLoop默认驱动(可替换
ctx.llm消息词汇表 + 适配器接缝
ctx.fs/ctx.shell/ctx.subprocess文件 / 命令 / 进程
ctx.sandbox/ctx.sandboxPolicy文件效果限制与策略解析
ctx.approval/ctx.permissionPresets审批与权限预设
ctx.subagents/ctx.jobs/ctx.goals子代理 / 后台作业 / 目标
ctx.agentPresetspreset 花名册与挂载

事件三域

什么时候用
会话事件(落日志,session/event广播)这个事实必须活过重启
Agent 事件agent/*,带活的 Agent)观察或拦截进行中的工作
能力事件fs/*tools/*telemetry/*给某个接缝挂策略或适配器,不用 import 循环

四种派发模式

模式await返回值用途
emit通知
waterfall拦截(around 中间件,next()委托)
parallel扇出
serial依次决策

一个 turn 的骨架

turn/start → claim → agent/pre-step → step/start → user/message → 装配 → deriveMessages → agent/request → llm/stream → assistant/chunk* → assistant/message → tool/call* → pre-execute → execute → post-execute → tool/result* → step/end → (还欠请求?→ 下一个 step) → agent/turn-stopping → turn/end

三条要背下来的规矩

  1. 模型可见即已记录(Model-visible means logged)——想给模型加东西?先落一条日志事件。
  2. 失败即关闭(fail-closed)——审批unavailable= 拒绝;ask但没有审批能力 = 拒绝;并发安全只有显式true才算
  3. 接缝是三个角色的总和——加能力就要设计服务定义、提供者、消费者三样,缺一不是接缝。

常用命令

npx @deepseek-ai/dsh web# Web UI,127.0.0.1:3080npx @deepseek-ai/dsh web --no-open# 不自动开浏览器npx @deepseek-ai/dsh--profileheadless"跑一下测试"# 一次性任务,打印答案后退出npx @deepseek-ai/dsh--profileweb --dump-config# 看这台机器实际会挂的树npx @deepseek-ai/dsh plugin--profilewebadd<pkg># 给 profile 装外部插件

八、继续往下走

  • 仓库文档docs/architecture.md(总图)→docs/cordis-primer.md(内核五个概念)→docs/glossary.md(术语,写得极好)→docs/subsystems/(每个子系统一页)。
  • 扩展指南docs/cookbook/下有加包、加工具、加 LLM 适配器、加会话节点、加设置卡片的分步指南。
  • 事故复盘docs/postmortem/—— 少见的、把真实事故连同根因写下来的开源目录,比大多数教程更有营养。
  • 姊妹系列:LLM 全景指南(模型侧)、多模态大模型。本系列讲的是模型外面那一层。

上一篇← 11 · 能力接缝
回到→ 系列索引


📚 返回专栏目录

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 12:03:24

11. 能力接缝:文件、命令、沙箱、审批、子代理

你在哪&#xff1a;运行示例的第 8、10、11 步——真正跟外部世界打交道的那一层。这是最后一个深度篇。 读完你会知道&#xff1a;接缝的三个角色为什么缺一不可、一次 provider 替换如何把 Bash/PTY/LSP 一起搬到远程、SandboxMode 三档策略与"部分执行"这个诚实的…

作者头像 李华
网站建设 2026/9/15 12:02:05

Flutter推送通知技术:本地与云端方案深度解析

1. Flutter推送通知的技术选型与场景分析在移动应用开发中&#xff0c;推送通知是提升用户留存和活跃度的关键功能。Flutter生态提供了两种主流方案&#xff1a;local_notifications用于本地通知&#xff0c;firebase_messaging则处理云端推送。这两种方案并非互斥&#xff0c;…

作者头像 李华
网站建设 2026/9/15 12:01:33

SSM+Vue构建抗疫物资管理系统的技术实践

1. 项目概述&#xff1a;抗疫物资管理系统的现实意义与技术选型2026届计算机相关专业毕业设计选择"抗疫物资管理系统"作为课题&#xff0c;具有极强的现实意义和应用价值。这个选题源于近年来公共卫生事件频发背景下&#xff0c;物资调配效率直接关系到应急响应能力。…

作者头像 李华
网站建设 2026/9/15 12:01:23

基于ECharts的物流大数据可视化平台源码解析

简介&#xff1a;基于ECharts的物流大数据可视化平台源码&#xff0c;定位于物流行业数据分析与智慧仓储监控场景&#xff0c;面向前端开发者、物流信息化学习者及运营管理人员&#xff0c;旨在通过直观图表解决海量物流数据难以理解和决策效率低的问题。这套源码融合ECharts、…

作者头像 李华