marchliu/dsh-cybernetics:把 agent 编排做成控制回路的 DSH 插件

前言

用 DSH 驱动多步任务时常见的问题是:目标只活在当前 prompt 里。一次性把需求写好发下去,agent 做一步算一步,做完之后验收责任完全落在人身上;结果偏离目标时,只能靠再补一条 prompt 修正,修正完再等一轮——整个系统是开环的,测量和校准都在人这边。

下面介绍的插件把这套回路搬进系统本身。marchliu/dsh-cybernetics 由 MarchLiu 维护,分类属「工作流」,GitHub 上有 11 个 stars、2 个 forks,许可证 MIT,协议名为 Cybernetics 工作协议。官方定位写得很直接:这是一个把多 agent 协作显式设计成控制回路的 DSH 插件,让用户从「逐条 prompt 的作者」变成「目标的设定者与外环的校准者」。协议全文见仓库内的 docs/protocol.md,演进历史见 docs/CHANGELOG.md,两份文档均为中文。

它解决什么问题

插件的命名来自维纳的控制论:设定目标(setpoint)、行动、测量输出与目标的偏差、用偏差修正下一次行动。README 把这套设计映射为四件事:

  • session goal 作为 agent 思考的产物持久存在,而不是只存在于当前 prompt;
  • task agent 行动,verify agent 测量,triage agent 分诊;
  • controller 在轮间推进;
  • 用户通过对话完成外环校准。

目标、行动、测量、校准构成负反馈闭环。当测量结果指出目标本身有问题时,回路不再自动迭代,而是回到对话。这正是它与传统一次性 prompt 的分野:传统做法是开环系统,测量的责任全在人。

核心概念与角色分工

插件内部定义了五类角色。先理解分工,后面讲的功能才有依托:

角色 职责
session agent 控制器:识别用户输入、维护 goal 板、启动/收取/取消轮、向用户转述;从不亲自执行任务
controller 轮间状态机,位于 src/controller.ts:startRound/foldRound/runRound,按轮推进 subgoal(task → verify → triage),维护审计日志
task agent 执行器:无对话历史的独立 sub agent,面向工作委托书(brief)实际执行,产出结构化交付报告
verify agent 测量仪器:独立于执行者验收,产出结构化 verdict {met, criteria, gaps, goalItselfQuestionable}
triage agent 分诊:对 verdict 暴露的后续线索四分拣 auto / defer / reject / human
用户 外环校准者:对话校准(目标修订经 session agent 的 revise 执行)、裁定 human 类目、确认完成

两条硬约束值得先记住。其一,controller 永不自宣完成,完成与否最终由用户确认。其二,上下文是被配给的、不是被继承的——task agent 只拿委托书,verify agent 只拿 goal、任务报告与证据,绝不拿 task agent 的叙述,否则验证就被污染。插件明令永远不使用 context: 'fork'。

核心功能

goal 是持久状态,内环自动、外环兜底

goal 是一等公民的持久状态(objective + 当前 subgoal + 完整工作委托书),构建在 dsh-goal 服务之上,自 v0.2 起不再依赖 goal.md 文件。

内环(task → verify → triage → subgoal 修正)在目标不变时自动迭代,每轮有连续迭代上限作为兜底。外环(用户与 session agent)只在目标本身有问题时介入——verdict 一旦给出 goalItselfQuestionable=true,插件禁止自动迭代,强制回到对话。这条边界是整套协议的安全阀。

append-only goals 与 revise 修订决策树

goal 一经存在不再原地修改,revise 是唯一的改目标动词,且 reason 必填。修订会创建带 parentGoalId 的新 goal 并立即启动其第 1 轮,旧 goal 置 superseded、记录 supersededBy,每次修订追加审计行到 .cybernetics/revisions-<新goalId>.jsonl。

