DeepSeek 这次公开了一篇 Super-agent 设计文章和一个配套研究原型,两者都建立在此前已经开源的 DeepSeek Harness 之上。大多数转述把它们合并成了一个产品,而三者的边界恰恰是最值得看的部分。
原型是一个 MIT 许可、只面向单个 GitHub 仓库的研究实现。README 明确写着:它不是 DeepSeek 的正式产品,文章里的部分设计是未来方向,并没有在原型里实现。
真正可复用的不是「无限自治」,而是一套把人的注意力和授权当成稀缺资源的控制平面:确定性 gate、模型 assessment、不可变 mandate、ticket、assignment、持久化 outbox,以及先对账再重试。
这篇文章把这套控制平面翻译成 AI 图片/视频站的后端,并落成三份可直接使用的东西:媒体生成 harness、给 coding agent 的 SKILL.md,以及一份可评审的 YAML 契约。
研究边界 · 2026-11-02
先把三个东西分开:Harness、Super-agent、原型
2026 年 10 月 31 日,DeepSeek Harness 团队公开了一篇设计文章《Super-agent 设计探索:迈向更自主的 Agent 系统》,以及配套的研究原型 dsh-superagent-prototype;两者都建立在此前已经开源的 DeepSeek Harness 之上。大多数转述把它们合并成了一句「DeepSeek 发布了超级 Agent」。这三样东西的边界,恰恰是这篇文章最值得看的部分。
先把三个东西各自是什么说清楚。DeepSeek Harness 是底座:基于 Cordis 的「一切皆插件」架构,可以作为桌面应用,也可以用代码启动成网页;它提供会话、工具、插件、自动化任务和工作区。Super-agent 是文章提出的一种系统形态:跨会话保持上下文、并行推进多项任务、持续观察环境、长期在线。原型则是这套形态的一个很窄的参考实现:只面向单个 GitHub 仓库,可选接飞书,MIT 许可。
| 东西 | 是什么 | 不是什么 |
|---|---|---|
| DeepSeek Harness | 开源的 agent harness,插件化,桌面/网页都能跑;提供 Session、工具、技能、自动化和工作区。 | 不是 Super-agent 本身,也不等于「多 Agent 框架」。 |
dsh-superagent-prototype | 面向单个 GitHub 仓库的主动式 coding agent,作为 DSH bundle 运行;每个工作项独立 Session 和目录,结果回到原对话,例如 PR。 | 不是官方产品。README 第一段就写明是 research prototype,为公开发布做过适配,不等同于内部版本。 |
| 设计文章 | 一篇架构探讨:Router/Worker 双层、事件驱动、异步交互、计算/状态/环境分离、Watchdog。 | 不是产品文档,也不是原型的完整规格;文章里部分设计是未来方向。 |
如果你还没看过 Harness 本身,可以先读我之前的两篇拆解:一个请求的一生 和 八道防线;这一篇不重复那些内容,专注 Super-agent 设计与新原型。
取证说明:设计文章原文发在 DeepSeek Harness 团队公众号,原链接无法稳定抓取。我读的是机器之心的全文转载,并把每个机制回到原型的 docs/ 与 src/ 交叉核对。原型检查于 commit 6337fc8(2026-10-31):src/ 下约 92 个 .ts、18 个 .tsx,tests/ 下 65 个文件,另有 9 篇设计文档、15 份提示词。文章与代码不一致的地方,下文会明确标出。
问题不是「能不能做」,是「人的注意力不够」
DeepSeek 的观察很具体:一个人通常只能同时跟进 3 到 5 个活跃 Session。模型越强,瓶颈越从「模型能不能完成任务」转向「人能不能记住、切换、验收这些任务」。他们把这种新形态拆成四个特征:
连续
用户可以在 IM、GitHub 评论等熟悉界面里交互,感觉始终只有一个 Agent;先在群里定方案 A,后在 PR 里改成方案 B,它知道这是同一件事。
高效
交互响应快,同时并行推进多个复杂任务;长任务不阻塞下一条指令。
主动
不等用户逐条派活,持续观察环境事件;值得处理就推进,不需要介入时保持安静。
持久
长期在线,接入真实工作环境;异常后能恢复,而不是会话结束就消失。
文章里有一句话值得单独抄下来:Session 应当是系统的实现细节,而不应是用户的认知负担。 这句话解释了为什么它要做 Router/Worker 双层:不是为了让 Agent 看起来更忙,而是把「维护上下文」和「并发执行」从人的工作记忆里拿走。
也正因为如此,这套设计的第一原则不是「更自主」,而是:占用尽可能少的人类注意力,交付尽可能多的价值。 后面所有的 gate、mandate、ticket、panel,都是这条原则的工程化。
五个模块:它到底怎么运转
1. Router / Worker:一个负责连续,一个负责吞吐
Router 层是常驻的全局路由层,承接所有入口的人类交互,维护全局上下文,做任务分派。它不直接执行具体任务,甚至可以不配 Bash 工具。Worker 层上下文隔离、挂完整执行工具,每个 Worker 专注一个任务;它不直接和人类说话,而是把状态和结果汇报给 Router,由 Router 统一对外回复。
单个 Router Session 为什么不是吞吐瓶颈?因为上一轮推理期间到达的 Trigger 会积压,下一轮合并处理;Router 每步通常是长输入、短输出,多条 Trigger 合并进一次调用后,Prefill 阶段并行计算,单次请求耗时的增长远慢于批处理大小。文章的实验也验证了这一点:在每分钟 1 到 40 条 Trigger 的到达速率下,批处理把单个决定分摊的推理时间压了下来。
2. 事件驱动:Fast Path 先筛,Slow Path 再判断
环境事件是「高噪音、低触发率」的。如果每条消息都调用大模型,延迟和成本都不可接受。所以第一层是程序策略:来源、群组、用户/机器人白名单、是否点名,直接由代码判断;第二层才是模型,判断「这段话是不是一个要被实现的请求」。这就是 Fast Path / Slow Path。
过滤之后是路由。已经确定归属的后续事件,由程序直接送到订阅了该 Thread 的 Worker,不再经过 Router;只有尚未归属的新事件才回到 Router。文章的实验里,取消这条直通机制,Router 决策次数增加约 30%,读缓存 token 从 2323 万增加到 3177 万,增幅约 37%。
上下文组织用两个动作:Inline 把近期很可能用到的信息直接放进窗口,Spill 把超出预算或低频使用的完整内容留在窗口外,只保留引用或摘要。对象都带 ID,注入时内联下一跳关联对象的 ID,模型就能沿着引用图逐跳召回,这就是「链式召回」。
再往前一步是投机执行:Agent 判断某件事可能需要处理时,先在后台尝试;有结果再交付,没结果就保持安静。文章里的例子是群里有人报 bug,Agent 先在后台复现、找修复方案,确认问题且修复简单后,才提交 PR;否则全程不说话。
3. 面向人的交互:全双工与多信道
经典 ReAct Loop 是半双工的:模型发起一次同步工具调用,循环阻塞到工具返回,期间无法响应新输入。Super-agent 把耗时命令改成异步的 start() / stop():start() 立即返回「已启动」,后台完成后把结果作为一条新事件投递回会话,前台继续响应人类。文章明确说,这个机制已经在 DeepSeek Harness 中实现,但尚未默认开启。
输出也要匹配信道。IM 是低打扰的轻量沟通,只在需要决策或验收时通知,单次回复不超过 100 词;GitHub、邮件、文档是异步高密度信道,按项目规范写清楚背景、改动和验证结果。Worker 的推理过程和工具调用不直接出现在外部信道,避免多人协作时信息过载。
4. 计算、状态、环境分离
本地 Agent 通常把三件事绑在一台机器上:推进 Agent Loop 的计算、需要持久化的会话状态、代码仓库和本地工具链。设备休眠或离线,Agent 就停了。Super-agent 分两步解耦:先把计算做成无状态容器、把 Session 状态放进数据库,让 Agent 摆脱单机生命周期;再让云端 Agent Loop 通过端侧一个不保存会话状态的轻量 daemon,按需访问留在原地的执行环境。这就是 BYOM(Bring Your Own Machine)的思路。
它解决的是「真实环境很难复制」:未提交的本地修改、私有凭据、内网服务、特定硬件和工具链,同步到云端成本很高,还容易漏。把执行环境留在原地,只把命令和结果传过去,比强行复制环境更现实。
5. Watchdog:主系统之外的独立控制回路
长期在线的系统很难只靠主系统内部逻辑保证可靠。文章在主系统之外放了一个独立的 Watchdog Session,用定时消息驱动,检查基础设施故障、状态恢复失败,也由模型判断各个 Worker 是否有实质进展、是否偏离目标。发现异常就介入修复,遇到偶发外部故障就持续复查。
这个设计有一个容易被忽略的副作用:Watchdog 本身也是一个有完整工具能力的 Agent Session,维护者可以用自然语言问它故障原因,也可以让它在线改提示词、调触发规则。它很强,但也意味着它必须被当成高权限组件来设计,而不是一个只读监控脚本。
原型真正实现了什么:一套「授权操作系统」
设计文章讲的是完整形态,原型只做了其中很窄的一段。但这一段恰好是最可复用的:它把「一次可以做」固化成数据结构,再把执行、重试、恢复、通知都挂在下面。
把这条链路拆成概念,原型的主表有 11 个:Source、Trigger、Thread、Correlation、Channel、Policy、Eval、Mandate、Ticket、Assignment、Subscription。下面只列和建站最相关的部分;其中 Decision 是 Eval 的输出,也单独列一行。
| 概念 | 原型里的定义 | 为什么重要 |
|---|---|---|
| Source | 一条事件流;保存读取位置和机器人身份,过滤自己的消息。 | 重启不能重放事件;来源决定「谁有资格在这里发起工作」。 |
| Trigger | 一个不可变事件,带来源自己的元数据,例如 actor。 | 审计和幂等的最小单位;同一个 Trigger 接两次只产生一个 Ticket。 |
| Thread | 按时间排列的 Trigger;来源决定哪些 Trigger 属于同一个 Thread。 | 给 Eval 一致的上下文视图;同一 Thread 同时最多一个 Eval。 |
| Eval | 对一批 Trigger 的判断,分 gate 和 assessment 两层。 | 决定「要不要开始新工作、继续、停止」;本身不持有状态。 |
| Decision | Eval 的输出:accept、stop、ignore、unavailable。 | 每个决定都能回放;失败也有明确去向。 |
| Mandate | 一次 accept 授予的东西:Policy 版本、范围、强度、匹配规则、发送者身份、来源接收方式。 | 不可变;范围要扩大就开新 Ticket。它回答「凭什么做、能到哪、谁批的、按哪版规则批的」。 |
| Ticket | 一个已授权的工作单元,有自己的生命周期。 | 只有 Eval 能创建;人 follow-up 可以直接进已有 Ticket,不重新 Eval。 |
| Assignment | Ticket 绑定到一个 Executor 实例。 | 一个 Ticket 可以有很多次 Assignment;重试不 fork 工单,也不 fork 账单。 |
| Subscription | Ticket 和 Thread 的多对多关系。 | 只提供上下文,不授予新权限;这是「上下文污染授权」的防线。 |
| Channel | Thread 的写侧:回复、PR、review 等。 | 结果回到请求发生的地方;消息按 Ticket + 会话保序。 |
| Policy | 版本化的评估规则、accept 模式、每个 Source 的白名单与准入表。 | 每张 Mandate 记录自己依据的版本;规则变化不会立刻吊销在途工作。 |
Gate 与 Assessment:默认答案是「不做」
Gate 是代码,管「这个人有没有资格让 Agent 干活」;Assessment 是模型,管「这段话算不算一个要被实现的请求」。原型里每个 Source 有一张准入表,三个格子的默认值很能说明取向:
| 情形 | 默认 | 含义 |
|---|---|---|
| 在白名单里,但没点名 | inferred | 只作为候选,不自动接单。 |
| 不在白名单,但 @ 了 Agent | ignore | 陌生人 @ 它,默认不理。 |
| 不在白名单,也没点名 | ignore | 直接忽略。 |
更关键的是 accept 模式:
| 模式 | 效果 | 适用阶段 |
|---|---|---|
off | 推断出来的候选一律不算。 | 完全关闭主动工作。 |
observe(默认) | 候选照常评估,结论是「我会接受」,但不真的接受,只记一条 would accept。 | 先看它会做什么,再决定是否放权。 |
auto | 评估通过就真的接单。 | 翻完 observe 记录、确认行为符合预期之后。 |
这个顺序和大多数团队的实际顺序相反:不是先开全自动、出事再关,而是先让它写「日记」,人看完再放权。默认配置下,这个 Agent 一件主动的活都不会干。
Mandate 与 Subscription:权限和上下文是两件事
这是我认为最值得抄的一条。一个 Ticket 可以订阅任意多个 Thread 来接收上下文;订阅到的消息直接送进执行会话,绕过 gate,但不授予任何新权限。Ticket 仍然受自己的 Mandate 限制。
这解决了一个很实际的问题:Agent 提了 PR,接下来有人在 PR 里讨论、CI 报红、reviewer 留意见——这些消息应该让 Agent 看到,但不该被当成「新的工作请求」重新走一遍审批。反过来,一条消息如果同时被多个 Ticket 订阅,它会扇出给所有活跃 Ticket;但「谁有权让 Agent 干活」永远只看 Mandate,不看订阅关系。
Mandate 只有三种操作会改变:accept 创建它,人明确 stop 撤回它,恢复前按当前 Policy 重新校验失败则吊销它。Policy 变化不会立刻取消正在跑的 Ticket,而是把变化作为一条 Input 投递进去,避免留下半成品。这个取舍很产品化:宁可让在途工作跑完,也不制造一次中断。
还有一个很克制的决定:「人」不是领域概念。 Trigger 只带来源给的 actor 标识;飞书白名单是飞书这个 Source 的 Policy,GitHub 白名单是 GitHub 的 Policy;同一个人在不同平台有不同账号这件事,落到团队提示词里,由 Agent 需要时自己读。代价是身份一致性交给自然语言,收益是不用维护一张永远会漂移的全局身份表。
执行与恢复:把失败当交接协议
每个 Ticket 一个执行 Session,命名是 worker-<ticket id>,一个独立目录;prepare 命令典型是 git worktree。前台 shell 命令约 5 秒后转后台,好让 worker 下一步就能收到新消息;后台任务每轮最多约 30 分钟,超时杀 shell job,但 subagent 可以继续。人工交接立即释放 worker slot;并发限制之外保留一个额外 slot 给人继续已有 Ticket,避免新任务把人工 follow-up 饿死。
Ticket 的阶段不是「运行中 / 完成」这么粗,而是按「现在在等谁」来分:person / delivery(消息投递失败)、person / answer(等回答)、person / review(等验收)、person / blocked, failed(受阻/失败)、working / queued(排队)、idle(等下一个 Trigger)、working。所有 person/* 都意味着「没有人的输入就走不下去」。面板首页先回答第一个问题:现在有没有东西需要我。
恢复机制也很具体:重启后,运行中的 Assignment 以「host restarted」结束,新的 Assignment 带一条 recovery note,要求先检查目录、git 历史、GitHub,再重复任何外部动作。所有外部副作用先写 outbox,commit 之后再执行;投递失败按 2 秒、4 秒、8 秒……退避到 1 分钟;Channel 消息按 Ticket + 会话保序。这些细节看起来不酷,但它们决定了系统能不能在崩溃后不重复扣费、不重复提交。
证据:12.4 分钟的背后,是一次调度实验
文章给了一组初步实验:约 300 条消息,其中约 20 条是工作请求,其余是闲聊、跟进、叫停和 CI 消息;编码工作用一个「只消耗时间」的工具代替,每种方案重复 5 次。它衡量的是调度与协作,不是代码生成质量。
| 方案 | p50 完成时间 | p95 完成时间 | 怎么读 |
|---|---|---|---|
| Super-agent | 12.4 分钟 | 15.7 分钟 | 双层 + 订阅直通 + 批处理。 |
| 单 Session + Subagent(中性提示) | — | 29.5 分钟 | 模型 5 次里一次都没调用 Subagent,所有工作都在主 Session 里被新消息打断。 |
| 单 Session + Subagent(优先委派提示) | — | 仍慢于 Super-agent | 5 次里 4 次委派,但仍有 1 次判断「工作很小」自己执行,形成长尾。 |
另一组数字是直通机制的价值:取消订阅直通后,任务完成时间与完整版基本一致,但 Router 需要做出的决定增加了约 30%,总读缓存 token 从 2323 万增加到 3177 万,增幅约 37%。也就是说,直通不提高完成质量或速度,但为 Router 省下了可观的 token 与推理开销。
不要把这组数字外推:它是合成负载、简化环境、单一团队、5 次重复;编码工具只消耗时间,不生成代码。它能说明「调度层值得单独设计」,不能说明「Super-agent 一定比单会话快 2 倍」,更不能说明它适合你的业务。真正的结论要你在自己的 provider、自己的失败率、自己的成本结构上跑出来。
文章 vs 原型:哪些是现在就能抄的
这是全文最重要的一张表。很多转述把文章里的未来设计写成了已实现功能;对建站的人来说,这会导致完全错误的预期。
| 设计 | 设计文章 | 原型 | 建议 |
|---|---|---|---|
| Router / Worker 双层 | 有 | 有:main Session + 每个 Ticket 的 Executor Session | 可以直接抄职责划分。 |
| 两级过滤(gate + assessment) | 有 | 有:代码 gate + 模型 assessment | 可以直接抄,尤其是默认 observe。 |
| 订阅直通 | 有 | 有:Subscription 扇出,绕过 gate | 可以直接抄,但必须同时抄「不授予权限」。 |
| Mandate / Policy 版本 | 有:授权与规则原则 | 有:不可变 Mandate,记录 Policy 版本 | 可以直接抄,这是最值钱的部分。 |
| 持久化 / 恢复 | 有:存算分离、长期在线 | 有:副作用先存后做,重启带 recovery note | 可以直接抄,媒体站尤其需要。 |
| 异常 / 待人工面板 | 有:少打扰、按信道输出 | 有:person/* 阶段优先展示 | 可以直接抄。 |
| 投机执行 | 有 | 未见完整实现 | 先别抄;视频全片渲染太贵。 |
| 异步 ReAct Loop | 有;DSH 已实现但未默认开启 | 依赖 DSH;原型只做了前台 shell 5 秒转后台 | 看 DSH 版本,别假设默认可用。 |
| Inline & Spill / 链式召回 | 有 | 部分:截断 + read pointer | 可以借鉴,但别声称原型已完整实现。 |
| 计算 / 状态 / 环境分离 | 有 | 未体现完整的云端/端侧分离;执行仍在 DSH 所在环境 | 建站时可以直接用云队列 + 对象存储 + 无状态 worker 实现。 |
| Watchdog | 有:独立 Session | 未见独立 Watchdog | 先做确定性检查,再考虑模型判断。 |
对 AI 视频/图片站的 9 条启发
下面是我认为可以直接迁移到媒体生成产品的部分。注意:这是我的翻译,不是 DeepSeek 的文章原文;DeepSeek 讨论的是通用 Agent 系统,不是视频生成。
- 把「生成任务」当工单,不要当聊天消息。 一次生成请求应该有一个不可变的 job id、一个授权范围、一个账单主体;聊天只是它的上下文,不是它的状态机。
- 钱必须是确定性 gate,不是模型判断。 余额、配额、模型/分辨率/时长白名单、单次成本上限、预审核,全部用代码做。模型只回答「这个 prompt 是否清楚、是否有政策风险」,不能决定价格或权限。
- 授权与上下文分离。 用户在同一个项目里补充「把背景换成海边」是上下文,可以直接进已有 job;但「从 720p 升到 4K、从 5 秒加到 10 秒」是扩大范围,必须重新报价、重新授权。聊天不能改预算。
- Ticket 与 Attempt 分离。 一个 job 可以有很多次 provider attempt;重试不 fork 订单、不 fork 账单、不换用户看到的 job id。每次 attempt 记录 provider、provider job id、请求 hash、原始响应。
- 先记账,再做副作用;重试前先对账。 先 reserve credits,再写 outbox,再提交 provider。provider 状态未知时,先查,不要重新提交;重启后先核对 provider,再决定 settle 还是 release。
- 每个任务独立工作区和资产前缀。 视频/图片站尤其要这样:临时文件、参考图、输出、缩略图都按 job id 隔离;provider 返回的 URL 不是存储,必须镜像到自己的 R2/S3,做 checksum、尺寸/时长校验和审核。
- 只暴露四种状态,异常才打扰人。 Working、Needs you、Ready、Failed。不要把 provider polling、重试日志、chain-of-thought 流到主界面;开发者轨迹放到单独的 trace 视图。
- 投机执行要降级。 DeepSeek 的投机执行假设后台算力相对便宜;视频全片渲染不是。可以投机做 prompt 清理、安全预检、成本估算、缩略图、480p 草稿;不要默认投机 1080p/4K 全片,更不要让它产生用户可见的扣费。
- 新 provider 先 observe 再 auto。 新模型/新渠道先 shadow:只报价、只记录「would route」,不真的提交;对比成本、延迟、失败率、人工验收质量,再按百分比放量。这就是把原型的 accept mode 用到 provider 路由上。
一张映射表:从 Agent 概念到媒体生成
| DeepSeek 概念 | 媒体站里的对应物 | 必须守住的规则 |
|---|---|---|
| Source | 网页上传、公开 API、Stripe、审核回调 | 按来源 event id 去重;先落库再处理。 |
| Trigger | 一次生成请求 / 支付回调 / 审核结果 | 不可变;客户端重试必须命中同一个 job。 |
| Thread | 项目、会话、素材血缘 | 提供上下文,不授予预算。 |
| Gate | 登录、额度、模型目录、分辨率/时长、成本上限、预审核 | 全部确定性;失败返回一个可行动的错误。 |
| Assessment | prompt 是否清楚、参考图是否合适、政策风险 | 只给建议;不能改价格、额度、权限。 |
| Decision | accept / clarify / reject / defer | clarify 一次只问一个具体问题。 |
| Mandate | 模型、分辨率、时长、最高成本、fallback 列表、版权范围 | 不可变;扩大范围 = 新 job + 新报价。 |
| Ticket | 一个可计费的生成 job | 拥有用户可见身份和账单主体。 |
| Assignment | 一次 provider attempt | 重试创建新 attempt,不 fork ticket。 |
| Executor | provider adapter / GPU worker | 独立工作区;只报告事实,不直接写 job 状态。 |
| Subscription | 项目/会话上下文 | 只给上下文,不扩权。 |
| Outbox | 扣款、退款、webhook、邮件、资产发布 | 先记录,commit 后执行;按 ticket 保序。 |
| Policy | 模型目录 + 价格表 + 安全规则 | 版本化;每个 quote 和 mandate 记录版本。 |
| Watchdog | 卡单、超预算、死循环、provider 故障 | 只能停/降级/释放,不能扩预算。 |
把结论落成一份 Skill
下面这份 SKILL.md 不是 DeepSeek 的原文,而是我把原型里可复用的控制面翻译到媒体生成场景后的产物。它给 coding agent 的不是「多一个角色」,而是一组建站时必须守住的约束:先授权再花钱、一个请求一个 ticket、上下文不扩权、重试不 fork 账单、先记账再做副作用、先对账再重试。
用法:复制到 ~/.codex/skills/media-generation-harness/SKILL.md,或者放到项目的 .agents/skills/。当任务涉及生成任务、provider adapter、credits/billing、retry/reconciliation、queue、moderation、asset delivery 时,它会触发。
展开完整 SKILL.md(可复制)
---
name: media-generation-harness
description: Design, build, or audit a durable backend for an AI image/video generation product. Use when the task involves generation jobs, provider adapters, credits and billing, retries and reconciliation, queues, moderation gates, asset delivery, or turning a prototype into a production media site.
---
# Media Generation Harness
Use this skill when building or reviewing the control plane behind an AI image
or video product. The goal is not "an agent that can call a model". The goal is
a system that spends as little user attention and money as possible per
delivered asset, and can prove what it did after a crash.
## When to use
- A generation request must survive restarts, provider timeouts and duplicate
webhooks.
- Users pay credits, so authorization, reservation, settlement and refund must
be auditable.
- More than one provider or model will be tried behind one product surface.
- The UI needs progress and failure states without streaming raw logs.
- A prototype works locally but cannot safely take production traffic.
Do not use this skill to design prompt content, model quality benchmarks or
creative direction. Those are separate jobs.
## Non-negotiable invariants
1. **Authorize before spend.** No provider call happens before ownership,
quota, price and policy checks pass.
2. **One immutable request, one ticket.** A client retry must resolve to the
same job, not a second charge.
3. **Deterministic gate before model judgement.** Auth, credits, catalog,
resolution, duration, rate limit and pre-moderation are code, not prompts.
4. **Context never expands scope.** A later chat message may add creative
context, but it cannot raise budget, resolution, duration or provider scope.
Scope changes need a new mandate.
5. **One ticket, many attempts.** A provider retry creates a new attempt under
the same job. Never fork billing or user-visible identity on retry.
6. **Persist before effect.** Reserve credits, write the outbox row and record
the provider request before performing the external action.
7. **Reconcile before retry.** If provider state is unknown, query it. Never
resubmit a job that may already be running.
8. **Exception-only UI.** Show "needs input", "ready to review" and "failed
with one action". Healthy work is quiet.
9. **The watchdog may stop or downgrade, never expand.** It cannot increase
budget, resolution, duration or permissions.
10. **Price, catalog and safety policy are versioned.** Every quote and mandate
records the version it relied on.
## Planes
| Plane | Responsibility | Must not do |
| --- | --- | --- |
| Control | intake, quote, gate, authorize, schedule, settle, notify | run GPU work |
| Execution | submit to provider, poll, download, validate, post-process | decide price or permission |
| Data | jobs, attempts, ledger, assets, events, policy versions | hide side effects in memory |
Treat the control plane as the only writer of job state. Workers report facts;
they do not mutate the database directly.
## Domain model
| Concept | Media-site meaning | Rule |
| --- | --- | --- |
| Source | Web upload, API, Stripe, moderation callback | Normalize and dedupe by source event id |
| Trigger | One immutable request or callback | Stored before processing |
| Thread | Project or conversation | Holds context; does not grant budget |
| Gate | Auth, credits, quota, catalog, cost ceiling, pre-moderation | Deterministic and synchronous |
| Assessment | Prompt clarity, reference suitability, policy risk | Advisory; cannot set price or permission |
| Decision | accept, clarify, reject, defer | `clarify` asks exactly one question |
| Mandate | Model, resolution, duration, max cost, fallback list, rights | Immutable; wider scope means a new mandate |
| Ticket | One billable generation job | Owns user-visible identity and ledger |
| Assignment | One provider attempt | Retries create a new assignment |
| Executor | Provider adapter or GPU worker | Isolated workspace and asset prefix |
| Subscription | Project/thread context | Context only; no new authority |
| Channel | SSE/WebSocket, webhook, email | Ordered per ticket |
| Outbox | Charge, refund, webhook, email, publish | Record first, send after commit |
| Policy | Model catalog, price table, safety rules | Versioned |
| Watchdog | Stuck, overbudget and loop detection | External control loop |
## Job state machine
```text
draft
-> quoted
-> authorized
-> queued
-> submitted
-> running
-> downloaded
-> validated
-> ready
-> delivered
any active state -> needs_input
any active state -> blocked
any active state -> failed
any active state -> canceled
quoted/authorized -> expired
```
Rules:
- `authorized` is the only state from which a provider may be called.
- `submitted` and `running` must carry a provider job id and the exact request
payload hash.
- `failed` must carry a normalized error code, not only a provider string.
- `canceled` must record whether the provider acknowledged cancellation.
- Terminal states are immutable except for append-only audit events.
## Money path
1. Quote from the current price policy version.
2. Reserve credits with an idempotency key derived from the ticket.
3. Commit the job and outbox row in one transaction.
4. Submit to the provider only after commit.
5. On definite submit failure, release the reserve.
6. On success, settle the actual amount; release the difference.
7. On failure, apply the published refund rule, not an ad-hoc decision.
8. Reconcile provider usage and ledger daily.
Never charge from a webhook handler without checking the stored job and
attempt. Webhooks are delivery mechanisms, not sources of truth.
## Provider adapter contract
```ts
export interface ProviderAdapter {
readonly id: string
quote(input: NormalizedRequest, policy: PolicyVersion): Promise<Quote>
submit(input: {
request: NormalizedRequest
attemptId: string
idempotencyKey: string
callbackUrl: string
}): Promise<{
providerJobId: string
acceptedAt: string
raw: unknown
}>
get(providerJobId: string): Promise<{
state: "queued" | "running" | "succeeded" | "failed" | "canceled" | "unknown"
progress?: number
output?: OutputRef[]
error?: { code: string; message: string; retryable: boolean }
raw: unknown
}>
cancel(providerJobId: string): Promise<{
accepted: boolean
state: "canceled" | "running" | "unknown"
}>
parseWebhook(headers: Headers, body: unknown): Promise<{
providerJobId: string
eventId: string
state: string
raw: unknown
}>
}
```
Adapters must normalize provider states, preserve raw payloads for audit and
never expose provider URLs as permanent assets.
## Minimum data model
- `generation_requests`: client idempotency key, owner, raw input hash.
- `quotes`: policy version, line items, currency, expiry.
- `jobs`: ticket id, mandate, state, current attempt id.
- `attempts`: provider, provider job id, request hash, response, timings.
- `credit_ledger`: append-only reserve, settle, release, refund entries.
- `outbox`: unique `(ticket_id, effect_type, effect_key)`, attempts, sent time.
- `provider_events`: unique `(provider, event_id)`, raw payload, processed time.
- `assets`: storage key, checksum, dimensions, duration, moderation result.
- `policy_versions`: catalog, price table, safety rules, effective time.
Use unique indexes for idempotency. A retry that violates one should return the
existing record, not an error the client retries again.
## Gates
Run these in order and stop at the first failure:
1. Authentication and ownership.
2. Request shape and size.
3. Model, resolution and duration allowed by the catalog.
4. Credit balance and per-user/per-IP quota.
5. Cost ceiling for this request.
6. Pre-moderation on prompt and reference assets.
7. Provider availability and fallback eligibility.
Return a stable error code and one actionable next step. Do not return a wall
of provider text.
## Assessment
Use a model only for genuinely semantic questions:
- Is the prompt specific enough to generate the requested asset?
- Is the reference image suitable for the requested edit?
- Does the request appear to violate a policy that the deterministic filter
cannot express?
The assessment returns `accept`, `clarify` or `reject`, with evidence. It never
sets price, credits, resolution or provider permission.
## Recovery
On startup:
1. Find jobs in `submitted` or `running`.
2. Query the provider for each known provider job id.
3. If the provider has a result, continue from that result.
4. If the provider has no record and the attempt is past its submit deadline,
mark the attempt failed and allow a new attempt.
5. Never resubmit solely because the local process restarted.
6. Release or settle reserves only after reconciliation.
Store enough provider request context to explain what was sent, but never store
provider credentials in job payloads or prompts.
## Watchdog
Start with deterministic checks; add a model only when the checks cannot
express the failure.
| Check | Action |
| --- | --- |
| Job older than p99 for its model | query provider, then cancel or escalate |
| No provider state change for N minutes | query, then retry with fallback |
| Cost exceeds mandate | stop, release reserve, alert |
| Same error three times | open a provider incident, stop retrying |
| Queue wait exceeds SLO | add capacity or degrade catalog |
| Duplicate webhook storm | dedupe, alert, do not create jobs |
The watchdog has a read-only view of policy and a narrow write set: cancel,
retry within the existing mandate, release reserve, open incident. It cannot
raise budget or permissions.
## Human attention budget
Expose only four user-facing states:
- **Working**: progress without logs.
- **Needs you**: one question, one input.
- **Ready**: preview, download, accept or regenerate.
- **Failed**: one plain-language reason and one next action.
Do not stream chain-of-thought, provider polling or worker logs. Put them
behind a developer trace view.
## Security
- Treat prompts, uploaded files, reference URLs and provider responses as
untrusted input.
- Never let user content change policy, price, budget or provider credentials.
- Keep provider keys server-side; never place them in model context.
- Use signed, expiring asset URLs; mirror outputs to your own storage.
- Moderate input and output; record the moderation policy version.
- Add rate limits, abuse detection and a manual kill switch.
- Consider watermarking and content provenance for generated media.
## Speculative work
Speculation is only safe when the marginal cost is small and the result can be
discarded silently. For media:
- Safe: prompt cleanup, safety pre-check, cost estimate, thumbnail, 480p draft.
- Unsafe by default: full 1080p/4K render, long video, paid third-party call
with no cap.
Cap speculative spend per request and per day. Never let speculation create a
user-visible charge.
## Build order
1. Schema, idempotency keys and append-only ledger.
2. Quote and deterministic gate.
3. One provider adapter and one worker.
4. Outbox and crash-safe submit.
5. Polling plus webhook reconciliation.
6. Asset mirroring, checksum and validation.
7. Exception-only UI.
8. Watchdog and incident view.
9. Second provider and fallback policy.
10. Shadow routing, then percentage rollout.
## Tests that must exist
- Same idempotency key twice returns the same job and one charge.
- Crash after reserve but before submit releases or reconciles correctly.
- Provider timeout followed by success does not double-submit.
- Duplicate webhook creates one event and one settlement.
- Cancel racing with success produces one terminal state and one ledger result.
- Restart during `running` reconciles before any retry.
- Overbudget request is rejected before the provider call.
- Prompt injection cannot change price, budget or provider.
- Provider URL expiry is handled by the mirrored asset.
- Watchdog cannot exceed the mandate.
## Anti-patterns
- An LLM deciding price, credits or permission.
- Charging after submit, or charging from a webhook alone.
- Retrying an unknown provider state.
- Treating a provider URL as permanent storage.
- One mutable `status` string for the whole lifecycle.
- Streaming logs to the main product UI.
- Multi-agent role-play where a deterministic queue would do.
- Speculative full-resolution renders with no cap.
- Storing provider keys in prompts, job payloads or logs.
- Adding a second provider before the first one is reconcilable.
## Output checklist
- [ ] Every external effect has an outbox row and an idempotency key.
- [ ] Every job has an immutable mandate and a policy version.
- [ ] Every attempt has a provider job id, request hash and normalized result.
- [ ] Credits reserve, settle and release through the ledger only.
- [ ] Restart and duplicate-webhook tests pass.
- [ ] The UI has exactly four user-facing states.
- [ ] The watchdog can stop and downgrade, but not expand.
- [ ] Provider credentials never enter model context.
把约束落成一份 Harness 契约
harness.yaml 不是代码,而是一份可评审的契约:它把 planes、concepts、job states、gates、money path、provider adapter、recovery、watchdog、UI、security、SLO 写成机器可读的结构。你可以把它放进仓库,作为 PR 评审时的检查表;也可以让 coding agent 按它生成 schema、状态机和测试清单。
展开完整 harness.yaml(可复制)
version: 1
name: media-generation-harness
status: reference-contract
principle: "Spend as little user attention and money as possible per delivered asset."
planes:
control:
owns:
- intake
- quote
- gate
- authorization
- scheduling
- settlement
- notification
must_not:
- run_gpu_work
- trust_provider_text_as_state
execution:
owns:
- provider_submit
- provider_poll
- download
- validate
- post_process
must_not:
- set_price
- grant_permission
- write_job_state_directly
data:
owns:
- jobs
- attempts
- credit_ledger
- assets
- provider_events
- policy_versions
must_not:
- hold_side_effects_only_in_memory
concepts:
source:
examples:
- web_upload
- public_api
- stripe
- moderation_callback
rule: "Normalize the event and dedupe by source event id before it enters the domain."
trigger:
rule: "Immutable request or callback, stored before processing."
thread:
rule: "Project or conversation context. Never grants budget or permission."
gate:
rule: "Deterministic checks only. Runs before any model assessment."
assessment:
rule: "Semantic advice only. Cannot set price, credits, resolution or provider."
decision:
values:
- accept
- clarify
- reject
- defer
rule: "clarify asks exactly one concrete question."
mandate:
fields:
- policy_version
- model
- resolution
- duration_seconds
- max_cost
- fallback_providers
- rights
- accepted_by
rule: "Immutable. Any wider scope requires a new ticket and mandate."
ticket:
rule: "One billable generation job. Owns user-visible identity and ledger."
assignment:
rule: "One provider attempt. Retries create a new assignment under the same ticket."
executor:
rule: "Provider adapter or GPU worker with an isolated workspace and asset prefix."
subscription:
rule: "Context only. It never expands authority."
channel:
examples:
- sse
- websocket
- webhook
- email
rule: "Ordered per ticket."
outbox:
rule: "Record the effect before performing it. Unique per ticket and effect key."
policy:
rule: "Catalog, price table and safety rules are versioned together."
watchdog:
rule: "External control loop. May stop or downgrade, never expand."
job:
states:
- draft
- quoted
- authorized
- queued
- submitted
- running
- downloaded
- validated
- ready
- delivered
- needs_input
- blocked
- failed
- canceled
- expired
transitions:
- from: draft
to: quoted
guard: "catalog entry exists and quote is complete"
- from: quoted
to: authorized
guard: "gate passed and credits reserved"
- from: authorized
to: queued
guard: "outbox row committed"
- from: queued
to: submitted
guard: "provider accepted the request"
- from: submitted
to: running
guard: "provider reports running"
- from: running
to: downloaded
guard: "output mirrored to owned storage"
- from: downloaded
to: validated
guard: "checksum, dimensions and moderation passed"
- from: validated
to: ready
guard: "asset manifest committed"
- from: ready
to: delivered
guard: "channel delivery recorded"
- from: active
to: needs_input
guard: "one structured question is pending"
- from: active
to: blocked
guard: "external dependency cannot proceed"
- from: active
to: failed
guard: "normalized terminal error recorded"
- from: active
to: canceled
guard: "cancel requested and provider state reconciled"
- from: quoted
to: expired
guard: "quote TTL elapsed"
- from: authorized
to: expired
guard: "authorization TTL elapsed before submit"
terminal:
- delivered
- failed
- canceled
- expired
rules:
- "Only authorized may call a provider."
- "submitted and running must carry provider_job_id and request_hash."
- "Terminal states are immutable except for append-only audit events."
gates:
order:
- id: auth
checks: ["session", "api_key", "ownership"]
- id: shape
checks: ["schema", "file_size", "url_allowlist"]
- id: catalog
checks: ["model", "resolution", "duration", "aspect_ratio"]
- id: quota
checks: ["credit_balance", "user_rate", "ip_rate"]
- id: cost
checks: ["max_cost", "daily_budget", "provider_price_version"]
- id: pre_moderation
checks: ["prompt", "reference_assets"]
- id: availability
checks: ["provider_health", "fallback_eligibility"]
failure_contract:
return:
- stable_error_code
- one_actionable_next_step
- policy_version
never:
- raw_provider_dump
- ambiguous_retry_instruction
assessment:
enabled: optional
inputs:
- normalized_prompt
- reference_asset_metadata
- policy_version
outputs:
- accept
- clarify
- reject
evidence_required: true
cannot:
- set_price
- set_credits
- raise_resolution
- extend_duration
- grant_provider_permission
money:
ledger: append_only
entries:
- reserve
- settle
- release
- refund
flow:
- "quote from policy version"
- "reserve with ticket-derived idempotency key"
- "commit job and outbox in one transaction"
- "submit after commit"
- "release on definite submit failure"
- "settle actual on success"
- "apply published refund rule on failure"
- "reconcile provider usage daily"
forbidden:
- "charge from webhook without stored job and attempt"
- "charge after submit with no reserve"
- "ad_hoc_refund"
provider_adapter:
methods:
quote:
input: ["normalized_request", "policy_version"]
output: ["line_items", "currency", "expiry"]
submit:
input: ["normalized_request", "attempt_id", "idempotency_key", "callback_url"]
output: ["provider_job_id", "accepted_at", "raw"]
get:
input: ["provider_job_id"]
output: ["state", "progress", "output", "error", "raw"]
cancel:
input: ["provider_job_id"]
output: ["accepted", "state"]
parse_webhook:
input: ["headers", "body"]
output: ["provider_job_id", "event_id", "state", "raw"]
normalized_states:
- queued
- running
- succeeded
- failed
- canceled
- unknown
rules:
- "Preserve raw provider payloads for audit."
- "Never expose provider URLs as permanent assets."
- "Never resubmit when provider state is unknown."
data_model:
generation_requests:
keys: ["client_idempotency_key", "owner_id"]
fields: ["raw_input_hash", "created_at"]
quotes:
keys: ["request_id", "policy_version"]
fields: ["line_items", "currency", "expires_at"]
jobs:
keys: ["ticket_id"]
fields: ["mandate", "state", "current_attempt_id"]
attempts:
keys: ["attempt_id"]
fields: ["provider", "provider_job_id", "request_hash", "response", "timings"]
credit_ledger:
keys: ["entry_id"]
fields: ["ticket_id", "kind", "amount", "policy_version", "created_at"]
outbox:
keys: ["ticket_id", "effect_type", "effect_key"]
fields: ["payload", "attempts", "sent_at", "last_error"]
provider_events:
keys: ["provider", "event_id"]
fields: ["raw", "processed_at"]
assets:
keys: ["asset_id"]
fields: ["storage_key", "checksum", "width", "height", "duration", "moderation"]
policy_versions:
keys: ["policy_version"]
fields: ["catalog", "price_table", "safety_rules", "effective_at"]
recovery:
startup:
- "find jobs in submitted or running"
- "query provider for each known provider_job_id"
- "continue from provider result when present"
- "fail attempt when provider has no record and submit deadline passed"
- "never resubmit only because the local process restarted"
- "settle or release only after reconciliation"
invariants:
- "Unknown provider state blocks retry."
- "Duplicate webhook is a no-op after the first processed event."
- "Cancel racing with success yields one terminal state and one ledger result."
watchdog:
checks:
- id: stale_job
condition: "age > p99 for model"
action: ["query_provider", "cancel_or_escalate"]
- id: no_progress
condition: "no provider state change for N minutes"
action: ["query_provider", "retry_with_fallback"]
- id: overbudget
condition: "cost > mandate.max_cost"
action: ["stop", "release_reserve", "alert"]
- id: repeated_error
condition: "same normalized error three times"
action: ["open_incident", "stop_retrying"]
- id: queue_slo
condition: "queue wait > SLO"
action: ["add_capacity", "degrade_catalog"]
- id: webhook_storm
condition: "duplicate event rate above threshold"
action: ["dedupe", "alert"]
write_set:
- cancel
- retry_within_existing_mandate
- release_reserve
- open_incident
cannot:
- raise_budget
- raise_resolution
- extend_duration
- grant_permission
ui:
user_states:
working:
shows: ["progress"]
hides: ["logs", "chain_of_thought", "provider_polling"]
needs_input:
shows: ["one_question", "one_input"]
ready:
shows: ["preview", "download", "accept", "regenerate"]
failed:
shows: ["plain_reason", "one_next_action"]
developer_trace:
includes: ["provider_request_id", "normalized_error", "timings", "policy_version"]
security:
untrusted:
- prompt
- uploaded_file
- reference_url
- provider_response
rules:
- "User content cannot change policy, price, budget or credentials."
- "Provider keys stay server-side and never enter model context."
- "Assets use signed expiring URLs and are mirrored to owned storage."
- "Moderate input and output; record the moderation policy version."
- "Rate limit, detect abuse and provide a manual kill switch."
- "Consider watermarking and content provenance."
speculation:
allowed:
- prompt_cleanup
- safety_precheck
- cost_estimate
- thumbnail
- draft_480p
forbidden_by_default:
- full_1080p_render
- full_4k_render
- long_video
- uncapped_third_party_call
rules:
- "Cap speculative spend per request and per day."
- "Never create a user-visible charge for speculative work."
- "Discard silently when the result is not useful."
slo:
track:
- quote_latency
- queue_wait
- submit_latency
- provider_duration
- success_rate
- cost_per_delivered_asset
- duplicate_charge_rate
- reconciliation_lag
targets:
duplicate_charge_rate: 0
ledger_mismatch_rate: 0
reconciliation_lag_minutes: 15
采用顺序与反模式
如果你现在只有一个原型,我建议按这个顺序做,而不是先搭「多 Agent 团队」:
- 01先做 schema、idempotency key 和 append-only credit ledger。
- 02再做 quote + 确定性 gate;没有这两层,不要接 provider。
- 03只接一个 provider、一个 worker,把 submit / poll / cancel 跑通。
- 04加 outbox 和 crash-safe submit;先证明重启不会重复提交。
- 05加 webhook + 轮询对账;重复 webhook 必须是 no-op。
- 06做资产镜像、checksum、尺寸/时长校验和审核。
- 07做四状态 UI 和异常队列;不要先做日志流。
- 08加确定性 watchdog;最后才考虑用模型判断「是否跑偏」。
- 09接第二个 provider,做 fallback 和 shadow routing。
不要抄的部分
- 11 个概念全搬。 两人团队做媒体站,先要 job、attempt、ledger、outbox 四个核心,别一开始就上 Correlation、Subscription、Policy 全套。
- 插件化架构。 DeepSeek Harness 的「一切皆插件」是它的底座优势,不是你的 MVP 必需项。
- 多 Agent 角色扮演。 一个队列 + 一组 worker + 一个异常面板,通常比「Router Agent、Planner Agent、Critic Agent」更可靠。
- 用 LLM 做 watchdog。 先把卡单、超预算、重复错误做成确定性检查;模型只处理「有进展但方向不对」这种模糊情况。
- 投机全片渲染。 视频的边际成本太高;投机只做便宜的前置步骤。
- 把 provider URL 当存储。 它会过期、会限流、会消失;你必须自己镜像。
- 先接五个 provider 再做对账。 一个 provider 的对账没跑通,接五个只会放大错账。
如果只记一句话:DeepSeek 值得抄的不是「多 Agent 角色扮演」,而是把一次「可以做」固化成一张可审计的凭证,然后把执行、重试、恢复、通知都挂在它下面。对 AI 视频/图片站,这意味着:先做 quote / gate / ledger,再做 provider 调度;先保证不重复扣费、不重复提交,再谈自治。
来源与核验
所有链接在 2026-11-02 检查为 200。原型的 commit、文件数和文档结论来自本地克隆;实验数字来自设计文章的中文全文转载,未把它当成独立基准测试。
- DeepSeek Harness · 产品页官方产品页:Cordis 插件架构、桌面/网页、MIT
- DeepSeek Harness · 快速开始开发者文档与安装入口
- deepseek-ai/deepseek-harness开源的 harness 仓库
- deepseek-ai/dsh-superagent-prototype研究原型,检查于 commit 6337fc8;README、docs/ 与 src/
- 机器之心 · Super-agent 设计(中文全文)DeepSeek Harness 团队设计文章的中文全文转载;因原公众号链接无法稳定抓取而使用
- GitHub · dsh-plugin 主题产品页提到的社区插件生态
如果 DeepSeek 后续更新原型,先重查三处:README 的免责声明、docs/001-domain.md 到 003-execution.md 的概念与状态机、以及 prompts/ 里的 admission 与 worker 约束。文章与代码的差异表也应该跟着更新。