• muxiesan

    从 Pi 到 DSH:一个可以动态重组的 Agent 运行时

    这次我们从论文、源码一路看到 GUI 和自定义模型,顺手把结果整理了下来。

    DeepSeek Harness(下面简称 DSH)乍看也是一个 Coding Agent。实际跑过以后,我们发现它和 Pi、Codex 这类产品的重点不太一样。

    DSH 先提供一个插件运行时,再用插件拼出具体的 Agent。模型、工具、权限和 Agent Loop 都在这套插件系统里。创造模式(ID 为 cordis)甚至允许 Agent 检查和修改当前运行时。

    我们觉得最好用的类比还是:

    Pi 像 Vim,DSH 像 Emacs。

    这个比喻不严格,但能说明两边的设计取向。下面聊聊我们实际确认了什么。

    DSH 还在快速迭代。文中的结论以我们检查时的仓库和运行时为准。

    DSH 先搭运行时,再组装 Agent

    多数 Coding Agent 都围绕一个固定的 Agent Loop 展开:```
    读取用户输入
    → 调用模型
    → 执行工具
    → 把结果交还模型
    → 继续循环
    ```模型、工具、权限和界面都接在这个循环上。

    DSH 连 Agent Loop 也做成了 Cordis 插件。不过在当前 Web 组装里,并不是所有插件都放进 Preset。Agent Loop、模型路由、会话持久化、Sandbox 和 Approval 等进程级能力留在 Host;Preset 主要负责一个 Agent 面向模型的那一层。

    结构大致如下:```
    DSH Host(进程级共享)
    ├─ Agent Loop / Agent Factory
    ├─ 模型路由与 Provider
    ├─ Session / Persistence
    ├─ Sandbox / Approval
    ├─ 工具、Skills、Subagent 等注册表
    └─ Agent Preset(按 Agent scope 生效)
    ├─ Persona 与提示词段
    ├─ 文件、Shell、Web 等工具
    ├─ Skills 入口
    ├─ Plan、Goal、Compaction
    ├─ 子 Agent 与工作流工具
    └─ 可选的 Cordis 动态工具
    ```所以,DSH 里的 Agent 仍然是一组插件的组合,但 Preset 管的是其中按会话变化的部分。进程级基础设施仍由 Host 统一提供。

    Cordis 论文到底在讲什么

    Cordis 的论文叫 A Programming Paradigm for Spatiotemporal Composability。它要解决的问题很具体:组件在运行时加入、退出或替换以后,系统怎么保持干净、稳定?

    论文把动态组装分成两个方向:

    - 时间可组合性(temporal composability):一个组件离开以后,它曾经给系统造成的影响能否被完整撤销。

    - 空间可组合性(spatial composability):组件之间的依赖关系发生变化以后,系统能否自动重新连接、激活或停用受影响的组件。

    这里的“空间”指依赖图,不是机器或进程的位置。“时间”指组件从加载到卸载的生命周期。

    时间组装:插件卸载后要收拾干净

    很多插件系统都有 dispose() 或 unload()。问题是,少清一个事件监听器或定时器,插件虽然停了,副作用还留在进程里。

    Cordis 把一次副作用写成:```
    e : Γ → Γ × (Γ → Γ)
    ```Γ 是运行时能管理的上下文。副作用 e 改变上下文时,还要返回一个逆操作。

    如果加载时按 A → B → C 注册了工具、监听器和定时器,卸载时就按 C⁻¹ → B⁻¹ → A⁻¹ 清理。工程上就是一个 LIFO 清理栈。

    Cordis 的卸载会撤销插件通过上下文登记的影响,不动其他插件。这里说的恢复是“观测等价”:不用把内存逐字节还原,只要外部看到的服务和行为恢复了即可。

    空间组装:运行时一直维护依赖图

    effect 记录组件改了什么,coeffect 记录组件需要什么。论文把依赖环境表示成一张从键到服务值的表:```
    Σ = (k : K) ⇀ Vₖ
    ``组件声明依赖 d 和供给 p。只有 d` 全部满足时,组件才会激活。服务加入、移除或更换提供者时,运行时会重新检查相关组件。

    放到 DSH 里看就很直观了。Agent Loop 需要模型、工具和会话服务。模型适配器还没加载时,Loop 不启动;适配器加载后,Loop 自动激活;适配器被替换时,依赖它的组件重新绑定;适配器退出时,依赖者先卸载。

    普通依赖注入通常在启动时做一次解析。Cordis 会在整个运行期维护这张依赖图。

    时间和空间怎么接到一起

    论文把状态、逆操作和依赖表放进同一个递归上下文:```
    Γ∞ = μΓ. Γ × (Γ → Γ) × Σ
    ```一个上下文同时保存当前状态、清理函数和可用依赖。它还能派生子上下文,最后形成一棵组件树。

    论文把组件实例叫作 fiber。每个 fiber 都记录自己的依赖、状态和清理函数。加载组件时执行 effect,卸载时执行 recover;父组件卸载时,子组件也跟着清理。

    提供一个服务本身也是可逆 effect。插件卸载时,服务被撤回,依赖它的 fiber 随之停用。时间和空间就是这样接起来的。

    论文把一个组件概括为三元组:```
    Component = Dependencies × Provisions × RevertibleEffect
    ```也就是:我需要什么、我提供什么、运行时会改什么,以及怎么撤销。

    在没有运行失败、逆操作正确、依赖声明完整等前提下,最终状态只取决于最后的组件配置,不取决于中间的加载和替换顺序。可以把它理解成:折腾完以后,运行时应该和“从一开始就按最终配置启动”基本一致。

    这些公式怎么落到代码里

    论文里的概念和 Cordis API 基本是一一对应的:

    论文中的概念 |
    工程含义 |
    Cordis 中的落点 |

    Γ∞ |
    可递归组合的运行时上下文 |
    ctx 与上下文树 |

    effect 与 inverse |
    一次状态变化及其撤销方法 |
    ctx.effect(callback);回调返回或逐步产出 disposer |

    accumulator / recover |
    按逆序合成全部清理动作 |
    fiber.dispose 与卸载流程 |

    Σ、get、set |
    服务供给、查询与撤回 |
    ctx.get()、ctx.set();set 本身也受 effect 跟踪 |

    d、p、e |
    组件的依赖、供给和副作用 |
    fiber.inject、组件的 provide、fiber.apply |

    fiber |
    一个组件的运行时实例 |
    ctx.use() 创建的实例及其生命周期状态 |

    target / committed view |
    希望绑定的依赖与当前已提交依赖 |
    refresh 驱动激活、卸载和重新加载 |

    声明式组件树 |
    系统希望达到的最终组合 |
    Cordis loader 的配置协调与热更新;DSH 在更上层用预设描述 Agent 组合 |

    开发者要做两件事:给副作用配上逆操作,并把依赖声明清楚。清理顺序、依赖变化和生命周期切换交给 Cordis。

    当然,这套保证有边界:

    - Cordis 不会自动证明 inverse 写对了。

    - 没有通过 ctx.effect 等机制登记的修改,Cordis 管不到。

    - 已经发出的网络请求和外部消息不会被自动撤回。

    所以 DSH 的“自进化”不是随意改写整个进程。Agent 改的是组件图,Cordis 负责把变化局部地应用到运行时,并在需要时撤销。

    Preset 到底控制什么

    在当前实现里,一个 Agent Preset 就是一个目录,核心文件是 agent.cordis.yml。文件里是一组 Cordis 插件行。某个会话选择 Preset 后,DSH 会把这组插件挂到该 Agent 的 scope 上。

    更具体一点,每个 Preset 代际在进程内只挂载一次,使用它的会话通过 scope 加入这份常驻组合。插件内部仍按 Session 或 Agent 保存状态,所以多个会话不会混在一起。修改 Preset 文件时,DSH 会为后续会话建立新代际,已经加入旧代际的会话不会被中途换掉。

    它主要控制这些内容:

    - Persona、Agent Instructions 和其他系统提示词段

    - 模型能看到、也能调用的工具

    - 工具以普通 schema 还是 Code Mode SDK 的方式呈现

    - 本地 Skills 的发现和加载

    - Plan、Goal、Todo、Compaction 等工作方式

    - Subagent、Workflow、Ralph 等委派能力

    - 是否开放 Cordis 运行时检查和动态插件工具

    Preset 会直接改变请求里的系统提示词和工具 schema,所以它不只是界面上的一个模式名称。选择不同 Preset,模型拿到的能力、提示词长度、工具调用方式和长期会话行为都会变化。

    它也有明确的边界。当前 Web 部署中,下面这些能力留在 Host,不由 Agent Preset 决定:

    - 使用哪个模型和 Provider

    - Agent Loop 与各类注册表

    - Session 持久化、Credentials 和 Settings

    - Sandbox、Approval 和 Permission Preset

    - Subagent 的底层 spawn、fork 等进程级后端

    所以“Agent Preset”“模型选择”和“权限预设”是三件事。Agent Preset 决定 Agent 有什么能力,模型选择决定由谁来推理,权限预设决定工具执行时受到什么限制。

    四种内置 Preset

    ID |
    界面名称 |
    基础能力 |
    适合场景 |

    standard |
    标准模式 |
    完整的原生工具集 |
    日常编码 |

    code |
    PTC 模式 |
    标准模式 + Code Mode 工具呈现 |
    多步骤、可编程的工具编排 |

    minimal |
    极简模式 |
    持久 Bash + str_replace_editor |
    最小实验、模型基线测试 |

    cordis |
    创造模式 |
    标准模式 + Cordis 自修改工具与创作 Skills |
    创建 Preset、开发和调试插件 |

    标准模式:日常编码的默认组合

    标准模式不是一两个常用工具的集合,它基本装上了 DSH 当前完整的模型侧编码能力:

    - Linux 和 macOS 使用 Bash,Windows 使用 PowerShell

    - 文件读取、编辑和文件系统搜索

    - 后台任务的查询与停止

    - 本地 Skills 发现、目录查询和 Skill 加载

    - Goal、Plan Mode 和 Todo

    - 自动上下文压缩、手动 /compact,以及长工具结果裁剪

    - Subagent 查询、spawn、fork

    - Workflow 和 Ralph 循环

    - 向用户提问

    - 网页搜索

    标准模式还会读取工作区里的 Agent Instructions,当前上限是 64 KiB。它带有完整的 Compaction 组合,长对话不会像极简模式那样一直堆上下文。当前配置还会裁剪超过阈值的长工具结果,保留头尾供模型继续判断。

    组装文件中已经放了 Codex 和 Claude Code 子代理工具的模板,但默认是 disabled: true。也就是说,标准模式不会自动获得这两个产品;复制成自定义 Preset 后,可以按需打开对应行。

    这套组合最适合日常使用。我们创建自己的 Code Preset 时,也应该优先从 standard 复制,而不是从空文件开始。

    PTC 模式:能力不变,工具调用方式变了

    PTC 模式的 agent.cordis.yml 基本是标准模式的完整副本,额外增加了一行:```
    - id: tool-presentation
    name: '@deepseek-ai/dsh-agent-tool-presentation'
    config:
    mode: code
    ```这行不会增加新的文件或 Shell 能力,它改变的是模型看到工具的方式。

    在标准模式下,模型直接看到 bash、文件工具、搜索工具、Subagent 等原生 schema。在 PTC 模式下,模型在协议层唯一可以直接调用的工具是 run_code,同时在系统提示词里拿到一份根据当前工具目录生成的 TypeScript SDK。模型可以写一段异步 TypeScript:

    - 连续调用多个工具

    - 并发执行互不依赖的查询

    - 根据前一步结果做分支

    - 在程序里过滤和汇总结果,只把必要内容交回模型

    原来需要五轮“模型—工具—模型”往返的操作,有机会压成一次 run_code。内部工具仍由 DSH 的工具注册表分发,原来的 Sandbox、Approval 和调用记录不会因为套了一层 TypeScript 就消失。

    这段代码是一次 run_code 调用的函数体,可以使用顶层 await 和 return。程序结束后,只把日志和返回值交回模型。它是一次性的工具编排,不是常驻插件,也不会自动写进 Preset。

    代价也很明显。一次程序可以做更多事情,执行范围比单个原生工具调用大,审查代码也更重要。它还依赖 Host 提供 Code Runtime;没有 TypeScript Runtime 的部署无法挂载这个 Preset。

    PTC 模式适合搜索多个文件、批量读取结果、串联查询和修改等多步骤任务。简单改一个文件时,标准模式通常更直观。

    极简模式:真的只剩两个工具

    极简模式不是“标准模式关掉几个按钮”,而是一份单独的最小组装。它只向模型提供:

    - 一个状态可跨调用保留的持久 Bash

    - 一个 str_replace_editor

    它使用固定的完整 Persona:You are a helpful software engineer assistant.,并关闭运行时上下文快照。普通的 Agent Instructions、Web 引导和后续提示词贡献不会继续叠加。

    它没有标准模式中的文件搜索、Web、Skills、Plan、Compaction、Todo、Subagent 和 Workflow。持久 Bash 的状态会在多次调用间保留,当前超时是五分钟;工具说明也明确写着不能通过它访问互联网。编辑器直接使用本地文件系统,并要求绝对路径。

    这个模式适合观察最小 Agent Loop、比较模型基础能力,或者做范围很小的编码任务。长会话和复杂仓库工作不太适合它,尤其是它没有 Compaction。

    创造模式:标准模式加上运行时创作能力

    创造模式的 ID 是 cordis。它保留标准模式的主要能力,又增加了三部分内容。

    第一部分是专门的 Persona。它会告诉 Agent:

    - DSH 分成 Host plane 和 Agent Preset plane

    - 哪些服务应该全局共享,哪些插件应该放进单个 Preset

    - 用户 Preset 在哪里

    - 不要修改随 DSH 安装的四个内置 Preset

    第二部分是 Cordis 动态工具:

    - cordis_inspect_list、cordis_inspect_query、cordis_inspect_self

    - cordis_define、cordis_run、cordis_stop、cordis_undefine

    它们可以检查当前服务、事件、工具和动态 Package,也可以在当前 DSH 进程中定义、运行、停止和删除临时插件。

    第三部分是两份内置 Skill:

    - editing-cordis-compositions:教 Agent 创建、修改和验证 Preset

    - cordis-plugin-development:教 Agent 检查接口并开发动态 Cordis 插件

    创造模式适合“帮我做一个新的 Agent”或“先在运行时试验一个插件”。它不适合作为默认日常模式:额外的工具 schema 和说明会增加上下文,而且动态 JavaScript 的权限接近 Shell 访问。临时 Cordis Package 也只存在于当前进程,重启后不会自动变成正式插件。

    怎么创建自己的 Preset

    DSH 当前采用“复制后修改”,不在网页里提供一个从空白开始的 YAML 编辑器。这是有意的:一个可工作的 Preset 往往包含多个插件行、隔离 realm、Skills 和其他资源,从已知可用的组合复制更稳。

    方法一:在 DSH Web 中复制

    操作路径是:

    - 打开“设置”。

    - 进入“Agent 预设”。

    - 在最接近需求的 Preset 上点击“复制”。

    - 填写标识符和可选的显示名称。

    - 创建完成后,打开新 Preset 的目录修改文件。远程或容器部署无法直接打开宿主目录时,页面会显示路径供手动复制。

    标识符会直接成为目录名,只允许小写字母、数字和连字符,规则是:```
    [a-z0-9][a-z0-9-]*
    ``例如 backend-reviewer 可以,Backend Reviewer、my_agent 和 ../test` 都不行。标识符创建后不能在界面里改名,也不能与内置或已有 Preset 重名。复制操作不会覆盖已有目录。

    默认的用户目录是:```
    ${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/
    ``一个自定义 Preset 通常长这样:``
    backend-reviewer/
    ├─ preset.yml
    ├─ agent.cordis.yml
    ├─ skills/ # 可选
    └─ 其他插件或资源 # 可选
    ```两个 YAML 文件的职责不同:

    - preset.yml 只放界面元数据,主要是 name 和 description。

    - agent.cordis.yml 才是实际的插件组装,里面每一行都有 id、包名和可选配置。

    preset.yml 可以很简单:```
    name: 后端审查模式
    description: 基于标准模式,保留代码检索与审查工具,关闭不需要的委派能力。
    ```复制会带走来源目录里的组装文件、Skills 和资源。系统会保留来源的 description,但去掉来源的 name 和内置排序值,避免副本在列表里看起来和原版一模一样。

    四个内置 Preset 属于 system,只能查看和复制,不能直接编辑或删除。用户创建的 Preset 属于 user,可以打开目录、继续复制或删除。

    方法二:让创造模式帮你做

    DSH Web 的“用「创造模式」创作自定义预设”会开启一个使用 cordis Preset 的空白会话。可以直接描述目标,例如:

    基于 standard 创建一个 backend-reviewer。保留文件读取、搜索、Plan 和 Todo,去掉 Ralph 与普通 Subagent,加入一份后端代码审查 Skill。完成后验证它能够正常挂载。

    正常流程应该是:

    - Agent 加载 editing-cordis-compositions Skill。

    - 查看当前 Preset 名单和运行时接口。

    - 通过 Preset 服务复制一个内置组合。

    - 修改用户目录下的 preset.yml 和 agent.cordis.yml。

    - 按 Host plane、Agent plane 和 isolate realm 规则检查插件位置。

    - 实际挂载一次做验证。

    - 让用户新建会话,检查最终工具列表和提示词是否符合预期。

    用户 Preset 目录通常不在当前项目工作区里,写文件时可能需要额外批准。运行动态 Cordis 代码也可能触发批准。这些都是正常的权限边界。

    当前 Waku 主要支持选择和使用 Preset,还没有 DSH Web 这套复制、删除和打开目录的完整管理界面。可以在 DSH Web 中完成复制,也可以在 Waku 里进入创造模式,通过对话让 Agent 创建。我们给 Waku 补的“选择器打开时刷新”,就是为了让新 Preset 能及时出现在列表中。

    Preset 什么时候生效

    Preset 是会话创建期配置,不是随时可以切换的皮肤。

    - 新会话页面可以选择一次 Preset;设置里的默认值只影响以后创建的会话。

    - 空白会话还可以切换。只要已经产生过对话或工具历史,Host 就会拒绝切换。

    - 这样限制是因为历史中的工具调用属于原来的工具 schema。中途换组合,新的 Agent 可能连旧工具名都不认识。

    - 修改 agent.cordis.yml 后,新会话会使用新的组装代际,已经运行的会话继续使用旧版本。

    - 删除一个自定义 Preset,也不会强行停止已经由它创建的会话。

    - Subagent 会继承父 Agent 正在使用的同一份 Preset 代际,不会重新读取磁盘上的最新版。

    Host 每次读取 Preset 名单时都会重新扫描目录,所以新建和删除在服务端是实时可见的。客户端如果自己缓存名单,仍然需要主动刷新;我们之前在 Waku 遇到的就是这个问题。

    还有一个当前限制:DSH 主要用 agent.cordis.yml 的修改时间和大小判断是否产生了新代际。只修改旁边的 Skill 或资源文件,未必会让新会话立即拿到新版本;这时需要同时触碰组装文件,或者重启 DSH。

    最后要注意信任级别。user 只表示“这是本地创作的 Preset”,不代表它运行在低权限沙箱里。Preset 的实际权限取决于它引用了哪些插件。拿到别人分享的 Preset 后,应该先读 agent.cordis.yml,再决定是否使用。

    “自进化”目前能做到哪一步

    在创造模式(cordis)下,Agent 可以获得一组运行时工具,用来:

    - 检查当前服务、事件、工具和插件契约

    - 定义新的动态 Package

    - 启动或更新 Package

    - 停止动态 Run

    - 回滚到旧版本

    - 删除动态插件

    这些工具确实能修改当前运行时,但不会直接改仓库源码,动态 Package 重启后也不一定还在。

    目前比较实际的用法是:```
    运行时实验
    → 检查效果
    → 更新或回滚
    → 确认有效
    → 固化成正式插件
    ```需要长期保存或分享时,再把验证过的代码整理成正式插件或 Preset。运行动态代码通常还要经过用户批准。

    Agent Loop 被拔掉怎么办

    真把 Agent Loop 停掉,Agent 当然就不能继续对话了。插件化只说明组件可以替换,不代表任意组合都能工作。

    一个可用的 Coding Agent 至少需要:

    - 一个模型适配器

    - 一个 Agent Loop

    - 一组消息与会话能力

    - 最基本的输入输出路径

    目前 Cordis 的动态工具主要管理动态定义的 Package,并不能随便拆掉整个宿主。

    不过,DSH 还缺少一套很清楚的保护规则:哪些插件不能拔,哪些可以热替换,哪些修改只能下次会话生效。当前实现里,这部分还有完善空间。

    Cordis 就是 DSH 的 Core 吗

    可以这么理解。更准确一点,Cordis 是 DSH 的运行时 Core。

    传统 Agent 的核心直接规定:

    - 循环怎么运行

    - 工具怎么调用

    - 模型怎么接入

    - 状态怎么保存

    DSH 固定下来的部分更少,主要负责:

    - 启动进程

    - 建立 Cordis Context

    - 加载插件

    - 提供服务和事件契约

    - 管理生命周期

    具体组装出什么 Agent,由后面的 Preset 和插件决定。这就是我们之前说的“Core 更后置”:核心只管怎么组装,不提前写死组装结果。

    这套架构有先例吗

    有,而且不少:

    - Emacs:编辑器本身也是一个可编程 Lisp 环境

    - Smalltalk Image:系统可以在运行时检查和修改自身

    - Eclipse/OSGi:功能通过服务化插件动态组合

    - Erlang:支持运行时升级和热代码替换

    - 微内核系统:核心只保留最小机制,其余能力放到模块中

    - 浏览器扩展系统:宿主提供生命周期和权限,扩展决定具体能力

    DSH 的新意是把这些做法放进 Agent 场景,还让模型参与插件的检查、生成和运行。以前插件系统主要给开发者用,现在 Agent 自己也开始操作插件系统。能力更灵活,权限、回滚和稳定性也会更难做。

    Pi 和 DSH 的差别

    两者都支持自定义工具和 Provider,但重心不一样。

    Pi 先把 Agent Loop 和 TUI 做稳,再让用户通过 Extension 扩展它。DSH 先做运行时,让 Agent Loop、服务和工具都参与组装。

    所以我们用了 Vim 和 Emacs 的类比:

    - Pi 像 Vim:一个稳定的编辑核心,加上强大的扩展生态

    - DSH 像 Emacs:核心之外还是一个可以继续编程和重组的运行环境

    Pi 的扩展也可以很强,DSH 也有固定核心。这个比喻只说明设计重心,不比较谁更强。

    日常怎么用 DSH

    日常写代码不需要一直开创造模式。我们建议这样分:

    - 日常编码使用标准模式、PTC 模式或自己的 Code Preset

    - 编写普通仓库插件时仍使用日常编码 Preset

    - 需要检查运行时、快速试验动态能力时使用创造模式

    - 动态插件验证成功后,再固化成正式插件或预设

    创造模式更像实验室。工作流和普通开发差不多:```
    想法
    → 运行时原型
    → 快速验证
    → 回滚或迭代
    → 固化为工程代码
    ```它省掉的是“改完代码、重启整个 Agent、再验证”这段来回折腾。

    Waku、Web 和 ACP

    DSH 自带 Web 界面,但写代码时,桌面客户端或 TUI 通常更顺手。我们检查了 Waku 的 DSH 接入,它走的是 DSH Web 后端的 HTTP 和 WebSocket,不是 ACP。

    连接关系如下:```
    Waku GUI
    ↓ HTTP / WebSocket
    DSH Host
    ↓
    Preset / Agent Loop / Cordis / Tools
    ```Cordis 和插件都跑在 DSH Host 里,自进化能力也在服务端。客户端只要能:

    - 展示会话

    - 发送消息

    - 渲染工具调用

    - 处理用户批准

    - 刷新模型和预设目录

    DSH Web 和 Waku 都能使用同一套运行时能力。

    ACP 的价值在于统一客户端协议。以后要接更多编辑器或 TUI,可以考虑 ACP;目前用 Waku 验证 DSH 没有问题,没必要先重写一套接入。

    动态后端也需要动态 GUI

    我们在 DSH 里创建了新 Preset,Waku 的选择器却没有更新。Preset 创建成功了,问题出在 Waku 缓存了 Provider 目录,只在启动或 Provider 重载时读取一次。

    后来我们给选择器加了“打开时刷新”,并把修改提交给上游。

    这个问题不复杂,但很典型:后端的模型、Preset 和工具会动态变化,客户端就不能一直拿启动时的缓存。还要考虑目录刷新、事件更新和能力失效。

    自定义 Provider 的模型能力要自己补

    DSH 可以通过通用的 llm-pi-ai 适配器接入 OpenAI-compatible Provider。

    接入过程并不复杂:

    - 设置 Provider ID

    - 填写 Base URL

    - 选择协议

    - 保存 API Key

    - 获取远端模型列表

    我们接入 CPA 后,通过 /models 成功拉到了模型列表,但界面里没有 thinking level。

    原因是 OpenAI-compatible 的模型列表通常只包含:

    - 模型 ID

    - 显示名称

    - 上下文窗口

    - 最大输出长度

    这里没有统一的推理能力描述,DSH 也不会根据模型名字猜 thinking level。

    下面这项配置:```
    compat:
    supportsReasoningEffort: true
    ``只说明 API 接受 reasoning_effort,具体模型还要单独声明:``
    models:
    - id: custom-reasoner
    reasoningEfforts:
    off:
    low: low
    medium: medium
    high: high
    ```协议能接什么参数,和模型实际开放什么能力,是两份配置。DSH 当前要求部署者手动补齐模型能力元数据。

    DeepSeek 模型名不会触发专用适配

    DSH 按 Provider 路由选适配器,不看模型名称。

    如果请求是:```
    provider: custom-gateway
    model: deepseek-xxx
    ``上面的请求仍然走通用 llm-pi-ai。只有 deepseek-official 路由会进入 llm-deepseek`。

    DeepSeek 专用适配器主要处理:

    - DeepSeek 原生 thinking 参数

    - off/high/max 推理等级映射

    - 工具调用轮次的 reasoning_content 回传

    - DeepSeek 工具消息格式

    - DeepSeek SSE 流式响应

    - Cache token 统计

    - DeepSeek 错误分类

    - 模型上下文和输出默认值

    这些主要保证协议处理正确,不会额外增强模型能力。

    如果自定义网关已经把 DeepSeek 协议完整转换成 OpenAI-compatible API,日常使用的差别可能不大。差异通常出现在:

    - 长时间多轮工具调用

    - 大量 reasoning 内容

    - 上下文压缩

    - 流式响应边界

    - 网关没有完整透传 DeepSeek 特有字段

    普通对话和简单改代码时,两条路径可能感觉不出区别。多轮工具调用和长 reasoning 场景下,专用适配器会更稳一些。

    最后怎么理解 DSH

    现在我们更愿意把 DSH 看成一个 Agent 运行时:

    - Cordis 用可逆 effect 管理组件在时间上的进入与退出

    - Cordis 用响应式 coeffect 管理组件在空间依赖图中的连接与重组

    - Preset 决定 Agent 的组装方式

    - Plugin 提供具体能力

    - Agent Loop 只是其中一个组件

    - GUI 是运行时的观察和控制界面

    - ACP 或 WebSocket 是客户端接入协议

    - 创造模式让 Agent 开始参与自身能力的构建

    DSH 现在的优势不一定是“写代码比别的 Agent 更快”,而是可以更灵活地拆分、替换和试验 Agent 能力。

    它还不成熟,下面这些问题都没有完全解决:

    - 动态能力的持久化与分享

    - 必要插件的保护机制

    - 前端对动态目录的完整支持

    - 自定义模型能力元数据

    - 权限和审批边界

    - 运行时实验如何转化为可维护代码

    Pi 更关注把一个小而稳定的 Agent 做好,DSH 更关注怎样继续组装出不同的 Agent。回到开头的类比:一个更像 Vim,一个更像 Emacs。

    主楼
  • Uncle_Money

    有过深度开发 Agent 经验的,很容易就能理解 DSH 的价值所在,也能很快理解究竟有多自由。

    不过,话说回来,真要想看到 DSH 一骑绝尘的领先,或是跑出一个确定性的优势,目前的 LLM 能力还是有点不够看。

    #1
  • blless

    怎么看着像后端微服务一些架构设计的依赖注入设计,自动管理依赖,自动生成依赖链,生命周期管理。。感觉这个架构设计很像后端架构设计底子的人搞的。。

    #2
  • huo0

    还真是这个感觉

    大约去年此时我就在搓类似的运行时

    感觉是skill还没流行的时候

    不过后来弃了

    #3
  • Fayilor

    感觉更像Nix一点,声明式依赖图,可逆副作用,最终状态只跟最终配置有关。

    但是跟Emacs还是有不一样的,Emacs几乎不区分插件和运行时,一切都是Lisp(这一点,与nix相似的guix也继承下来了,毕竟都是gnu旗下的emm),而且也几乎所有能力都通过函数提供,编辑器能用的,人也基本能调用。

    DSH目前还是Host和Preset分层,虽然创造模式已经能做很多事了,但本质上仍是一个设计精巧的插件系统(指cordis)。

    #4
  • 谢大佬

    目前体验下来感觉又是一个极客的玩具,理念和Pi有一些相似,开发者给了一个基座,可以通过这个基座扩展出各种可能。但是跳脱开coding这个单一场景的话,个人感觉DSH似乎野心更大,毕竟是梁圣背书,看好它的未来。

    #5
  • Fayilor

    去看看cordis就知道了,人家已经是很成熟的架构了,dsh直接拿来用了

    #6
  • Zcb

    感觉是一个能够影响接下来Coding Agent走向的一个东西。目前个人来看可玩性很高。

    #7
  • blless

    cordis

    看了一下 最早提交4年前,但是依赖注入这套,不说java spring吧,golang 的dig依赖注入都是7年前的玩意了。。这玩意真不是啥新东西啊,还发论文了,有点绷不住 ^-^

    #8
  • Fayilor

    原理当然是很早就有了,但是各领域的实践确实是不一样的,所以不神化,正常使用就好。

    所有的系统都只是管理自己想要管理的一部分,不相关那部分就形成了外溢的熵。

    dsh这套大概能预想到同时使用大量插件的管理问题和生态混乱问题,另外可能插件能力参差不齐,非开发者安装一大堆推荐插件后没有能力甄别用ai来管越来越乱 (

    #9
  • aeo

    其实我去年在 skill 还没这么火的时候,也尝试做过类似的机制,是热插拔的插件形式。

    但是实际用久了之后,想增删东西,或者说当初立项时没有考虑得这么全面,就不可避免会有卸载残留:即便卸载插件,依然会出现残留子进程、临时文件没有回收的情况。

    不是 DSH 独有的问题,是几十年动态插件系统反复遇到的固有矛盾。从早年 OSGi/Eclipse 的 bundle 陈旧引用泄漏、Emacs Lisp 插件卸载后钩子残留,再到 VSCode 扩展卸载后孤儿子进程存活,包括 JS 生态 Koishi 这套同样主打可逆插件的框架,都遇到同类问题

    #10
  • 老文

    看你了好几遍,但是发现有点看不懂,但是我感觉这样插件化其实很好,舍弃所有全部交给插件来完成

    #11
  • xsmingerfan

    如果每个步骤是可逆的,在这套框架内可以保证没有残留。但是问题是可逆的保证是一个很强的保证,插件的行为不一定能保证这种可逆性

    #12
  • edapan

    好文章,通读下来,原本以为Pi Coding Agent已经够自由了,但是没想到DSH比Pi还自由。

    但是DSH把Agent loop也可以定制化,那么没有一个稳定的Agent loop,DSH搭建出来的Agent质量可能参差不齐,全靠使用者的设计理念和LLM的能力吗?

    现在各家的coding agent 的 agent loop 虽然大同小异,但是细微优化还是有差距,这方面还得看deepseek怎么设计底线了。

    #13

发表回复

登录后回复