v0.7 起,abortPrevious 缺省时按自动决策树裁定(纯函数 decideAbort,依据 goal 板的 running 状态):旧轮在跑且修订改写目标本身(objective 或 depth 变化)→ 自动中止旧轮;旧轮在跑但目标不变(仅 brief/title/role 修正)→ 等待旧轮自然结束;旧 task agent 已结束 → 直接启动新 goal。显式传 abortPrevious: true/false 则保持人工控制,每次修订的审计行与 revise 返回值都携带 abort: {abort, mode: auto|explicit, basis}。

被取代的 goal 再次 revise 会被拒绝(already superseded),以免谱系分叉。

quick / full 双档轮次

  • quick:单 task agent 一轮出结果直接反馈,跳过 verify/triage,成本约为 full 的三分之一,verdict 由任务报告派生。
  • full(默认):完整走「工作 → 验证 → 分诊」循环,验收通过才交付。协议明令禁止用 quick 抄近路做变更。

allGoalsMode:任何请求先建 goal 再执行(默认 false)

开启后注入「ALL-GOALS MODE ACTIVE」路由协议,四条规则:

  1. 任何用户请求(问题、检索、分析、改动)一律先建 goal(start + title/brief)再执行;session agent 禁止用读工具就地完成任务,哪怕单文件查询或一次计算也走回路。
  2. 简单只读请求 → depth quick:有界查询、单文件读取、小型统计,单 task agent 一轮。
  3. 复杂分析/写任务 → depth full 多轮渐进:start 异步启动,逐轮 collect 收敛,controller 按轮推进 subgoal;写任务额外依赖硬门——session agent 永不亲自改动,goal 回路是唯一路径,改动再小也一样。

关闭该模式时,提示段与 revise 行为与 v0.6 完全一致。配置入口在 v0.7.1 之后:插件导出 schemastery Config schema,allGoalsMode 为 volatile 字段,在 DSH Web 编辑器 Settings → Plugins 的本插件条目面板上直接切换,保存后 loader 提交新值进运行实例(不 remount),提示段即时重挂,无需再手改 profile JSON。

硬门 hardGate:默认 allowlist 的结构性门禁

hardGate 默认 true,安装即激活,v0.8.10 起在插件页设置卡片上可热切换。hardGate 是 volatile 字段:保存开关即把新值提交进运行实例,插件随即卸载或重装门禁监听(装配摘工具 + 分发拒绝 + 审批透传),即时生效,无需重启宿主、无需插件重载。

生效语义(v0.8.6,默认安全):hardGate 开启且未配置 gateMode 时,门按 allowlist 运行。顶层 session agent 只保留读 + 对话 + 展示 + goal/控制动词(内置 DEFAULT_GATE_ALLOWLIST,可经 gateAllowlist、gateAllowlistExtra 扩展),所有变更类工具在装配期被移除,并在调用分发层被拒绝。

之所以改成这个默认,是因为旧的 denylist 默认是个陷阱:它只隐藏 gatedTools 的三个名字(bash/edit/write),bsk_*、taskboard_*、schedule_*、farm_*、uno_*、ppt_*、config_restore 以及整个变更工具长尾都仍可调用——名为硬门、实为半门。schema 不再给 gateMode 设默认值,因此「未设置」与「显式 denylist」可以被区分。hardGate: false 则与以前完全一致,不装门。

显式配置 gateMode: 'denylist' 属残留风险、需主动选择:只移除 gatedTools(默认 bash/edit/write)中的工具名,顶层 agent 至少仍可使用 bsk_*(含 bsk_session 启停)、taskboard_* 写操作(create/update/move/comment/checklist/delete)、schedule_* 写操作、farm_* 服务生命周期(start/restart/stop/register/unregister)、uno_* 与 ppt_* 生成器、config_restore、subagent/team 派生、knowledge_save、job_kill。这个集合随每个新装插件增长,denylist 永远不可能完备,只有在清醒接受该风险时才显式选择,并把需要的变更工具逐项补进 gatedTools。该模式下注入的 HARD GATE 提示会如实说明「只摘 gatedTools」的半门立场,不再假称所有改世界的工具都结构性不可用。

