@mstar-harness/dsh
v4.0.7
Published
Morning Star harness dsh (DeepSeek Harness) cordis function plugin — in-process engine gates (status/dispatch/lease) with hard refusal channels.
Readme
@mstar-harness/dsh
English | 中文
让 Morning Star 成为一等公民的 dsh(DeepSeek Harness)宿主——一个 cordis 函数插件,将 mstar engine 进程内挂载,实现 engine HostAdapter(host: 'dsh'),守护 {HARNESS_DIR}/status.json 写入(校验 + 咨询;hard 下按修复逃生放行),在 Enforcement: hard 开启时阻止被禁止的 subagent 派发,对挂载技能根下的 SKILL.md 写入执行技能撰写 lint,通过 dsh skill-filesystem 提供者挂载 mstar skills/ 镜像(单一规范挂载),并向每个组合后的 agent 步骤追加一条持久化的 mstar-engine catalog 行(持久化日志的读取方也接受旧标识 mstar-engine-status)。随 dsh Loader 应用启动;一切均通过 seam 的拒绝/咨询通道行使职责,从不改动工具本身。
本插件的运行时要求来自 dsh 宿主:宿主提供的运行时必须提供 mstar store 所需的原生 node:sqlite 模块(store 闸门强制的是这项能力)。Bun 宿主不做版本闸门 —— workspace 各 manifest 里的 engines.bun 只是开发工具链声明 —— store 的 Node 版本下限只作用于 Node 入口。本包不额外要求 Node。
Usage
dsh 应用如何使用本插件——安装路径、配置、挂载时发生什么、强制执行语义。
Install paths
本包以 workspace 包形式发布(workspaces: ["packages/*"]),构建时把 engine 打进 dist/(bun run build;dist 已被 gitignore)。安装途径是 profile bundle,装进现成的 web profile(dsh --profile web——开箱即用的 web 应用 profile,即 dsh web),经 dsh.bundle.patch 清单——一个叠在 dsh-base 默认层之上的补丁层。该补丁还挂载一行宿主中立的 MCP 服务器——即整个宿主共享的 mstar-mcp 连接;需要固定 harness 根的部署用 mstar mcp --harness <绝对路径> 启动它(挂载契约见 bundle/README.md)。
一条命令的 CLI 入口(推荐)——npx @mstar-harness/cli init --target dsh 一次性装齐全量能力:它按序运行下面两条 dsh plugin --profile web add 安装(先 mstar bundle,再 dsh-llm-fallbacks),并可用 npx @mstar-harness/cli doctor --target dsh 逐行报告 uninstalled / disabled / mounted / drifted(drifted = fallbacks 行的安装版本不等于 pin;init --target dsh 会按 pin 重新 add,在此之前 doctor 以非零退出)。它编排的仍是同一条双命令安装;--no-fallbacks 跳过第二行(连带跳过 seeded 角色——见下文「零配置获得什么」)。
(a)registry 安装(发布形态)——npm 包自带构建好的 dist/(安装时无需构建):
dsh plugin --profile web add @mstar-harness/dsh(b)local checkout 安装(开发)——包检出本身,用于迭代插件:
cd <repo>/packages/dsh
dsh plugin --profile web add .dsh plugin --profile <name> add <spec> 首次使用时初始化 profile(web 从出厂模板起步:@deepseek-ai/dsh-base + @deepseek-ai/dsh-web-app),把 <spec> 转发给 profile 目录中的 pnpm,并按已安装状态对账 dsh.profile.bundles 层列表:任何 package.json 声明 dsh.bundle 的依赖都会加入层栈。相对 spec(.、file:/link:)锚定调用目录,因此 add . 须在包检出目录内执行;pnpm 须在 PATH 上。local checkout 需要先执行过 bun run build(本包不使用 prepare 脚本——monorepo 与 cli/opencode 一致,显式构建各包)。细节、层位置与出厂默认见 bundle/README.md——registry 与 local checkout 形态均走同一 pnpm + reconcile 机制。cordis 与 @deepseek-ai/dsh-* 各 seam 均为 peerDependencies——由组合后的 dsh 应用提供。
(c)可选能力:dsh-llm-fallbacks(第二条命令)——基于角色的 subagent 配置能力(见 LLM fallbacks integration)是独立的插件行,须以单独命令安装:
dsh plugin --profile web add dsh-llm-fallbacks双命令安装即契约——把 dsh-llm-fallbacks 行折叠进本 bundle 的补丁明确否决(roadmap §8.3 F4):loader 没有 insert-if-absent 语义,因此同 id 插入是 duplicate loader entry id 启动失败(整个 dsh 会话无法启动);异 id 插入则插件被挂载两次——对同时直接安装该包的人,会出现两次 apply() 与分裂的 fallback 状态(各自独立的 state store、双份监听器、配置覆盖抽签)。层序为 reconcile 追加序:dsh-llm-fallbacks 落在 dsh-base/llm-retry 之后(其硬性排序要求)并位于 mstar 行之后。单命令多激活是上游功能缺口(reconcile 去重或 insert-if-absent 补丁语义),本仓库无法实施。
零配置获得什么——两条行都装上后(经 CLI 入口或上面两条命令),mstar 插件在 boot 时把 13 个 mode: subagent 的 mstar 角色种子(从打包的 harness-agents/ 镜像派生,排除 project-manager)声明进 fallbacks taxonomy:每个 seeded 角色的 persona 默认取其镜像 description 外加一行强制角色加载引导;seeded 状态保持可 revert(fallbacks/revert-seed 网关 / settings 回滚按钮);运行时 advisory 报告缺失 id 与 persona 覆盖。该 seeds 机制即 B4——installed-deployment e2e(tests/install-e2e.spec.ts)本轮把验证闭环:真实 init --target dsh 安装进临时 DSH_HOME、从安装产物 boot,断言 13 个 id 全部出现在 effective taxonomy 且 persona 非空。不含模型路由、不含 automatch 派发、不含 dsh-tui。
被派发子代理的角色身份需要部署方的一处声明。 dsh 0.2.1 通过选择 agent preset 交付子代理身份,因此角色匹配的派发会选择 rolePresets 为该角色映射的 preset。请为每个角色声明一份完整 preset(@deepseek-ai/dsh-agent-preset 行,其 plugins 挂载 @deepseek-ai/dsh-persona 并携带该角色的 persona 文本——persona、工具与技能都在该组合内),并把其 id 列进插件的 rolePresets。未映射的角色保持委派方的组合——不会有任何故障,只是子代理不携带角色身份。详见下文《角色身份交付》。
fresh publish 年龄窗口提示:pnpm 的
minimumReleaseAge门禁可能让dsh plugin add <spec>的 range 解析在全新发布后约 24h 内选中旧版本(不含 seeds surface)——窗口过后重跑npx @mstar-harness/cli init --target dsh(或显式 pin 版本)即可收敛到最新 surface。
Headless profile(一次性运行)
本插件并非 web 专属:它同样可以挂载进 dsh headless profile(dsh --profile headless "<task>"——一次性、无 GUI/无端口模式)。安装路径完全一致,一条命令:
dsh plugin --profile headless add @mstar-harness/dsh出厂 headless 模板在首次使用时自动初始化(@deepseek-ai/dsh-base + @deepseek-ai/dsh-headless),reconcile 步骤把 mstar 行追加进 bundle 层栈(dsh-base → dsh-headless → @mstar-harness/dsh)。插件的所有能力都落在 headless 继承的 dsh-base seam 上——status/dispatch/lease/worktree 闸门、技能挂载、engine-status catalog、harness-rules system-prompt 注入、三个原生工具——因此在一次性运行中 harness 完整生效。必须从仓库工作目录启动(runner 写入 meta.cwd = process.cwd(),按工作区的 harness 目录探测从这里开始)。浏览器客户端半体仅属于 web profile,headless 下自然不加载。
Headless 使用注意事项(已在 dsh 0.1.0-rc.6 上验证):
- 一次性 turn 模型——runner 把任务作为一条 user message 提交,agent 转入 idle(
whenIdle())后即退出;它不会等待run_in_background子任务。前台 subagent 派发可用(创建子会话、结果回到父会话);后台 QC-tri 式并行在进程内不会完成——要么前台派发 QC 席(串行墙钟时间),要么让 agent 在结束 turn 之前用tool-subagent-control收集完后台结果。 - 无交互通道——没有可用应答者时,
ask_user_question与审批提示会 fail-closed。无人值守运行应保留受限权限预设(workspace-write搭配 approvalask,或使用read-only进行只读检查),并在开始工作前核实会话实际生效的权限。仅执行该边界内已允许的操作;若某项操作需要审批,停止该操作并通过 web profile 获取审批。不要通过解除沙箱限制来绕过缺失的审批通道。交互式 Prepare 流程(grill-me)属于 web profile。 - 默认模型解析——headless 不组合 fallbacks 行,因此 settings 里
agent-default-model钉在FallbacksChain会以NO_ADAPTER失败(web profile 的产物)。把默认模型指向真实 provider,或同样把dsh-llm-fallbacks装进 headless profile(注意:已发布的 dsh 0.1.0-rc.6 上,fallbacks 的 settings 集成早于SettingsProvider.installSectionAPI,虚拟适配器不会注册——随 dsh ≥ 0.1.2-alpha 解决)。
Configuration
| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| harnessDir | string | 仓库 .mstarc [config] harness_dir,否则按会话工作区探测(.mstar/ → .agents/ → .plans/ → plans/,从会话工作区根目录开始——绝不从启动 cwd) | 显式 harness 根目录;优先于 engine 探测。harness 根不在探测名列表中的仓库必须配置;探测从会话工作区根开始(绝不从启动 cwd)并在那里停止——永不越过会话工作区向上,因此其上方的 harness 目录(如全局 ~/.mstar)永远不会被采纳。 |
| enforcement | 'hard' \| 'soft' | compass,否则仅告警 | 按部署覆盖。优先级:Config 优先;否则取 Assignment 自身的 **Enforcement**: hard 头字段(仅派发闸门);否则取仓库 .mstarc [config] enforcement;否则取迭代 compass frontmatter;否则仅告警。Config / .mstarc soft 是仅有的本地回滚——Assignment 级 soft 不能覆盖 hard compass。 |
| dispatchTools | string[] | ['subagent', 'subagent_fork'] | 派发闸门匹配的委派工具名——dsh preset 的两个委派工具:subagent 及其 fork 兄弟 subagent_fork(两者都携带 Assignment 形态的 { description, prompt } 参数;toolName 配置可重命名实例)。 |
| dispatchBinding | string | 未设置——caller 从调用方会话推导 | 派发方 agent 自身的 harness 角色(反递归 caller)——根会话身份。Assignment 的 Execute as 等于该 caller 即自我递归(dispatch.anti-recursion.self-type)。该字段只在调用方是根会话(session.header.parentSession 不存在——本 harness 中的协调 agent)时读取:此时显式值(profile patch 或配置页)原样生效,显式置空('')fail-closed,未设置则读作 project-manager(身份读取,绝不凭空编造)。子会话——或闸门读不到会话的路径(无 exec 的宿主 beforeDispatch 钩子)——永不继承它:无论该字段写什么,caller 都是空,于是每一条 Assignment 形态的派发都以 dispatch.anti-recursion.empty-binding fail-closed,因为叶子不得派发,且任何配置都不能解除这条红线。可在 dsh 插件配置页实时编辑(与 roleGate 同一条 volatile 通道)。协调 agent 不是根会话的部署无法用该字段承载。 |
| roleMap | Record<string, string> | 未设置 | mstar 角色 id(Execute as)→ dsh-llm-fallbacks 角色 id。仅作日志与未来规则驱动互操作的分类桥——角色-preset 通道按设计不使用(见 LLM fallbacks 集成)。 |
| rolePresets | Record<string, string> | 未设置 | mstar 角色 id(Execute as)→ 部署方声明的 agent preset id;角色-preset 通道的唯一载荷来源。角色匹配的 start 会在激活请求上选择该 preset(SubagentStartRequest.agentPreset),由 dsh 把它作为子代理的整份组合挂载(persona 经 @deepseek-ai/dsh-persona、工具与技能),并在 resume 时重放该选择。部署方需为每个角色声明一份完整 preset——preset 是整份组合,绝不是只改 persona 的补丁。合并次序:请求自身已携带 agentPreset(委派工具配置的默认值,或模型自己的 agent_preset 选择)时原样生效;否则从 Assignment 头部读取角色,未映射的角色静默跳过。空/纯空白 id 会在插件挂载时被 Config 校验拒绝。当提供者不支持独立选择(capabilities.agentPreset 为 false,或该提供者继承父会话)或组合未挂载 agentPresets registry(web 与 headless bundle 挂载 @deepseek-ai/dsh-agent-preset-registry;base 与 SDK bundle 不挂载)时,通道跳过选择(恰一条 contained debug 日志,start 不受影响)。配置的 id 每次 apply 校验一次——未声明的 id 会得到一条 warning,而不是等到子代理启动失败才暴露。 |
| roleGate | 'off' \| 'warn' \| 'hard' | 'hard' | 子代理启动缝隙处的派发角色归一化(见《角色身份交付》)。显式声明——Execute as: / Act as: / Role: 字段行,或反引号/加粗的已声明角色 id,两者都只在 Assignment 头部内读取——会在 preset 合并前被改写为规范的 Execute as: <role> 头,使两个角色消费方都能绑定。hard 拒绝完全没有可靠角色声明的启动,也拒绝点名两个及以上不同角色的启动(消息点名真实原因与这些 id、确切修复以及逃生口);同时拒绝字段存在但值不可用(为空或不是小写角色 id——消息点名原始值与合法形状)以及只存在于围栏代码块中的规范声明(头部读取不识别围栏,缝隙仍会绑定该引用);warn 每次 apply 只记一条咨询并照常启动;off 只跳过拒绝——单一显式声明仍会被归一化。豁免按子代理实际启动目录判定(绝对 cwd 原样;相对 cwd 相对父目录当前目录解析;缺省 cwd 继承该目录——父目录取运行时自身解析所用的 working-directory 投影,而非不可变的 header.cwd):解析不到 {HARNESS_DIR} 的目录静默直通;已声明可用角色、携带 agentPreset 的请求以及未知提供者同样直通。这是插件两个可实时编辑的设置字段之一(roleGate 与 dispatchBinding):在 dsh 插件配置页保存不同模式会写入 profile patch,并在下一次子代理启动时生效(无需重载);表中其他字段都在 apply 时读一次,需要重载。 |
| skillRoots | string[] | 未设置(不注册自定义根) | 向 dsh skill-filesystem 提供者注册的额外技能根(customSkillDirs 语义——先于用户根扫描)。开发期:镜像 <repo-root>/skills 的绝对路径。 |
| bundledSkillDir | string | 打包的 harness-skills/ 镜像(包相对路径) | 向 dsh skill-filesystem 提供者注册的打包技能根(bundledSkillDir 语义——最后扫描、受信任)。默认取包内自带的 harness-skills/ 镜像(bundle-assets 同步;gitignore)——包相对路径,非 cwd 锚定。显式值优先。 |
| catalogTtlMs | number | 60000 | pre-step catalog 缓存刷新间隔(毫秒):按工作区缓存的统一 mstar-engine 行(迭代闸门 + 工作区摘要)多久重读一次 store.db 执行权威 / compass / 知识索引。刷新间隔之间热路径只是时间戳比较 + Map 命中;会话中 plan/compass/residual 的变化会在一个间隔内落地。 |
| workflowGate | 'off' \| 'warn' \| 'ask' \| 'hard' | 'warn' | workflow/ralph 闸门模式(见 Gates → Workflow / ralph gate)。off = 直通且不产生 verdict 行;warn = 仅咨询;ask = 首见名字走审批瀑布(P-c);hard = 策略违规在任何子进程启动前否决。默认 warn 不改任何 hard 行为——除非部署显式选入 ask/hard,闸门仅咨询。 |
| workflowNames | string[] | 未设置 | workflow 名字白名单(P-a):被闸门视为 KNOWN 的 meta.name 值。为空或缺省 ⇒ 每个名字都 unknown(有文档——闸门绝不因缺省而"全放行")。ralph 调用不携带 meta.name——P-a 对其永不适用。mstar 只读扇出路径推荐值:['mstar-qc-tri', 'mstar-audit-fanout', 'mstar-pr-seats']。 |
推荐的 operator 覆盖层。 上表三个推荐 workflowNames 覆盖 mstar 只读扇出路径(plan QC tri、大型仓库 /codebase-audit、/amazing-pr-review deep);写入它们是 profile 层的操作者覆盖,绝非 mstar 默认。出厂空列表下每个名字都是 unknown,在默认 workflowGate: 'warn' 下这只是运行会存活的一条 workflow.name.unknown 咨询。生产部署若还要否决 unknown 名字,可设置 workflowGate: 'hard';出厂默认仍为 'warn'。
bundledSkillDir 默认取包内自带的 harness-skills/ 镜像(见 Skills mount)——显式 Config 值仍然优先。相对覆盖仍是 cwd 锚定(skill-filesystem 以 join() 语义相对 dsh 进程 cwd 解析),因此覆盖默认的部署应在 profile 层传绝对路径(见 bundle/README.md)。
组合后的行集合
profile bundle 组合出以下行——注册表行来自 @deepseek-ai/dsh-base 层,本 bundle 的补丁在其上插入 mstar 行并携带中性默认(即全应用 e2e fixture 启动的行集合):
- name: '@deepseek-ai/dsh-skill' # skill 注册表(ctx.skills)——dsh-base 行
- name: '@deepseek-ai/dsh-tools' # tool 注册表(ctx.tools)——dsh-base 行
- name: '@deepseek-ai/dsh-commands' # command 注册表(ctx.commands)——dsh-base 行
- name: '@mstar-harness/dsh' # 本 bundle 补丁插入(config: {}——插件默认生效)注册表行先于插件挂载,使 mstar 各闸门、seam 工具与 bundled 命令注册时 ctx.skills / ctx.tools / ctx.commands 已存在。
What the plugin does when mounted
- 状态闸门——
fs/write-intent+fs/edit-intent监听器校验 v3 协调文档目标集——v2 根{HARNESS_DIR}/status.json、workflows/<id>/snapshot.json与projects/<id>/residuals.json——各自使用匹配的 engine 校验器(validateStatus= v2 根 /validateWorkflowSnapshot/validateProjectRegister,即 P2 修复的harnessDocKindOfTarget形态),外加快照目标的findingsCleanupGate扩展(按配置模式的计划行,从项目注册表读取残留)。 - 派发闸门——
tools/pre-execute监听器作用于委派工具,通过 engine 的单一composeDispatchGate组合(字段闸门、反递归预检、默认分支闸门——与 opencode/omp/CLI 对齐,违规码按构造即相同)校验 subagent Assignment 文本,外加 dsh 租约闸门与 worktree L1/L2 检查。 - 技能撰写 lint——已配置技能根下的
SKILL.md写入运行 engine 技能撰写 lint(lintFrontmatter+lintFiveQuestion)。 - seam lint——harness 下
DESIGN.md/ 评审报告({REVIEW_DIR}={HARNESS_DIR}/reviews/)/ 知识文档 / roles 目录的写入运行各自的 artifact 级 engine lint。 - 模型可见工具——
ctx.tools上恰好注册三个工具,且只覆盖 MCP 面做不到的事:mstar_plan_bind(持久槽生产者——dsh 本地executionBinding槽及其采纳收据的唯一写入者)、mstar_audit_validate(评审报告凭据扫描,mstar_audit家族没有任何 action 提供)与mstar_execution_clear(不存在共享的execution.clear命令)。其余所有运行时能力都走 MCP 家族:每个家族一个工具 +action参数。store 级命令(store.*)是刻意的例外:它们搬运整个 store,因此只在 CLI,家族成员以transport: "cli"发布。成员规则与各工具的独有贡献见 § Native command surface。 - bundled 命令——向
ctx.commands注册/iteration-start、/iteration-drive、/iteration-loop、/codebase-audit(来自打包的harness-commands/镜像;每条声明 frontmatterinputhint,使 web 客户端 claim/name并等待用户后续输入而非立即执行;handler 把命令正文 + 用户输入 steer 进接收 agent)。 - 原生执行绑定路径——持久执行绑定由成功的原生
mstar_plan_bind(两个 bind 臂,均在引擎只读 C1 session resume 成功之后)自动采纳,并按需用mstar_execution_clear工具清除:身份来自exec.agent.id(无参数、无模型可选面),清除会保留本会话自己的用户选择与排除下限,未绑定会话或不可读的 binding 记录会得到命名拒绝及其恢复路径,而不是静默写入。这条路径上没有任何斜杠命令——操作者绑定或清除执行会话,用的就是这两个原生工具。 - pre-step catalog 行——一个咨询式
agent/pre-step监听器追加一条统一的mstar-enginecatalog 消息,只携带一条 delta 行(workflow/ 非 Doneplans/branch锚点 / 解析到 steering compass 时的相位gate判定),选中项带警告时再附workflow warning披露行。residuals 汇总、branch policy、spec 集成锚点、row scope、知识摘要、compass 方向与 agent-flow ledger 均为 payload-only(面板 + engine 权威)——见 § Engine-status catalog。发射按会话+工作区变更驱动——首次发射之后,仅当渲染出的行或面板相关 payload 变化时才追加;turn 开始本身从不触发,未解析到{HARNESS_DIR}的工作区不会组合该行。其背后的构建共享同一份内存 TTL 缓存(catalogTtlMs,默认 60 秒);每会话最新发射持久化到 control 根的store.db(dsh_status_emissions),而非文件缓存。
Enforcement semantics
默认仅告警:闸门违规记录日志并发出咨询事件(mstar/status-gate、mstar/dispatch-gate、mstar/skill-lint 及各 seam 咨询),动作照常继续。Enforcement: hard——来自迭代 compass frontmatter、Assignment 头字段或插件 Config(enforcement: hard)——把违规升级为经 cordis 拒绝通道的真实否决/拒绝:subagent 派发在不调用 next() 的情况下返回 PreToolDecision { kind: 'deny', reason };状态/技能 lint 写入因 intent 瀑布链内容盲而从不硬否决——对已非法的文档按修复逃生放行(hard: true, repair: true 咨询),让修复性写入能落地。Config soft 是唯一的本地回滚;hard 闸门绝非全局默认。
Gates
Status gate
fs/write-intent + fs/edit-intent 监听器(以 prepend 注册,确保先于 dsh-fs-policy 执行)对 {HARNESS_DIR}/status.json 的写入把关:基于当前磁盘文档运行 validateStatus + 按 plan 的 findingsCleanupGate(文档只解析一次——无 TOCTOU 双重读取)。闸门从不抛出:每次决策都以 mstar/status-gate 咨询事件呈现,并通过 next() 委托 intent 瀑布链。告警模式(默认)在有违规时记录日志并发出事件。hard 模式对已非法的文档按**修复逃生(repair escape)**放行(error 级日志 + hard: true, repair: true 咨询)——intent 瀑布链不携带写入内容,若对非法文档硬否决,反而会卡死修复性写入本身。意外内部错误在两种模式下都降级为放行并发出 degraded: true 咨询(错误隔离包络);首次破坏文档的写入本身无法在此 seam 上被否决(见 Known Limitations)。
Dispatch gate
tools/pre-execute 监听器作用于委派工具:解析载荷中的 Assignment 文本,在头区域上运行 engine 的单一派发闸门组合(composeDispatchGate——形状守卫、validateAssignmentFields、antiRecursionPrecheck、默认分支闸门、头区域强制执行;opencode/omp/CLI 绑定使用同一组合,违规码按构造即相同),外加 dsh 侧 worktree L1/L2 检查与租约闸门。拒绝通道为不调用 next() 而返回 PreToolDecision { kind: 'deny', reason };告警模式记录日志、发出 mstar/dispatch-gate 并委托。非 Assignment 提示与非委派工具保持惰性。两种模式下 engine 故障都降级为放行,且降级可观测:catch 路径发出 degraded: true 的插件自有咨询 + error 日志,使 hard 部署能察觉控制失效而非静默放行。以 prepend 注册,防止更早挂载的决策监听器把本闸门短路在不可达处。
Lease gate
在 opencode 字段之上:声明 Execution mode: sdd 或行处于 InProgress 的可写 Assignment 派发,将 Worktree path/Working branch 与权威行 metadata.worktree_path/metadata.working_branch 比对,不读取 holder 或逐行 lease。缺少登记 scope 产生 lease.dispatch.unverifiable(可写派发前 STOP)。权威/status/snapshot/row 缺失或不可读仍产生 lease.dispatch.unverifiable 或 lease.dispatch.unreadable;warn 咨询,hard 拒绝。Worktree/branch 不匹配保留 lease.dispatch.worktree-mismatch/branch-mismatch;holder-mismatch 已删除。只读角色跳过;其他非 SDD/非 InProgress 派发保持降级放行。读取限定 assignmentHeaderRegion。
Skill lint gate
作用于已配置技能根下 SKILL.md 文件的 fs/write-intent 监听器,对写入前的磁盘文档运行 engine 技能撰写 lint(lintFrontmatter + lintFiveQuestion——与 CLI mstar skill lint 组合一致)。该槽位内容盲(intent 瀑布链只携带 (target, actor)):文件缺失 = 首次创建 = 放行;磁盘文档干净 = 静默放行;告警模式下有违规 = 咨询 + 委托;hard 模式下有违规 = 修复逃生——文档已经非法,本次写入可能就是修复本身(error 级日志 + hard: true, repair: true 咨询,携带强制执行后的 hardBlocked 判定)。强制执行解析方式与其他闸门相同(Config 覆盖优先,否则取迭代 compass,否则仅告警)。闸门从不抛出;读取失败与意外错误降级为放行并发出 degraded: true 咨询。类型化 hard 否决(SkillLintVetoError,码 skill-lint.veto)位于传入文档分支(lintSkillWrite)——当前接线见 Known Limitations。
Workflow / ralph gate
tools/pre-execute 的一个分支(位于 subagent prompt 分支之前)把关 workflow 与 ralph 工具调用——这是剩余的不携带 Assignment 文本、模型可达的扇出路径。它匹配固定工具名(workflow / ralph);重命名后的 workflow 实例不在范围(名字守卫是固定默认)。非 workflow 工具不受影响——subagent 分支照旧拥有它们,语义不变。
WorkflowGate 的 off/warn/ask/hard 四级模式仍独立于 Enforcement。P-b 先检查登记的可写 scope,再检查 P-a 名字与 P-c 首见审批;不再逐行 holder 准入。
| 策略 | off | warn(默认) | ask | hard |
| --- | --- | --- | --- | --- |
| P-b:InProgress 行缺少 metadata.worktree_path/metadata.working_branch | allow(off) | warn/咨询,workflow.lease.uncovered | warn/咨询;ask 仅服务名字 | 子进程前 deny,workflow.lease.uncovered,点名该行 |
| P-a:workflow 名字 ∈ workflowNames(非空列表) | allow(短路) | allow——无咨询(P-a 在任何模式下都放行) | allow——无 ask | allow |
| P-a:workflow 名字 unknown(空/缺省列表 ⇒ 每个名字都 unknown) | allow(短路) | warn——放行 + 咨询(workflow.name.unknown)+ 一条 warn | ask(首见)→ {kind:'ask'};之后复用缓存决策(allow/deny)——已解析名字绝不再 ask | deny——在任何子进程启动前否决(workflow.name.unknown),reason 点名该名字 |
| ralph(无 meta.name——无白名单身份) | allow(短路) | allow——P-a/P-c 永不适用 | allow——P-a/P-c 永不适用 | allow——P-a/P-c 永不适用;P-b 仍适用(uncovered 时 deny) |
默认 warn 的理由。 默认 warn 使闸门绝不让部署意外吃硬阻断:除非操作者显式选入 ask(人工 ask 通道)或 hard(否决),闸门仅咨询。workflowNames 空/缺省使每个名字都 unknown——闸门绝不因缺省而"全放行",但默认模式把这一点变成咨询而非阻断。
与 Enforcement: hard 的交互。 workflow 闸门的模式是它自己的 Config 旋钮——跨切面的 Enforcement: hard 解析(compass / Assignment 头字段 / Config enforcement)不会升级 workflowGate。hard-enforcement 部署仍按已配置模式运行 workflow 闸门(默认 warn = 仅咨询),除非同时设置 workflowGate: 'ask' 或 'hard';反之 workflowGate: 'hard' 与跨切面解析无关地否决。二者不可混淆:workflow 闸门只在部署把模式选入时才关闭 "Enforcement: hard 下的未把关扇出" 缺口。
Fail-open 边缘(有文档,绝不崩溃合规调用)。(1)畸形参数——workflow 调用缺少非空字符串 meta.name(控制字符归一化之后),或 ralph 调用缺少字符串 objective → 在每个模式下(hard 亦然)直通 + 一条 warn,且无 verdict 行(未产生策略判定)。只含控制字符的名字归一化为空 → 视为畸形。(2)status.json 不可读——经含容解析器路径的 P-b 状态读取抛出 → 仅本次调用的 P-b 降级 + 一条 warn;P-a/P-c(基于名字,无状态依赖)照常运行。闸门从不抛出:每次读取都是结构化的。
Verdict 账本行。 每个被把关的调用都在 agent-flow 账本记录一条持久化 workflow-verdict 行(P2 账本 plan 的记录路径,完全含容——账本写入失败绝不波及闸门):tool(workflow | ralph)、workflow(归一化后的 meta.name)或 objective、mode(绝不为 off——off 在策略前短路)、判定词汇 ok / advisory / denied / ask(ask 判定是本次扩展:首见 ask 本身也是被把关的调用,其行携带 ask 直到审批瀑布解析——"每次被把关的调用一行")。违规码来自判定、绝不猜测:workflow.name.unknown(P-a)vs workflow.lease.uncovered(P-b)。fail-open 路径(畸形参数 / 状态不可读)不记录;未解析出 harness 目录的调用跳过该行(与派发记录路径相同的静默 no-op)。
P-c 答案观测 seam。 闸门无法观测 ask 结果——工具注册表的 serviceAsk 在内部消费审批结果。run-start 观测就是答案 seam:被 ALLOW 的 ask 执行调用 → 持久化 tool-workflow/run-start 会话事件落入父会话日志 → workflow-ledger 消费者记录 W-B2 workflow-run 行并把 allow 按运行名缓存进 apply 作用域的 WorkflowAskCache。被 DENY 的答案不产生运行 → 无观测 → ask 模式下下一次同名调用重新 ask(fail-closed——无授权证据,绝不发明 allow)。缓存键在两个 seam 都是归一化(剥离 ASCII 控制字符)不截断的名字——闸门合成 meta.name 与观测记录 runName 都走同一个 normalizeWorkflowName,因此含控制字符的名字(au\u0000dit)永远无法卡死缓存(ask 一次、同一键观测),>1024 字符的名字仍以完整名字为键(账本行的展示名字单独截断;身份轴从不截断)。缓存是 apply 作用域的——新 apply(HMR 重载)从空开始,因此未解析的首见名字每次调用都会重新 ask,直到一次观测(或显式 record())落地。缓存记录抛错时观测降级为一条 warn——账本行已追加,运行不受影响。
P-b 抢占: InProgress 行缺少登记 source scope 优先于名字策略,对 ralph 同样适用。在可写派发前通过普通 prepare 配置实际 source 事实,不逐行恢复或 claim lease。Warn/ask 仍仅咨询,hard 否决。
LLM fallbacks integration
可选的 dsh-llm-fallbacks 插件(以第二条命令安装——见 Install paths)驱动基于角色的 subagent 配置——角色种子与采纳建议;角色身份交付本身走 dsh 原生 subagent preset 通道(与 fallbacks 无关,见下文)。mstar 插件对该包携带零运行时与零类型引用——src/ 无任何导入(运行时或类型);被消费的服务面由 src/gates/fallbacks-structural.ts 的本地结构类型镜像,该包仅是 mstar 插件的开发期依赖(类型镜像 + 真实包测试 harness)。dist/ 仅在 1 处字符串字面量中命名该包——探测的 loader 条目匹配——绝非导入或类型引用;互操作是决策点能力探测,绝不读取其他插件的模块内部。
能力探测
挂载状态的两个视图(决策点即时读取,无缓存——loader 并发挂载条目):
fallbacksService(ctx)——插件被 apply 期间命名的 cordis 服务(ctx.get('llm-fallbacks'));HMR/纤程切换窗口内即使 loader 条目仍在,服务也为undefined(条目是声明式的,比纤程活得久)。fallbacksMounted(ctx)——能力视图,服务优先 + loader 条目回退:名为dsh-llm-fallbacks的 loader 条目存在、启用(尊重entry.disabled与 group 行)且纤程存活。
状态区分:mounted(服务已 apply——完整能力)、unmounted(无条目:未安装 fallbacks 插件——mstar 能力降级,绝不中断)、disabled(条目存在但禁用/分组——能力关闭),以及 HMR 窗口(条目在、服务缺——loader 回退覆盖)。
角色身份交付(原生 subagent preset 通道)
角色身份通过选择一个已部署的 agent preset 交付:插件经 cordis internal/get waterfall——框架文档化的服务读取拦截钩子——拦截 ctx.subagents 的服务读取并包装运行时值,使角色匹配的 startActivation(spec) 在子会话组装之前合入 spec.request.agentPreset。dsh 0.2.1 中 startActivation 是唯一的启动面(一次性与 continuable 子代理一致),而 preset 拥有子代理的整份组合(persona、工具、技能);dsh 会把该选择记入子会话日志并在 resume 时重放。底层 SubagentRuntime 对象绝不被改动(无 monkey-patching),包装监听器归 apply fiber 所有(HMR 重挂载会先卸载再恢复,并重新绑定新 Config)。persona 交付走 dsh 0.2.1 的设计而非私有段:上游已移除 SubagentStartRequest.persona 与 start/startContinuable 对,且 tool-subagent 对 persona 配置直接报错「configure persona and tool plugins in an Agent Preset and select it with agentPreset (spawn only)」。
能力闸门:独立选择要求提供者 SubagentCapabilities.agentPreset 为 true 且 inheritsParentContext 为 false——正是上游 tool-subagent 断言的同一组合(「provider does not support independent agent preset selection」)。同时必须挂载 agentPresets registry:没有它 dsh 会 fail loud PRESET_UNAVAILABLE,因此通道跳过选择并记录恰一条 debug 日志(每次 apply 只记一次),start 照常继续。闸门未通过绝不使派发失败。
组合归部署方所有:mstar 只负责角色 → preset 的选择。每个角色指向的 preset 都由部署方声明(@deepseek-ai/dsh-agent-preset 行,通常挂载 @deepseek-ai/dsh-persona 并携带该角色的 persona 文本,与上游自带的 standard/minimal preset 同构);mstar 不生成 preset,因为 preset 是完整组合——由 mstar 并不拥有的行构成。
先归一、后拒绝(roleGate):同一个包装器在 preset 合并之前先归一化显式角色声明——Role: \fullstack-dev` 行、Act as:值(去掉一个前导@、引号或反引号后),或反引号/加粗且属于**已声明**角色 id(rolePresets键 ∪ 打包的harness-agents/shell 文件名)的 token——以上都在 Assignment 头部内读取,都会被改写进规范的Execute as: 头,改写后的 prompt 才是内层消费方与运行时收到的内容。两类候选都限定在头部并读取围栏掩码后的文本:正文句子或围栏示例里引用的角色 id 永远不是候选(粘贴的散文或模板无法武装角色);而头部解析器**只在围栏内**读到的规范声明在hard下会被拒绝而不是静默放行——头部读取不识别围栏,缝隙会绑定该引用。头部里的**每一条**Execute as字段行都会被修复并把标签归一化,因为解析器区分大小写且绑定**最后一条**字段行;两个及以上**不同**候选视为歧义,绝不去猜——该拒绝消息点名这些 id 并说明只能有一个声明(自由散文如「You are an architect doing…」永不挖掘)。只有完全没有可靠角色声明(或有歧义、围栏内、字段存在但不可用——为空或不是小写角色 id)的启动才会被拒绝(默认hard),消息点名真实原因与原始值、确切修复以及**与模式匹配**的恢复路径:hard拒绝提供roleGate: warn|off,而 warn咨询只建议roleGate: off(它已经放行)。非 harness 项目按子代理**实际**启动目录判定(绝对 cwd原样;相对cwd相对父目录**当前**目录解析;缺省cwd继承该目录,与运行时自身resolveDirectory的选择完全一致;父目录取 working-directory 投影而非不可变的header.cwd)并豁免;已携带可用 Execute as的 prompt、携带agentPreset 的请求以及未知提供者同样直通(后者的失败由服务按自身语义抛出)。off` 只跳过拒绝;单一显式声明仍会被归一化。
roleGate 与 dispatchBinding 可在插件配置页编辑 —— 两者都是 volatile schema 节点,经 Loader 的实时引用读取,因此配置页的一次保存写入 profile patch 并在无需重挂载的情况下生效。
反递归绑定(dispatchBinding)是根会话身份,永不被继承——caller 从调用方会话推导,且该字段只在根会话上读取。根会话(session.header.parentSession 不存在)就是本 harness 的协调 agent:显式绑定(profile patch 或配置页)原样生效,显式置空仍然 fail-closed,没有显式值时读作 project-manager——身份读取,绝不凭空编造。子会话(任何 parentSession)永不继承该绑定:无论字段写什么,caller 都保持空,于是每一条 Assignment 形态的派发都以 dispatch.anti-recursion.empty-binding fail-closed——正是引入默认值之前的行为,因为叶子不得派发。闸门读不到会话的路径(无 exec 的宿主 beforeDispatch 钩子)同样回落到空,绝不回落到协调者,也绝不回落到已配置的值。声明或推导出的 caller 仍是真实比较:Execute as 等于它仍按自我递归拒绝(dispatch.anti-recursion.self-type);显式置空状态还会在 apply 时记录一条 warn,指向插件配置页。协调 agent 不是根会话的部署无法用该字段承载。
Seam 探针(fail-loud):通道每次 apply 对 cordis internal/get seam 探测一次(临时 canary 监听器 + 一次受控代理式 ctx.subagents 读取——绝不 ctx.get);seam 缺失或未被识别时记录恰一条 warn——role persona channel not installed — cordis 'internal/get' seam missing or unrecognized (role presets will not be selected for subagent starts) + (reason: seam-absent | wrap-skipped)——而非静默失败。健康 boot 不产生任何 warn(锁定的判定表):在今天的组合形态下 apply ctx 不解析 subagents,因此每次 apply 发出一条无 warn 的 service-absent debug——cordis 在 dispatch 作用域解析该服务,读取在那时逐次被拦截;探针内部错误按 fail-open 降级(一条 debug)。wrap-skipped 已被钉死但无法从当前发布的 apply 布线触达——它只在 apply 时即可解析 subagents 的场景触发,该路径由 harness pins 覆盖。seam 名由专用探针家族 tests/persona-seam-probe.spec.ts 钉死(经导出的 PERSONA_SEAM_EVENT 常量);安装产物断言在携带探针的版本发布后自动启用(tests/install-e2e.spec.ts 的 surface-vintage guard)。
角色 seeds 与采纳建议(Adoption advisory)
当可选的 dsh-llm-fallbacks 能力已挂载(第二条安装命令——见 Install paths)时,mstar 插件会向 fallbacks seed registry 零配置声明 13 个 mode: subagent mstar 角色 seed:persona = harness-agents/ 镜像 description(原样)+ 一行强制加载引导(Load mstar-roles (references/<role-id>.md) first — identity comes before skills; load topic skills only when the Assignment activates them via its Skill presets field.);含 {{...}} 插值风险的 persona 跳过并告警,绝不声明。声明会合并保留 readback 中当前已 seeded 的非 mstar id——例如上游包在其自身 apply 时自声明的 5 个上游 preset 角色:上游 declare 全量替换 registry,若不保留,mstar-only 批会摘掉 preset id 的 seeded 注记(行仍在,仅失去 seeded)。声明在每次 fallbacks(重新)apply(HMR/纤程切换)时幂等重放——绝不用一次性 latch——因此两种 boot 顺序(presets 先或 mstar 先)都收敛到同一 18-id 全 seeded registry。boot 时收敛经 bounded retry(有界重试):上游的 seed 写通道在其 apply 之后一个 macrotask 才绑定,因此 apply 窗口内首次尝试被 seeds: settings service is unavailable 拒绝时会重试(跨上游 apply 窗口的 3 次尝试),暂时性拒绝自行收敛;仅当所有尝试最终失败时,声明才记录恰好一条终态错误,同时 advisory 的决策点 re-declare 仍可用作 retry 路径。无需手动编辑 roles.list。
一条只告警的采纳建议通道(日志器 mstar/fallbacks-advisory)每次 apply 只跑一遍——apply 时先尝试一次;当 fallbacks 行在 dsh 之后挂载(loader 并发挂载条目)时,改在首个 subagent/start 决策点只跑一遍。服务存在时,通道先 await 幂等 re-declare(闭合 boot 竞争窗口)再读取有效状态(getEffectiveRoles),并按每类至多一条告警有界报告:
- 缺失 mstar 角色——无有效行的 mstar id(一条告警列出全部;id 集合派生自
harness-agents/镜像——绝不硬编码;无镜像 → 检查跳过并记一条 debug); - persona 覆盖——行 persona 与 seed 默认值不同的 mstar 角色(一条告警点名 + revert 入口:
fallbacks/revert-seedgateway / fallbacks 设置卡片回滚按钮——操作者覆盖在 revert 前保留); - 空 persona——
persona缺失/空白的行,仅限非 seeded 或 overridden-empty(一条告警点名); - 遗留键——
chains、roles.default、roles.list[].label/.description、悬空roles.rules[].role引用,经已应用服务自带的detectLegacyKeys(一条告警引用其语义); - declare 跳过/冲突——本地跳过(
interpolation/no-persona)+ 上游跳过/冲突(码persona-source——操作者覆盖保留)合并为一条告警;默认已 seeded 静默(一条 debug 点名)。
loader 回退路径(无服务)保留结构化 roles.list 读取(缺失 id / 空 persona;无 revert 入口——无 seeds 面;遗留键检查跳过——绝不重新实现)。行配置缺失或非对象、或 roles.list 不可读 → 跳过并记一条 debug。该建议绝不写入 fallbacks 配置——唯一写路径是经已发布 seeds 面的幂等 seeds re-declare(无差异 → 上游零设置写入)——绝不抛出,且 fallbacks 未挂载时不调用:它是信号,不是闸门。
配置面
| Key | Type | Default | Meaning |
| --- | --- | --- | --- |
| roleMap | Record<string, string> | 未设置 | mstar 角色 id → fallbacks 角色 id。仅作日志与未来规则驱动互操作的分类桥——角色-preset 通道按设计不使用。 |
| rolePresets | Record<string, string> | 未设置 | mstar 角色 id → 已部署的 agent preset id;通道的唯一载荷来源(选中的 preset 拥有子代理的 persona、工具与技能)。 |
Service
apply 构造 ctx.dshMstar(engine 支撑:validateStatus、validateResidual、findingsCleanupGate、resolveCompassEnforcement、resolveHarnessDir、readHarnessVersion、applyEnforcement)。分层:P1 各闸门是本包内与 engine 同置的包装器,直接导入 engine(同一插件,构建时打包 engine);ctx.dshMstar 是供 inject 消费者使用的组合/测试外观;宿主适配器(下节)是面向宿主的门面。两条路径共用 engine 这唯一语法源。伴随入口 @mstar-harness/dsh/invariant 以文档化的空安装器保留包所有权。
Host adapter
插件以 DshHostAdapter 实现 engine HostAdapter 契约(host: 'dsh'),并以 ctx.dshHostAdapter 服务暴露。检测:engine detectHost 将 dsh 委派工具名——ToolSignal subagent(模型可见的 dsh subagent 工具)——映射为 'dsh',在 omp 之后、kimi/zcode/codex 之前求值;混合会话按固定顺序让位于更早的行。适配器与插件内闸门共用同一套校验核心(单一代码路径):beforeStatusWrite(path, doc) 在宿主提供文档时校验传入文档,否则走磁盘文档回退(文件缺失 = 首次创建 = 放行);beforeDispatch(assignment) 运行字段 + 分支 + 反递归闸门并携带强制执行后的 hardBlocked 判定(租约闸门留在监听器侧——它绑定该钩子不携带的 ToolExecution 会话上下文);beforeMerge(lease) 是 engine validateIntegrationMergeLease 的薄包装(向 status.json 的预留写入是 P3 seam)。log 默认路由到 dsh ctx 日志器 mstar/host-adapter。
dsh 的冻结技能根形态(engine resolveSkillRoot('dsh', …))为 $DSH_BUNDLED_SKILL_DIR/<name>[/<rel>]——解析器只定义技能相对路径解析器(resolveAssetPath)所用的规范形态;它不挂载目录。挂载由插件负责(下一节)。
Skills mount
mstar 技能通过 dsh skill-filesystem 提供者以单一规范挂载接入:插件把配置的根注册为一个提供者(providerName: 'mstar'、includeDefaultRoots: false——隔离,绝不看到宿主应用自身的项目/用户技能),上文的 engine 形态是共享的技能根契约。两条 Config 路径填充它:
| 路径 | 机制 | 时机 |
| --- | --- | --- |
| Bundled 默认 | bundledSkillDir 默认取包内自带的 harness-skills/ 镜像——仓库根 skills/(19 个 mstar-* + pm)由 bundle-assets 在构建时同步(gitignore),按包相对路径解析(非 cwd 锚定——任意启动 cwd 都可用) | 发布包 / 无覆盖的任何部署 |
| 自定义根 | skillRoots / 显式 bundledSkillDir → skill-filesystem customSkillDirs / bundledSkillDir 条目(显式值优先) | 本地开发 / 测试 / 使用不同镜像的部署 |
打包镜像是单一规范挂载:技能内容只在仓库根 skills/ 镜像中存一份并同步进包(与 opencode 的 harness-skills/ 相同),mstar 技能在任何地方都保持可独立使用。不重复加载:opencode 插件在自己的包里携带同一批技能,因此 dsh 只能通过这条 skill-filesystem 路径挂载它们。
开发期现实:@deepseek-ai/dsh-skill-filesystem 运行时是 peer stub(契约镜像的注册,无文件 watcher),因此挂载通过真实组合(stub + 实际镜像 skills/ 的 frontmatter,用 engine lintSkillFrontmatter 校验)验证;真实运行时组合(真实 seam 包、watcher、$DSH_BUNDLED_SKILL_DIR 环境变量流)是部署目标,不在本包测试套件覆盖内。
Commands
插件把 bundled 的 mstar 命令(omp/opencode 对齐面)注册到 ctx.commands:harness-commands/*.md——仓库根 commands/ 镜像(iteration-start、iteration-drive、iteration-loop、codebase-audit)由 bundle-assets 在构建时同步(gitignore)。每条注册读取命令的 name/description/input frontmatter;声明了 input hint 的注册会将其作为 input.hint 公布,使 dsh web 客户端的决策表从「脱离式裸执行」翻转为 leadingInput claim——菜单点选后把 /name 插入输入框(命令色 token + ghost hint),按 Enter 才提交,用户可以键入后续参数(与 /plan、/goal、/advisor 相同的交互)。handler 把命令正文以 USER source 消息 steer 进接收 agent(dsh-plan-mode 命令先例——source: { kind: 'user' },模型把正文当作要执行的任务而非注入上下文;即 dsh-commands 的「经接收 Agent 显式调度模型可见工作」路径),用户键入的参数以 ## User input 小节追加在正文后,返回成功结果。注册以 ctx.inject(['commands'], …) 延迟进行——与工具注册相同的可选单元模式——插件在无 commands 服务时也能启动;镜像缺失(未跑 bundle-assets)则不注册任何命令。
Native command surface
本宿主的可写 mstar 命令面是 MCP 路线:整个宿主共享一条 mstar-mcp 连接(即 bundle 行——见 § Shared MCP connection),每个命令家族一个工具(mcp__mstar__mstar_<family> + action 参数)。该连接自身不携带任何身份,但随发行的行以 --trust-caller-session-id opt-in,因此需要会话身份的操作在这条连接上传 sessionId(未 opt-in 的连接不发布选择器并拒绝传入的 id)。下列三个 dsh 原生工具只留给本 MCP 面做不到的事——写不了的插件本地状态,或没有发布的能力;那里身份来自 exec.agent.id。
成员规则——记住它而不是记清单:一个命令只有在满足下列之一时才是 dsh 原生工具
- 产出 MCP 路线写不了的 dsh 本地持久记录,或
- 提供 MCP 面根本没有发布的能力。
目前恰好有三个工具满足该规则:
mstar_plan_bind—— 规则 1。它既绑定引擎 coordinator 行,也持久化本插件自己的持久状态:control 根store.db中本会话的executionBinding槽,以及 workflow ledger 归因解析器与mstar_execution_clear读取的进程内采纳收据。形态:共享plan.bind参数按已发布 schema 机械镜像(身份来自exec.agent.id),引擎拒绝原样返回,已知叶席位在准入前 fail-closed 拒绝。MCPaction: "bind"路线只绑定引擎行、到此为止——两条路线并不等价。mstar_audit_validate—— 规则 2。评审报告凭据扫描(mstar-auditHard Rule 4,即mstar audit scaffold背后的校验器);mstar_audit家族只发布scaffold/secret-scan/supply-chain,都不校验已存在的报告文件。mstar_execution_clear—— 规则 2。它清除同一个 dsh 本地executionBinding槽,而 MCP 路线没有可承载的共享execution.clear命令。它完全不带参数:身份来自exec.agent.id,因此只能触达调用者自己的绑定,并保留用户的工作流选择与排除下限;已知叶席位在读取 store 之前 fail-closed 拒绝。其拒绝(execution.clear.no-harness/no-binding/store-refused)都点名原因与恢复路径。
其余运行时命令全部走 MCP——store.* 命令只在 CLI,不在此发布;旧的 mstar_<family>_<action> 工具名不会以别名复活:用家族 + action 调用,并用 mcp__mstar__mstar_schema(family: <name>,或单个叶子契约的 command: <command-id>)读取家族真实的 action 与字段。这个表面上的身份、准入与 fail-closed 叶席位拒绝遵循 ADR docs/adr/dsh-native-identity-channels.md。
Shared MCP connection(显式声明的 control 根)
bundle 补丁只插入一行 mstar-mcp,而这一行就是整个宿主的全部 MCP 通道:插件不自行注册任何 MCP client,不存在按 agent 挂载,subagent 共用父级已有的那一条连接。按 agent 的实例本来也拿不到按调用参数(PreToolDecision 没有改写参数的通道),因此它换不来任何能力——只会每个 agent 多一个 Node 进程。
control 根是「声明」出来的,绝不推断。 它按一条有序规则解析,启动 cwd 从不是候选——那个目录属于宿主,它说不出调用方属于哪个工作区,采用它会把写入落到不相干的项目上:
- 连接钉——
mstar mcp --harness <绝对路径>,否则服务器环境中非空的MSTAR_HARNESS_DIR; - 否则由被准入的这次调用通过工具的
harness字段指名根(绝对路径,或相对于服务器启动目录的路径); - 契约不读取任何根的无根命令类以空根运行——
harness.scaffold(建立根本身就是它的操作),以及schema、path.resolve、host.*、doctor与lint/skill.lint/qc.validate-report之类的只读校验器:它们只按显式输入作答,因此从不解析隐式 harness 路径; - 否则该调用以
[mcp.control-root-unresolved]被拒绝,并点名原因与全部恢复路径——两条启动方恢复路径对任何命令都成立,而按调用的harness选择器是依工具而定的(不发布该字段的命令无法接受它)。
连接钉优先于按调用的 harness,因此调用方绝不可能把某次调用钉在启动方未声明的工作区上;这次调用实际使用的那个根还会把调用工作区锚定在其父目录,因此由 cwd 派生的路径落在那份 harness 下,而不是启动 cwd。在未钉的连接上,凡是根不另有来源的工具,其声明的根选择器都是必填——用 harness 指明根的命令就是该字段,sdd.workspace 则是 controlRoot——模型无法漏掉解析所需的这唯一事实,tools/list 也绝不会把解析器拒绝省去的字段公布为可选。只读校验器(review.seats、worktree.qc-alignment、lint、skill.lint、roles.validate、design-md.validate、compound.validate、dispatch.validate、qc.validate-report)的相对路径参数按已声明的工作区解析,因此 --workspace /caller 读的是 /caller 下的文件,而不是宿主检出的;未声明工作区时相对路径一律被拒绝。
连接自身不携带身份;随发行的行信任调用方声明的 sessionId。 共享连接自身无身份,且仅属 MCP 的身份接线已移除:没有 --identity-env、没有已声明身份锁、没有选择器剥离,也没有连接级默认 session id。按调用的 sessionId 选择器只在启动方显式 opt-in(mstar mcp --trust-caller-session-id)的连接上发布;随发行的行会传这个开关,未 opt-in 的连接仍以 mcp.caller-identity-unavailable 拒绝传入的 id。在 opt-in 连接上该值是 bearer 身份:executionContextFor 把它变成 context.caller,引擎据此解析调用方自己的 coordinator 绑定,因此知道一个活跃会话的 id 就足以以它行事——授权仍是那条绑定行加上可选的 --expect CAS 令牌。需要会话身份的操作在本条连接上传 sessionId 运行——包括 plan progress、plan complete、workflow register/evidence/phase、session recover、workflow-note.append。上文列出的三个 dsh 原生工具只留给本 MCP 面做不到的事,那里身份是 exec.agent.id——宿主事实,绝不是模型提供的字段。未 opt-in 的连接没有调用方声明通道,那里才走原生工具或 CLI。
Engine seam mapping
每个 engine 模块都挂到一条 dsh 表面——除 lint 模块的 plan/tdd fs 闸门外均已交付(延后项;见 Known Limitations):
| Engine 模块 | dsh seam | 状态 |
|---|---|---|
| core(applyEnforcement、GateResult/Severity) | 跨切面否决/拒绝 | 已交付(P1) |
| path(resolveHarnessDir) | harness 目录探测 + {HARNESS_DIR}/status.json 目标匹配 | 已交付(P1) |
| status(validateStatus、validateResidual、findingsCleanupGate) | status.json 的 fs/write-intent + fs/edit-intent | 已交付(P1) |
| lease(verifyPlanExecutionLease、validateIntegrationMergeLease) | exec 租约:tools/pre-execute(派发闸门内);merge 租约:HostAdapter.beforeMerge | 已交付(P1 exec / P3 merge) |
| dispatch(composeDispatchGate、isReadOnlyAssignmentRole、parseAssignmentFields) | 作用于 subagent 工具的 tools/pre-execute(PreToolDecision.deny 阻断);agent/pre-step 咨询 | 已交付(P1) |
| host(resolveSkillRoot、HostAdapter) | engine host.ts 检测行 + 插件适配器(host: 'dsh') | 已交付(P2) |
| skill-authoring(lintFrontmatter、lintFiveQuestion) | skill-filesystem 根 + 对 SKILL.md 的 fs/write-intent | 已交付(P2) |
| lint(lintSkillFrontmatter、planQualityBar、assertSddTddTriple) | 未接线——plan/tdd 的 fs 闸门为延后项;lintSkillFrontmatter 仅运行于 skills-mount 测试套件 | 延后 |
| agent catalog | 生产者自有的 mstar-engine catalog source(锚点)+ /api/mstar/engineStatus 发射读取(模型可见 ⟺ 已记录) | 已交付(P2) |
| sdd(sddWorkspace、taskBrief) | MCP 家族 mstar_sdd(workspace / task-brief)——无 dsh 原生包装 | 已交付(P3) |
| iteration(evaluatePhaseGate、parseCompassFrontmatter) | agent/pre-step + iteration 闸门 | 已交付(P3) |
| worktree(l1PreDispatchCheck、l2PreDispatchCheck) | tools/pre-execute L1/L2(派发闸门内) | 已交付(P3) |
| design-md / audit / compound / roles | fs/write-intent seam + 唯一的原生 mstar_audit_validate;其余为 MCP 家族 | 已交付(P3) |
Engine-status catalog
一个咨询式 agent/pre-step 瀑布监听器向发射时的组合步骤追加一条 mstar-engine catalog MessageSource——生产者自有的 mstar-engine kind,即恰好 { kind: 'mstar-engine', form: 'catalog' }、不含任何其它成员(dsh 生产者自有 source 契约):模型可见的 <mstar_engine_status> 块渲染为 payload 的投影(renderEngineStatusCatalog):渲染为一条 delta 行——workflow: <id> (<kind>) | plans: <非 Done 行> | branch: <base> → <target> | gate: <判定> (<transition>)——只携带选中的生命周期、其非 Done plan 行、解析到的分支锚点,以及 steering compass 校验迭代时的相位闸门判定(FAIL 带违规码;裸判定不指出原因)。payload 里的其余事实刻意不进该行:residuals 汇总与其明细行、branch policy、spec 集成锚点、各 plan 行的 row scope(记录范围,绝不是所有权声明或租约)、knowledge 摘要、compass direction、agent-flow ledger、原先的 iteration/transition 历史以及水印三行。这些事实留在 MstarEngineStatusPayload(面板从已存发射渲染)与其背后的 engine 权威中——mstar status、mstar roadmap show、mstar plan show、compass 文档、{KNOWLEDGE_DIR} 与 dsh_status_emissions——该行只是变更提示,绝不是权威。水印仅存在于 payload:mstar 版本(插件自身清单;单一版本不变量把打包的 engine 钉在同一版本)、harness 目录(解析后的 {HARNESS_DIR},缺失为 none)与 enforcement(compass 模式,soft / hard (compass))留在 MstarEngineStatusPayload(面板 meta dock 渲染版本 + harness 目录),并在常驻 system prompt(mstar:engine-status 摘要 + mstar:harness-rules)中模型可见一次,故该行绝不重复它们。回退组合:只要 prompt 注册未被确认生效——宿主未提供 system-prompt 服务、异步注册失败(返回 true 仅表示 inject 子已排期)、或既有注册被释放——监听器逐步骤求值的 showWatermark 判定即让该行自行重渲染水印三行,版本 / harness 目录 / enforcement 事实因此不会丢失(随后才生效的注册会自动结束回退)。监听器先调用 next() 并基于委托后的决策追加——从不否决步骤、从不替换已组合的消息。未选中任何生命周期时,该行以 workflow selection: ERROR (<code>) <message> 取代状态行(此时的聚合按构造即空,绝不能被读成「无 plans」),该消息不再指向已退役的 status.json 路径——它指出原因并给出真实的恢复命令:plan 用 mstar workflow register、迭代用 mstar iteration register(为本会话从已注册生命周期中挑一个,是另一条出路)。模型可见 ⟺ 已记录:持久化的 source 不携带任何事实——该行是持久化的锚点,其实际发出的 payload 在同一次发射时按会话持久化为 control 根 store.db 表 dsh_status_emissions 的最新一行,因此「会话日志 + 该存储」无需重新解析该块即可重建该行(dsh packages/AGENTS.md)。dsh session-format V4 在消息准入处拒绝已退役的 { kind: 'plugin', plugin } 包装,故 source 上永远只写生产者自有的 kind。锚点 kind(更名,带读兼容):发出的 kind 为 mstar-engine(更名前为 mstar-engine-status);已发布构建发出的行在其 V3 会话日志中持久化旧的 plugin 包装,dsh V3→V4 迁移会把它提升为 plugin:mstar-engine(更名前标识则为 plugin:mstar-engine-status),面板的锚点读取方同时接受这些 kind——任何其它 kind 都不是锚点,会话降级为显式 waiting 态(绝不猜测数据)。工作流面板通过宿主共享的 /api typert 网关按需读取该已存发射:路由由宿主网关持有(插件无法注册路由;connection.rpc.handle 的普通信道会经由调用 fiber 上的 webServer 挂载、并使本行的 boot 挂起——本包刻意不走该机制),本包只经可选 ctx.typert.register(...) 子单元贡献 mstar/engineStatus 端点描述符。浏览器半体调用 connection.rpc.call('/api', 'mstar/engineStatus', { args: { sessionId, cwd } }),得到该会话自己的已存发射,或带原因的显式 unavailable(绝不返回其它会话的数据、绝不静默空字段)。信任边界,如实说明:该端点自身不做任何鉴权——传输层围栏(host/origin + 浏览器鉴权)只证明调用方是本宿主自己服务的页面,因此持有 (sessionId, cwd) 组合 + 能访问本机 /api 网关即为能力。应答按请求限定:查询以被断言的 id 为键,存储记录的 cwd 必须同时等于断言值与服务端解析出的会话 cwd,客户端在渲染前还会复核回显的 id 与工作区——因此一个会话的请求绝不会被返回另一个会话的记录。payload 是工作区摘要(版本、harness 目录、enforcement、plan/residual/row-scope 计数),面板自身的会话记录本就暴露这些信息;它不被当作机密,而不同的 unavailable 原因会让该端点对已持有 id 的调用方成为一个弱的「会话是否存在」探针。fiber 销毁即移除监听器(HMR 安全)。
发射是变更驱动、而非 turn 驱动:按会话+工作区,首次发射时追加,之后仅当渲染文本或面板相关 payload 投影变化时才追加(该投影会把 store revision 归一化,因为发射自身的 store 写入会推进它——直接比较原始 payload 会把这个副作用读成变化,从而每步都重发)——20 步的 turn 只显示一次 catalog,而不是 20 次,20 个不变 turn 的会话也不再每 turn 堆积一份相同副本。turn 开始本身从不触发,因此未解析到 {HARNESS_DIR} 就完全没有该行(否则该块会是空包裹),面板显示其 waiting 态。两个后果值得明说:发出的块是 payload 的投影(不渲染水印三行,见上),且持久化 payload 就在同一闸门写入——因此面板的已存发射是模型最后看到的 payload,其新鲜度页脚 turn / at 意为最后发射、而非当前 turn;只要没有发射,已存行仍是当前状态。仍保留一条显式重锚路径:picker 提交会驱逐该会话的 digest,故下一步即使文本未变也会重发。已知限制:该行不做周期性重锚,因此宿主侧上下文压缩之后它可能在下一次变化之前缺席——常驻 system prompt 摘要仍携带版本、活跃工作流与其未 Done 的 plans。source 共享同一按工作区缓存条目:显式 harnessDir 时在 boot 构建(否则在工作区首次 pre-step 构建),并按 TTL 刷新(catalogTtlMs,默认 60 秒)——刷新间隔之间热路径只是时间戳比较 + Map 命中,会话中 plan/compass/residual 的变化在一个间隔内落地。
Agent-flow ledger
agent-flow 账本位于现有 control-root store.db 的 host 域,不再使用 workflow JSONL 文件。catalog 和面板窗口只是有界读取投影,不裁剪存储的事件。ACTIVE 权威建立之前,dsh 原生会话日志继续记录,authority 持久化如实报告 unavailable/degraded,并提供 store init / store upgrade 恢复路径;绝不伪造成功或回退到旧文件。真实派发与结算证据仍为咨询式、try/catch 包裹:账本不可用不阻断派发或结算。
- 记录身份——已核实的原生 workflow 事件保留
{sessionId, streamId, seq}与wfe1:<kind>:<sessionId>:<streamId>:<seq>。流化身来自持久原生会话头部,绝不是 store epoch 或文件 mtime。实时工具调用行在两个 ID 均已知时可带wfc1:<sessionId>:<callId>:<kind>作关联;不伪造缺失 ID,实时行允许重复。 - 派发(dispatch)——每个 Assignment 形态的派发一行,记录派发身份:
role(在写入边界归一化——去首尾空白并剥掉一个前导@,故@explore与explore是同一 actor 而非两个角色)、planId、Assignment 的Task N标签记为taskId、taskCategory与判定。 - 结算(settle)——仅为真实配对的完成记录。前台调用在
tools/post-execute监听器结算;{ kind: 'background', jobId }结果按注册表jobId配对(jobs 注册表键,绝非子会话 id),待ctx.inject(['jobs'])→jobs.events.subscribe发出终态settled事件时结算(completed → ok/killed → denied/failed → error,可用时附durationMs)。每条配对结算都携带派发身份(Task N标签仍是taskId;taskRef专用于注册表 job id,仅后台结算携带)外加可选childId——前台runId,或 catalog 关联已提供的后台子会话 id。可选 id 缺失或超长时只省略该字段:真实完成照常记录,id 绝不截断、绝不重键。未配对载荷不记录任何东西——绝不伪造结算。 subagent-link(非终态)——从父会话自有的subagent/catalog事件观测子会话 ID,并在派发的 apply 作用域调用窗口内关联。它记录身份而非完成,不伪造outcome、verdict或paired;catalog 证据缺失时不产生 link 行。- 原生存储——事件、已接受来源身份、单调扫描下界、会话绑定与最新状态发射分别使用
dsh_ledger_events、dsh_accepted_identities、dsh_scan_bounds、dsh_session_bindings、dsh_status_emissions。不再有共享 JSONL tail/index/cursor/cache 权威、文件压缩、冻结或锁。旧文件字节保留为只读一次性 upgrade 输入/历史。
Workflow 行
同一账本从调用方父会话的原生日志中持久化的 tool-workflow/* 事件记录 workflow / ralph 扇出运行,而非内存中的 workflow/* emits。冷扫描与实时 session/event 监听器消费已核实的来源位置。事件追加、已接受身份/摘要检查与扫描下界推进在同一个原生事务内共同提交。
| tool-workflow/* 事件 | 账本行 | 字段 |
| --- | --- | --- |
| run-start | workflow-run | runId、name、agent?(承载的父会话 id) |
| agent-start | workflow-agent | runId、seq(1 起始的成员序号)、label、phase?、childId |
| run-end | workflow-run-end | runId、stopReason(completed / cancelled / error) |
tool-workflow/agent-end 是上游成员簿记,没有账本类型(成员 outcome 有意不持久化)。缺席的可选字段仍省略。既有展示字段限长与身份尺寸校验保留:超长展示值带标记截断,超长 runId / childId 整行跳过,绝不制造身份碰撞。读取窗口限制只是投影,不是存储截断。
第四种类型 workflow-verdict 由 workflow/ralph 闸门(而非本消费者)写入——每个被把关的调用一行(tool、workflow/objective、mode、判定 ok/advisory/denied/ask、违规 code)——见 Gates → Workflow / ralph gate。展示身份字段(workflow / objective)同样带 1024 字符上限;判定的违规码绝不猜测(P-a workflow.name.unknown vs P-b workflow.lease.uncovered)。
已接受来源回放。 已核实的原生 workflow 事件 ID 经数据库身份/摘要域只接受一次,覆盖冷热重叠与插件重启;同一已接受 ID 携不同字节会被拒绝而非覆写。扫描下界在已核实的流化身内单调推进。会话头部不可读时不接受事件,也不臆造化身。构造期带种子日志的会话在 session/created 上扫描;分叉从 inheritedEventCount 之后开始,保留父会话归属。实时 dispatch/settle/link/verdict 行允许重复,关联 ID 不使它们成为 accepted-operation 权威。缺失、staged、损坏或 schema 拒绝的 authority 如实保持 unavailable;真实的定址无记录仍与这些状态区分,并保留原生拒绝原因和恢复路径。
childId 关联 + 成员计数。 workflow-agent 行保留已发布成员的 childId(子会话 id);运行的展示 name 只存在于 workflow-run 行,面板为 agent/end 行经窗口查找解析(同一 runId——成员行本身不带名称)。面板把成员 COUNT 挂到 workflow-run 行(窗口内该 runId 的 workflow-agent 行数;窗口有界——被 ≤50 事件窗口截掉的成员如实缺席,绝不猜 0)。
深度咨询(观察时)。 agent-start 时,消费者经 sessions.get(childId) 解析子会话,当其 header.delegationDepth ≥ 2 时告警——每个运行至多一次(per-runId 闩锁),日志器 mstar/workflow-ledger。仅观察时,绝不是拒绝通道:子会话读取抛出只降级咨询本身,绝不影响行或运行。
零行为变更保证。 消费者仅观察:零门禁——每次读取与追加都 try/catch 包裹;账本写入失败绝不崩溃或改变 workflow 运行;会话读取抛出只记一条 warn 并继续。sessions 服务经 ctx.get('sessions') 结构化读取——对 @deepseek-ai/dsh-session 无运行时依赖。
挂载顺序说明。 消费者只在 apply 时 sessions 服务可用时激活——dsh-session 行必须先于 mstar 行挂载(标准 web profile 顺序即如此)。dsh-session 后于插件挂载(或缺失)的组合静默降级:一条 debug 日志(sessions service absent — workflow-ledger consumer disabled)且不记录任何行——绝不是错误,绝不是运行损坏。
面板可见性。 三种 workflow 行经现有事件行样式(无重设计)渲染在 事件记录(Event Log)tab 的 Agent 流转事件分区:摘要身份是运行 NAME(agent/end 行经窗口查找解析;回退 runId → 「未知」),详情体新增四个 workflow 字段——run-id / name / members / stop-reason(缺失 → 「—」)——expected/settled 席位渲染「—」(workflow 行不是角色派发——不存在 settle 配对,与 settle 行同先例)。未知 kind 字符串渲染为通用行(kind 原文、无 workflow 字段)——绝不丢弃、绝不猜测。catalog 摘要把 workflow 行计为独立 workflow 桶(模型行 by role: workflow N)——绝不并入派发角色计数。
Web 客户端插件(工作流面板)
本包为 dsh web profile 提供浏览器客户端半体,在已安装的 mstar bundle 行上被自动发现(package.json 的 dsh.client 声明 + exports["./client"] → dist/client.js——上游 web dsh.client 发现逻辑扫描 loader entries,并把每个客户端的 exports["./client"] 解析进 boot 图)——无需独立 profile 层或安装步骤(spec §6.1)。web 应用在 /plugins/@mstar-harness/dsh/client.js 提供该 bundle,并经 closure-factory loader 握手加载(window.__ModuleLoader__.load({ id, factory }))。
客户端入口经公开的两段式席位契约把面板注册为右 Sidebar 页签类型:ctx.sidebarRightTabs.register(definition)——id: '@mstar-harness/dsh'、kind: 'mstar-workflow'(页类型——无 patterns/canOpen/priority,按 kind 打开)、跟随 locale 的 title() thunk,以及恰好一个引导页 capsule(order: 20,描述 + MStar glyph 图标)——点选 capsule 会在该 pane 的槽位打开面板(替换引导页签、展开侧栏),再次点选聚焦既有页签(宿主页去重规则)——不自动打开、不重复表面。旧 conversation.view view-ring 页签已移除(迁移,而非第二表面):sidebar 页签取代会话区页签。面板体与其 chip 标题在同一 id 下注册为 keyed 席位(sidebar.right.pane.tab + sidebar.right.pane.tab.title;chip 为 glyph + 打开时捕获的标题——不跟随会话中途的语言切换)。tab.visible === false(列折叠或同 pane 其它页签激活)时面板体不渲染任何内容(无投影、无 DOM),选中分区存放在按页签记录键控的 entry slot store 中,因此能在 docked 面板体卸载后存活。文案来自 mstar-panel locale 命名空间:"晨星工作流"(zh)/ "Morning Star Workflow"(en)。
面板是最新 catalog 快照(取自宿主 /api/mstar/engineStatus 端点——刷新跟随拉取,不轮询)的纯渲染:窄列布局为绑定 sidebar pane 确定高度的单列 flex,恰好三个分区——分区导航(任务迭代 / 代理执行 / 事件记录,flex: none)、面板自有的唯一滚动主体([data-mstar-scroll]——面板内唯一 overflow-y 元素;任何元素都不横向滚动;data-mstar-graph 随其上)、钉住的 meta 小面板(版本 + harness 目录;永不滚动)。工作区状态摘要(计划 ≤5 时间倒序 + +N more、未决残留 findings ≤10 带 severity chip、策略 enforcement 首位 + push / worktree / control worktree、租约、知识、方向)以流内内容位于滚动主体末尾——固定 300px 兄弟列及其嵌套滚动器已删除——新鲜度 footer(last-updated HH:MM:SS + turn)收尾滚动主体。宿主 composer-overlay opt-in 不再需要(sidebar pane 体本就是确定高度盒),过时的视口媒体查询已删除:壳层携带 container-type: inline-size,宽度规则为容器查询——容器宽 480px 以下内距与分组间距收紧(300px 下限的紧凑节奏);≥720px 共享分组网格铺开为两列(repeat(auto-fit, minmax(280px, 1fr)))——这是任何宽度带来的唯一结构变化(一份 DOM、一棵树;无 JS 布局测量)。缺失字段显式降级为空态/最后已知态(muted,绝无橙色 warn 框);waiting 保留 muted hint;无 harness 时渲染居中未激活态卡片,检测到后自动呈现。
三个分区在唯一滚动主体内堆叠。任务迭代渲染迭代头部(未激活时为折叠摘要)+ 竖直 5 步 stepper(每步一行——徽标 · 相位 · chip · 预留 verdict 席位;保留四态 current / next / done / idle 状态机:current 之前的步骤投影为 done「已完成」,next 为唯一前向目标,idle 仅为 schema 余项;steering compass 为 status: active 时 Step 1 即当前步——无 PASS/FAIL 徽标——Step 5 永远不会是当前步)+ 分支面板(迭代 base / 目标 / spec 集成分支,仅激活时渲染)+ 计划板为五个竖排状态组(恒定顺序——Todo / InProgress / InReview / Done / 合并的「受阻/未知」/「Blocked / Unknown」列),每 data-kanban-column / data-kanban-arrow / data-kanban-count 锚点全保留;每组渲染行数以 PLAN_CAP 封顶,超限显示可点击「更多」/「收起」展开按钮(data-kanban-more)展开全量(投影保留全部 plan 行——封顶只是渲染关注点,绝不丢弃)——随后是项目 rollup。事件记录把两个分区(Agent 流转事件 / 违规记录)渲染为流内容——每条可展开原生 <details> 呈现完整 catalog 字段(缺失显示「—」,绝不捏造;workflow 行保留 name / 成员数 / stop-reason 字段),无分区自有滚动器。代理执行把 agents 渲染为竖直分组列表——绝对定位画布连同 SVG 连线层、卡片端口与指针 pan 已删除:两个 Phase 组恒定顺序,Phase 1(iteration-start——顺序完成的 Review & Edit 链:product-manager → architect → writing-specialist)在上,Phase 2(autonomous-execute——循环迭代 plans;其组标签标注当前 plan:投影 activePlanId(data-agent-group-plan),多个进行中 → +N more(data-agent-group-plan-more),无 → 灰字「无进行中 plan」(data-agent-group-no-plan))在下;每个 Phase 组竖排其 stage 组——sdd-implement 拆为上方 implementor 分区(flow 角色 fullstack-dev / fullstack-dev-2 / frontend-dev 按 flow 序,随后 on-demand 角色 ops-engineer / prompt-engineer,带虚线按需徽标)与下方 reviewer 分区(code-reviewer),general 桶下沉到最后一个 stage 组底部的 unknown 子桶、仅有成员时渲染;每个 stage 组把完整 14 角色 roster 渲染为全宽流行(flow row)(idle 行虚线 muted——roster 永不隐藏),带 role chip / 状态点 / ×N 计数 / 记录行(session id · 任务标签——辅助字段,绝不作标题),头部为 N 执行中 · M 待执行 摘要(data-agent-summary-*),三态图例以流内形式位于列表下方。行携带投影透明度分级:emphasis: 'current' | 'next' | 'off' | null——当前阶段角色 100% chrome 强度、后续阶段预期角色 75%、已过阶段 / 无阶段角色(on-demand、general)45%,null(无迭代 / transition 不可解析)不覆盖——恒为 chrome alpha 混合(--mstar-canvas-emphasis-* token;绝不整行 opacity,状态点与 running 辉光保持不透明)。已结算实体带独立绿色完成框 + 绿 ✓(data-agent-done="true"——圆角行体 success 边框 + 1px ring + 状态点绿 ✓,全强度 evidence 态)——仅当 emphasis ≠ 'off';off 档角色显示灰字圆点、绝不显示完成标记。代理执行页含零 <svg> 元素、零 data-agent-port / data-canvas-* 锚点、无 pan 变换。派发证据仍只投影当前迭代的 plans(steering compass iterationId 活跃时取之,否则经 catalog plans[].iterationRefs 推导最近迭代);可证明跨迭代的事件不产生实体——roster 常驻 idle 卡,无 plan / 未知 plan / 独立 plan 的派发绝不隐藏。状态诚实:advisory 不再终态——软执法派发落到配对 settle(有 settle 绿 ✓ 已完成、无 settle 显示执行中),denied 仍为终态,advisory verdict 仍在事件记录页呈现。迭代信息 Section 由任务迭代与代理执行两页共用——单一 IterationInfoSection 组件,两页渲染同一 view.iteration 块(摘要 + steps + 分支)。投影为纯函数 projectGraph(source)(schema 常量与 catalog 证据严格分离;永不 throw)。
依赖:面板客户端 bundle 不携带任何图库——@xyflow/react devDependency(此前构建期内联进 dist/client.js)随 react-flow 渲染层一并移除,唯一消费者是 @xyflow/react/dist/style.css 的 plain-.css text loader 也已删除(CLIENT_EXTERNALS 不变——react / react-dom 与 @deepseek-ai/dsh-client-* 平台模块保持外部)。构建脚本端到端断言移除成立:产物不得含 xyflow/reactflow 标记、@deepseek-ai/* 值导入为 0、无 import.meta / ESM 语句——web loader 以经典 <script> 执行插件 bundle,字面 import.meta 是 parse-time SyntaxError(zustand v4 的 import.meta.env 读取已在构建期 define 消除;见本迭代 install-verification guide §6)。当前 bundle 体积:145,159 B raw / 29,460 B gzip(以各迭代 install-verification guide 为重新测量 SSOT——react-flow 移除后缩至约 85 KB,代理执行页实体渲染落地后回升,透明度分级样式后再度增长)。
安装 / 验证(客户端半体与服务器半体走同一条 bundle 行安装):
cd <repo>/packages/dsh
bun run build # dist/client.js(closure-factory CJS)+ dist/client.d.ts
# corepack 机器(仓库根声明 packageManager: bun):命令前加 COREPACK_ENABLE_PROJECT_SPEC=0
dsh plugin --profile web add <abs packages/dsh path> # 同一 profile bundle 安装
dsh web # 启动 → 服务 /plugins/@mstar-harness/dsh/client.js本地已验证(install-verification guide):boot 图包含客户端 entry(@mstar-harness/dsh 携声明的 inject 面)、/plugins/<id>/client.js 路由服务的正是构建产物(rev = 内容 sha1)、浏览器握手 materialize 出插件入口(inject + apply + CSS 注入,经典脚本语义)——见 panel-beautify 迭代的 install-verification.md guide(本地 harness root)。
Known Limitations(本迭代):迭代 stepper 的 Step 1(iteration-start)在 steering compass status: active(Phase 1 进行中——catalog compassStatus 字段)时为当前步,且不携带 PASS/FAIL 徽标(Phase 1 无 gate 判定);Step 5(merge-ready)永远不会是当前步——engine 相位门只评估 Phase 2→3→4(merge-ready 从不是 gate transition);仅当 Step 4(pr-delivery)为当前步时渲染 next,其余为 idle;当前步跟随 TTL 刷新的 compassStatus——会话中途 active→locked 翻转后最多落后一个 catalog 间隔(60 秒)(有界、已记录的陈旧;绝不给出错误判定);代理实体状态按精确配对派生(paired settle 携带的 (agent, role, planId, taskId) 标识精确配对到对应派发——QC tri N=3 并发下各卡各自结算;未配对派发保持 running,绝不捏造);无 steering compass 时的「当前迭代」过滤按 plan id(8 位日期前缀)+ doneAt 推导迭代——确定性、已记录的启发式,且只丢弃可证明跨迭代的事件);不回溯 resumed 长日志的历史行(该行按变更发射、而非每 turn 首步:resumed 会话在 app 重启后的首个组合步骤会再次显示锚点——digest 在内存中——此后仅在行变化时才显示);sidebar chip 标题在打开时捕获(会话中途切换语言不会翻转它——为与一方 chip 的 glyph 形态对齐而接受);列折叠或同 pane 其它页签激活时(tab.visible === false)面板体不渲染任何内容(无投影、无 DOM、隐藏期零快照开销)。面板验收为双轨:in-loop 浏览器 harness 验证(对重建 bundle)+ 用户重启后 GUI 终验——重跑步骤见 install-verification guide §8。
Development
命令(在 packages/dsh 下执行):覆盖率门禁为 src/ 逐文件 100%(dsh 测试策略);构建命令把 src 条目 bun 打包进 dist/(内联 engine 与 schemastery;@deepseek-ai/cordis 与运行时 seam 导入——@deepseek-ai/dsh-skill-filesystem、@deepseek-ai/dsh-tools(defineTool)、@deepseek-ai/dsh-llm——保持外部),运行 build-client(scripts/build-client-bundle.ts——按 spec §6.2 产出的 closure-factory CJS 浏览器 bundle dist/client.js)并输出 tsc 声明。
bun test --coverage
bunx tsc --noEmit
bun run buildbun run test 会先构建客户端 bundle(pretest 钩子执行 build-client——manifest-contract 套件断言 dist/client.js 存在);fresh checkout 下直接 bun test 会以 bun run build 提示失败而非裸断言。
开发期 seam 表面(类型、事件形态、运行时)是来自公开 npm registry(registry.npmjs.org,无需根 .npmrc)的真实 @deepseek-ai/dsh-* 包(bun 默认自动安装 peer)。
Model Experience
Request surface and condition
What the model sees
组合步骤在该行被发射时携带一条 mstar-engine catalog 用户消息(<mstar_engine_status> 块——见 Engine-status catalog 一节):首次发射与变化时发射,绝不仅因 turn 开始,未解析到 {HARNESS_DIR} 的工作区则完全没有。该块是 payload 的投影,故其中不重复版本 / harness 目录 / enforcement 水印——常驻 system prompt 已携带它们(未提供 system-prompt 服务的组合则由该行自行携带这三行)。闸门决策额外添加:派发否决以注册表物化的 PreToolDecision { kind: 'deny', reason } 错误呈现;状态闸门以 mstar/status-gate 咨询呈现(告警放行、hard 修复逃生或降级放行);派发闸门以 mstar/dispatch-gate 咨询呈现(告警放行或降级);技能 lint 闸门以 mstar/skill-lint 咨询呈现(告警放行、hard 修复逃生或降级放行)。每条模型可见的行都能从会话日志重建(catalog 形态 source + 咨询事件)。
Leaf 交付纪律(PM 2026-08-12):leaf 子代理在最终(closing)消息中交回完成报告,不通过 report 工具交付——dsh tool-subagent-report 默认 reportDelivery: quiet 会把报告投递到父代理的 next-step 队列,父代理 turn 结束(无 step 边界)后报告会滞留队列。closing 消息是可靠交付通道;report 保留给「中途发现、需要父代理改变下一步」的发现(SSOT:skills/mstar-host/references/dsh.md → PM dispatch)。
Token effect
catalog 在该行被发射时追加一条固定、稳定的用户消息(小的常量块——不随会话长度增长:按会话的发射摘要会抑制未变化的行,故长 idle 会话不堆积副本)。否决与咨询文本仅在闸门触发时存在。
KV Cache effect
catalog 行在委托之后追加到组合步骤消息的末尾——请求前缀(system prompt 与先前消息)不受影响,因此插件既不创建也不失效前缀缓存状态;已发射的行在未变化的工作区上逐字节相同,而未变化的行根本不会再次追加(变更驱动发射)。工具错误文本随违规变化,但从不参与请求前缀。
Known Limitations and Deferred Work
- 开发期 seam 从 npm registry 解析 ——
@deepseek-ai/dsh-*各 seam 仅为 peerDependencies(运行时由宿主提供);开发期 typecheck/测试/构建从公开 npm registry(registry.npmjs.org,无需根.npmrc)解析(bun 默认自动安装 peer)。全部运行时 seam 导入在构建时外部化(--external @deepseek-ai/cordis / @deepseek-ai/dsh-skill-filesystem / @deepseek-ai/dsh-tools / @deepseek-ai/dsh-llm——发布的dist/导入它们而非内联占位代码);闸门通过真实注册表/fs 工具执行的同一ctx.waterfall派发来验证。本包套件直接运行来自 npm registry 的真实 seam 包——不再有提交的peer-stubs/占位、不再有本地 link farm。 - 反递归绑定是根会话身份,永不被继承 —— dsh 在工具执行上下文上不暴露每 agent 角色,故
dispatchBinding声明派发方根会话的角色(可在插件配置页实时编辑);未设置时根会话读作project-manager。子会话(以及闸门读不到会话的路径)无论该字段写什么都保持空并 fail-closed,因此叶子无法靠配置把自己塞进协调者席位。Execute as不同的 Assignment 无法被识别为自我递归,多角色派发方需要按实例拆分插件,而非根会话的协调者无法用该字段承载(该字段只对根会话调用方读取)。读不到会话的路径(无 exec 的beforeDispatch宿主钩子)只能由不提供调用会话的宿主钩子触达——本仓库内只有测试驱动它;而每一条生产派发路径都经tools/pre-execute监听器携带调用方 agent,故该拒绝在生产派发路径上从不触发。接入此类钩子的嵌入方必须在派发前解析出会话,该字段不是它的恢复手段。根会话判定信任session.header.parentSession:不为子会话标记该字段的宿主 provider 会被读作根会话,从而获得协调者身份。 - 租约闸门有意与 opencode 分叉 —— opencode 的
beforeDispatch不运行租约检查;dsh 租约闸门是新增的(lease.dispatch.*码),且仅对可写 SDD/InProgress 派发触发,故对齐覆盖字段集而非租约面。 - 已采纳 engine 共享组合 —— 派发闸门核心即 engine 的单一
composeDispatchGate(与 opencode/omp/CLI 对齐,字段/分支/反递归违规码按构造即相同),compass frontmatter 解析器即 engine 的共享parseCompassFrontmatter(本地 dsh 镜像与 CLI 副本均已删除——不再有可漂移的分叉)。两者都运行在 dsh 头区域切片上;租约 + worktree L1/L2 检查仍为叠加上去的 dsh 侧扩展。 - engine 单一版本钉定 ——
@mstar-harness/engine为精确2.1.1devDependency,构建时打入dist/(绝非运行时依赖);readHarnessVersion()读取 bundle 旁的 dsh 包清单 ——2.1.1,按单一版本不变量与钉定的 engine 相等。 - Schemastery 空数组物化 —— 省略的可选 ARRAY Config 键会物化为
[];派发键通过.default(undefined)保留省略语义,未来任何可选数组键都必须同样处理。 - 载荷边界 —— 派发闸门校验委派载荷(Assignment 文本),而非子代理的运行时行为;如需向模型可见的子活动建面,事后经
subagent/start观察仍为可选项。 - 状态闸门因 seam 设计而内容盲——
fs/write-intent/fs/edit-intent瀑布链只携带(target, actor),从不携带写入内容,因此首次把合法status.json写坏的写入在两种模式下都会通过(闸门只校验写入前的磁盘文档)。hard 模式因此从不否决状态写入:对已非法文档按修复逃生放行(error 级咨询,hard: true, repair: true),让修复性写入能落地。恢复路径:就地修复文档(闸门允许)或删除status.json让 harness 重建;hard 部署应监控repair: true咨询。 - 缺失
status.json的租约行为——sdd 可写派发遇到缺失状态文件会发出lease.dispatch.unverifiable(告警下 advisory,hard 下 deny);非 SDD 派发无租约义务,保持静默降级放行。 - 闸门匹配跟随
displayPath——状态闸门按 fs target 的解析后displayPath匹配。后端报告工作区相对路径、harness 目录为符号链接、或远程/URI target 时永不匹配,闸门对其惰性(无误报);受守护的 harness 写入请使用绝对本地路径。 - design-md seam 作用域为全局 basename 匹配——
isSeamTarget('design-md')匹配文件系统上任意DESIGN.md/DESIGN.dark.md,无论解析出的{HARNESS_DIR}/ 仓库根是什么。因此对不遵循 mstar token 格式的外部项目 DESIGN.md 的写入,在 hard 模式下会在 harness 之外记录 error 级修复逃生咨询(hard: true, repair: true)——一个嘈杂的误报面(写入从不被阻断)。有意为之(「工件即文件本身,无论设计位于何处」);在 harness 目录可解析时把作用域收窄到仓库根是可能的后续项。 - audit seam 作用域需要有可解析的
{HARNESS_DIR}才够收敛——作用域是解析出的{REVIEW_DIR}({HARNESS_DIR}/reviews/,感知.mstarcreview_dir)下的 markdown;当 harness 目录完全无法解析时(事件不携带会话工作区、也未配置harnessDir),该 seam 退化为匹配路径中的reviews段,因此无关目录树的reviews/**.md在写入时也会被凭据扫描。仅咨询、从不阻断,且凭据扫描就是该 seam 的全部契约——评审产物不再携带计划 Status 块。 <root>/mstar-roles/SKILL.md上 skill-lint × roles seam 双重触发——当某个已配置技能根包含mstar-roles目录(开发期的仓库根镜像情形,以及发布形态的打包镜像)时,对mstar-roles/SKILL.md的一次写入会同时触发技能撰写 lint 闸门与 roles seam 闸门(hard 下两条咨询 / 两条修复逃生日志)。两个校验器都合理适用——双重 lint 仅为咨询,并非正确性破坏;「作用域互不重叠」的性质只在四个 seam 之间成立,不跨技能闸门。- 内容盲的 skill-lint 盲区——
fs/write-intent槽位只携带(target, actor):首次创建的传入内容不被 lint,合法→非法覆盖在监听器路径上无法检出(它只 lint 写入前的磁盘文档)。告警/hard 咨询只呈现已存在的磁盘违规——与状态闸门同类限制。 - 显式相对
bundledSkillDir覆盖锚定 cwd——skill-filesystem 对相对打包根按普通join()语义解析到 dsh 进程 cwd。插件的默认打包根是包内自带的harness-skills/镜像,按包相对路径解析(非 cwd 锚定——任意启动 cwd 都可用);只有显式的相对覆盖继承 cwd 锚定,因此覆盖默认的部署应在 profile 层传绝对路径(见bundle/README.md)。 - Bundled 镜像是构建期同步——
harness-skills/+harness-commands/由bundle-assets在构建时产出(仓库根skills/+commands/镜像;gitignore)。未跑bundle-assets的检出不挂载 bundled 技能、不注册命令(默认挂载惰性,而非报错)。 - profile-bundle 安装到
webprofile:registry 与 local checkout——dsh plugin --profile web add @mstar-harness/dsh(registry)与add <本地检出>为受支持途径,均走同一 pnpm + reconcile 机制(reconcile 步骤把@mstar-harness/dsh并入dsh.profile.bundles)。local checkout 需要先执行bun run build——本包没有prepare脚本(monorepo 显式构建各包),未构建的检出会装入空的dist/。 lintSkillWrite类型化否决尚未接入生产——传入文档分支的 hard 否决(SkillLintVetoError,码skill-lint.veto)已导出并测试覆盖,但尚无生产调用方:engineHostAdapter没有携带内容的技能写入钩子(只有beforeStatusWrite/beforeDispatch/beforeMerge),且 fs intent 槽位内容盲。接线随未来携带内容的钩子落地;在此之前监听器路径只通过修复逃生咨询执行(从不否决)。- CLI 宿主信号列表与 engine 联合一致——CLI 的
hostSignals列表(packages/commands/src/families/local.ts)接受与 engineToolSignal联合相同的 token,包括subagent与 Agent Teams 载体spawn_teammate/team_task_create;因此mstar host detect --signals subagent解析为dsh而不再被拒绝,未知 token 仍返回 usage 信封。 - 入口是
src/gates/*之上的模块索引——src/index.ts提供 gate 模块的具名导出,保留插件 manifest、单一 cordis augmentation 点、命令注册与apply()启动接线,不再冻结导出数量。入口必须仅使用具名导出,因为存在 default export 时 dsh Loader 会丢弃 namespace metadata;service 导出同时向消费者提供ctx.dshMstar的类型 augmentation。 - engine dsh 行待上游化——engine
host.ts的 dsh 改动(DetectResult、ToolSignal、resolveSkillRoot)位于 mstar-workflow engine 镜像,计划经用户授权的上游 PR 合入 mstar-harness;mstar-host技能镜像(§ Detect / § Resolve loaded skill root /references/dsh.md)随之一并更新。 - 迭代 stepper:Step 1 为 compass 驱动,Step 5 为 schema 驱动——工作流面板的 Step 1(iteration-start)在 steering compass
status: active(Phase 1 进行中)时为当前步(无 gate 判定 → 无 PASS/FAIL 徽标);Step 5(merge-ready)是 engine 闸门永不点亮为当前的 schema 常量(transition 只覆盖 Phase 2→3→4,merge-ready 从不是 gate transition);仅当 Step 4 为当前步时作为next渲染,其余为 idle——已记录于迭代 guide,非缺陷。完整面板限制清单见 Web 客户端插件一节。 dsh-llm-fallbacks为可选的开发期依赖——dsh 原生覆盖 subagent 定制,fallbacks 因此严格可选:src/对其零导入(运行时与类型——被消费面是本地结构镜像fallbacks-structural.ts,由探测的 exact-keys 漂移闸门 +typecheck:tests的 real → view 可赋值检查保持同步),package.json仅在devDependencies携带它(类型镜像 + 真实包测试 harness),dist/无导入也无类型引用(仅 1 处命名该包的字符串字面量——探测的 loader 条目匹配;建议日志写作fallbacks)。激活是单独显式安装(双命令契约),绝不传递;不再有--external护栏——未来的值导入必须按设计重新加入运行时依赖。- 本批次未交付角色→模型覆盖——把角色路由到 fallbacks
model(或经 fallbacks 规则路由 persona)需要改写启动请求上的子会话agentOptions,但启动请求选项由调用方控制(tool-subagent 自己的 Config;调用参数为cwd/description/prompt以及可选的 preset/route 字段,且深度冻结)。等待上游fallbacks-explicit-role-tool或 N-B1 systemPrompt 采纳(roadmap §10.4)。 - 角色身份通过 preset 选择交付——角色匹配的
startActivation设置SubagentStartRequest.agentPreset,由选中的 preset 组合子代理(persona 经@deepseek-ai/dsh-persona、工具、技能);该选择记入子会话日志并在 resume 时重放。dsh 0.2.1 移除了逐次启动的persona槽与start/startContinuable对,因此不再存在「只合入 persona」的路径,也不再有mstar:role-persona系统提示段:部署方为每个角色声明一份完整 preset。 - 角色-preset 选择与 fallbacks 无关——
dsh-llm-fallbacks只路由 LLM 失败;角色身份从不依赖它。未挂载 → 仍以单条交付 debug 日志选中同一 preset。不支持独立选择的提供者(capabilities.agentPreset为 false,或inheritsParentContext)与未挂载agentPresetsregistry 的组合会跳过选择并记录恰一条 contained debug 日志——通道绝不因此使 start 失败。 - fork 门禁仅默认开启;显式
dispatchTools可省略subagent_fork——自定义dispatchTools列表整体覆盖默认(既有重命名模式),因此自行声明列表的部署须包含subagent_fork才能继续门禁 fork 派发。 - preset 的 persona 文本由 dsh 渲染——
@deepseek-ai/dsh-persona提供deployment:persona-prefix/-suffix模板,dsh 的严格{{variable}}插值对与后文}}配对的{{(未知/畸形/未定义引用)在子会话提示组装时直接抛错。该约束如今属于 preset 作者(mstar 只原样传递 id);打包的harness-agents/persona 现在只喂给 fallbacks seeds 面,那里对含风险的默认值是告警并跳过、绝不抛错。 - fallbacks HMR 重挂后的 seeds 再收敛受 seeded-only preservation 设计边界限制——seed registry 是每次 apply 的内存态,纤程切换(HMR / 设置编辑)会丢弃它并让双方 declarer 从头重放。mstar re-declare 只合并保留已 seeded 的 id(seeded-only,设计使然),因此在 preset-last 提交顺序下,preset 行的 seeded 注记不会被 re-declare 恢复——它们在下一次 fallbacks apply 时恢复(上游 preset 自声明重新播种)。
llm-fallbacks服务消失时 advisory 一次性 latch 会重新武装(inject teardown),因此下一个决策点会重新收敛 mstar 侧;preset 侧是 seeded-only preservation 设计的有文档说明的暂时现象。 - 咨询跳过原因
no-persona亦涵盖 extraction 失败——在 extraction 期被拒绝的镜像默认(例如含{{...}}插值风险)会在合并的 declare-outcome 行中以no-persona呈现(extraction 在风险门之前就返回无可用的 persona);逐 id 的 extraction 诊断保留在 debug 通道(mstar/fallbacks-advisory/mstar/fallbacks-seeds)。