task/verify/triage 子 agent 从不在门禁范围内,保留全部工具。部署注意:本构建首次上线时运行中宿主仍持有 pre-0.8.10 代码,需重启宿主一次加载新代码;此后开关即热生效(lib/client.js 只需刷新浏览器)。

子代理审批透传 gateApprovalRelay(默认 true)

宿主在委派边界把每个子代理(workflow 子代理、teammate)的审批策略钉死为 never——每次审批请求在任何 answerer 看到之前就被确定性拒绝。结果是 task agent 需要沙箱越权(典型:写会话工作区之外的路径,如 ~/.dsh/profiles/web/cordis.patch.yml)时,用户根本没机会被询问。

gateApprovalRelay 开启时(默认,hardGate 伴随项),插件做两件事:

  1. 创建时解除子代理策略钉死:监听 agent/created,对委派子代理在其 session 上追加一条持久 approval/policy {policy:'ask'} 事件。宿主的折叠规则保证后写事件覆盖委派时的钉死值;没有这一步,审批服务会在任何 answerer 运行之前把请求确定性拒绝成 rejected。
  2. 在顶层 session agent 上重新发起请求:插件把 approval/request answerer 注册为最外层(prepend: true)。宿主在启动时(早于所有用户插件)注册的 approval/request 转发器会被浏览器审批 answerer 抢先 claim,子会话面板用户看不见,转发器永远轮不到执行;插件抢在最外层运行后,沿持久 parentSession 链找到子代理的顶层活祖先,直接调用宿主的 ApprovalService.request,让原生允许一次/拒绝面板出现在用户正在看的那个会话里,并在该 session 的 approval/asked + approval/decided 审计对中留痕。子代理的 tool call id 不向上转发,面板标题(本地化 displayReason)会写明是哪个子代理发的请求。

回退与终态语义(v0.8.8):若顶层回合已关闭(continuable 子代理比父回合活得久),或原生面板应答 unavailable(没有应答端 claim 该请求,例如 frame 根本没被浏览器认领),relay 继续回退到同一顶层 agent 上可见的 ask_user_question 卡片(「子代理审批透传」)——原生 unavailable 不是终态。仅当问题卡片通道也不可用时才以终态 unavailable 结束;完全没有活的顶层 agent 则回落原生应答链。拒绝语义始终显式:转发失败答 rejected、子代理已中止答 cancelled,子代理模型总能经自己的审批审计对拿到真实结果,不会静默成功。

可观测性与排障(v0.8.9):relay 每次决策记录两处——宿主 logger 一行 cybernetics approval-relay: path=… outcome=… root=… elapsed=…ms(无 logger 时 console.debug 兜底),以及 <workspace>/.cybernetics/relay-log.jsonl 追加一条 JSON({ts, root, path, outcome, elapsedMs})。该文件是验收时的落盘事实,无需宿主 stdout 或浏览器 console 即可直接读取。path 取值:native-panel、question-card、next-passthrough(无活顶层 agent → 原生链)。典型排障:子代理快速失败且审计为 decided unavailable,但 relay-log.jsonl 一条记录都没有,说明请求根本没到 relay,先确认宿主确实重载了插件构建;若出现 path=native-panel outcome=unavailable 后跟 path=question-card 两条,说明 frame 离开了宿主但浏览器没有认领——此时用户仍能看到问题卡片。

设 gateApprovalRelay: false 可回到严格确定性拒绝的立场(CI/无人值守部署)。这个字段是 hardGate 伴随项,属非 volatile / 配置文件维护字段,请在 profile 配置文档中维护,保存触发插件重载。宿主侧证据(文件:行号)、被否决的方案与复现方式记录在 docs/approval-relay-investigation.md。

持久执行者 executor

executor: 'ephemeral' | 'persistent',与 depth 正交。persistent 用 Agent Teams 的持久 teammate 执行 task 工作:首轮 spawnTeammate(context:'fresh') 投生,后续轮只投递委托书修订(新增约束/验收标准变化/范围收窄,禁止开放式对话)。full 轮中 teammate 报告直接作为 task 报告,task agent 被跳过,verify/triage 仍以临时 agent 独立运行——验证不继承任务叙述。quick 的 verdict 由 teammate 输出派发(输出契约要求最后一行 {"done": bool, "summary": "...", "evidence": "..."},缺失标记即视为未完成)。

这是软集成:运行时优先用 Cordis 的无注入反射读 ctx.reflect.get('agentTeams')(不需要 inject,返回服务值或 undefined),再回退 try/catch 安全访问器读 ctx.agentTeams(Cordis 的 context 代理在读取未注入的服务属性时会直接抛错,不能当成「服务不存在」)。两路都取不到就回退 workflow,不硬绑实验包,报错是结构化的 needs Agent Teams … fall back to executor "ephemeral",不是 Cordis 代理异常。goal 完成/cancel 时会 interrupt 并清理常驻队员,每 goal 至多一个执行者,goal 板保持唯一真相、不双写 team 的 task 板。

多 goal 板与客户端页签

同一 session 支持多个并发 goal(插件自有 goal 板;dsh-goal 的单槽降级为焦点 goal)。goal 板是进程内存态,每个写点(goal 创建、轮次启动、结算决策、cancel)同时把整板快照到 <workspace>/.cybernetics/board-<sessionId>.json;首次触碰空板时从快照 rehydrate,恢复后立即 publishBoard() 一次,页签不用等下一轮就显示历史 goal。sidecar 文件写盘失败只吞掉(try/catch),只读工作区照样能跑轮次;快照是当前状态、不是事件日志,事件日志仍在 session 里。中途被杀死的轮会以 status: 'running' 恢复但无运行句柄,下一次 round/start 会正常重新启动。

客户端 lib/client.js 提供两个会话页签,经插件自有 session 投影 cyberneticsGoals 实时推送、就地更新:

  • 「目标 / Goal」:本会话全部 goal(标题、状态徽标、第 N 轮、当前 subgoal、depth、executor 小标签、被取代与修订来源标注),按「未完成在前、已完成在后」两段排序,空板回退 dsh-goal 单 goal 渲染。
  • 「控制 / Control」:递归展示本会话全部 agent(主控 + subagent catalog)的运行状态与各自 goal 视图,顶部汇总运行中数量,并列出每个 goal 的执行者行(状态 + 标题 + executor 临时/持久 + 持久 teammate 名称 + 轮次),与「目标」页签同一数据源。

两个页签都没有第二套状态,数据源就是同一份 cyberneticsGoals 整值投影。v0.10.0 起,每个会话头部常驻一枚徽标:绿点加「开」表示当前控制模式生效中(本会话 /cybernetics on 显式 override,或继承全局 allGoalsMode),灰点加「关」表示生效关闭。

安装与启用

插件发布在社区目录 SkillHub 与 GitHub 上。安装命令请从社区目录页获取,不要凭仓库名拼接:

  • 社区目录页:https://www.skillhub.cn/plugins/MarchLiu/dsh-cybernetics
  • GitHub 仓库:https://github.com/MarchLiu/dsh-cybernetics

需要先说明的是,社区目录是独立站点,与 DeepSeek / 幻方无官方从属关系,不要把安装来源当成官方应用商店。包信息(来自仓库 package.json):包名 @mars.liu/dsh-cybernetics,版本 0.15.0,许可证 MIT,type: module,两份 README 的徽章版本为 0.10.0。peer dependency 为 @deepseek-ai/dsh-session,运行时依赖 @deepseek-ai/schemastery ^3.18.5;engines.node 要求 >=23.6。

启用后的行为:

  • hardGate 默认开启,无需额外配置即拿到门禁;gateMode/gatedTools/gateAllowlist/gateAllowlistExtra/gateApprovalRelay/executor/depth/axiom* 等字段属非 volatile,在 profile 配置文档中维护,保存触发插件重载。
  • allGoalsMode 默认关闭,需开启全量目标路由时在 Settings → Plugins 面板切换。
  • 首次安装本构建时,宿主若仍持有旧代码,需重启宿主一次加载新代码,之后开关即热生效。
  • 仓库根目录提供 cordis.patch.yml 作为 bundle patch,lib/client.js 侧依赖若干 @deepseek-ai/dsh-client-* 注入包,加载前确认宿主的插件机制已支持 bundle patch 与 client inject。

典型用法

插件的工具面集中在 goal 生命周期,cybernetics 是核心动词。会话内走一轮的路径如下:

  1. 发起目标。由 session agent 创建 goal(给出 title/brief/objective),或在 allGoalsMode 开启后由任意用户请求自动触发。goal 板随即出现一行 running 记录。
  2. 观察轮次推进。插件以每轮一次 workflow 运行的方式驱动内环:task agent 读委托书执行并产出结构化交付报告,verify agent 产出 verdict,triage agent 把线索四分拣为 auto / defer / reject / human;auto 类目自行迭代,human 类目回到对话等待用户裁定。
  3. 用 revise 修订目标。调用形如 cybernetics { action:'revise', goalId, reason, objective?, title?, depth?, executor?, abortPrevious? }——新 goal 继承旧 goal 的 depth/executor/role/objective(可被参数覆盖),写入 parentGoalId,立即启动第 1 轮;旧 goal 置 superseded。
  4. 在 full 写任务下验证门禁与审批透传。session agent 会委派 task agent;若子代理请求越权写入会话工作区之外,gateApprovalRelay 会把允许一次/拒绝面板带到当前会话,决策后可在 <workspace>/.cybernetics/relay-log.jsonl 核对实际路径。若子代理请求被静默拒绝,检查本会话是否确实启用了 gateApprovalRelay,并确认宿主已重载插件构建。
  5. 在客户端确认完成。在「目标 / Goal」或「控制 / Control」页签查看本轮 verdict 与执行者行。controller 不会自宣完成,需由用户确认。

适用场景与注意事项

适合这几类实践者:

  • 需要在多步任务里把「测量」这一步交给系统的人,而不是每次手工验收。迭代型分析、多轮写任务尤其受益。
  • 希望有明确安全边界的团队。默认 allowlist 的结构性门禁,比只隐藏几个工具名的 denylist 更接近其声称的强度。
  • 需要同时跟踪多个 goal、并分别修订的场景。多 goal 板与两个页签正是为此设计。

三条注意事项:

  1. 权限范围。插件以当前 dsh 进程权限运行,安装前应阅读源码与 LICENSE(仓库内 LICENSE 为 MIT),并核对 package.json 声明的依赖与 peer dependencies;社区目录页只作发现入口,不等于官方背书。
  2. 门禁语义不要混淆。「显式配置 gateMode: 'denylist'」是残留风险、需主动选择,只摘除 gatedTools 名单内的工具;需要结构性隔离时保持 gateMode 未设置,走默认 allowlist。
  3. 状态与审计的边界。goal 板是进程内存态,靠 sidecar 快照跨进程恢复,快照存的是当前状态;审计与事件日志在 session 内,relay-log.jsonl、revisions-<goalId>.jsonl 是落盘证据,用途不同,不要混用。

小结

一句话回顾:marchliu/dsh-cybernetics 用控制论的语言重构了 DSH 里的 agent 编排——目标持久化、行动与测量分离、分诊决定下一步、用户只校准外环,配合默认开启的结构门禁和审批透传,把一个容易跑偏且无人验收的开环流程,变成有测量、有兜底、有人工确认的负反馈回路。

更多细节可查阅协议全文 docs/protocol.md 与演进历史 docs/CHANGELOG.md:

  • 社区目录页:https://www.skillhub.cn/plugins/MarchLiu/dsh-cybernetics
  • GitHub:https://github.com/MarchLiu/dsh-cybernetics
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