关于 dagr-producer
dagr-producer 是用于创建和维护 dagr 运行文件的 Agent 技能,文件由 dagr view 实时呈现。它要求先严格验证候选文件再原子发布,并明确规定任务状态、尝试记录、依赖关系和结果证据。
主要功能
- 01发布前严格验证
- 02原子更新运行文件
- 03建模任务、项目与尝试
- 04仅追加事件溯源记录
- 05为结果标注证据等级
适用场景
- 01创建新的 dagr 运行
- 02安全更新实时任务图
- 03记录重试与评审退回
- 04区分已验证与未验证完成
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ aemrebarut/ herdr-dagr --skill dagr-producer安装后在 Claude Code 中输入/dagr-producer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/01-init.json02-first-attempt.json03-send-back.json04-gate-promotion.json05a-policy-declared.json05b-policy-materialized.json06-evidence-tiers.json07-answer-question.json08-project-scopes-and-message.json详细介绍
dagr 生产者——写入运行记录,验证运行记录
你是 producer:运行文件的唯一写入者,dagr view 会实时渲染该文件。dagr 是一个表示内核:你负责声明任务事实,它只会根据这些事实推导出明确定义的视图信号。缺失或错误的事实仍会生成缺失或错误的图;没有工作流引擎会替你修正。
契约版本:"dagr": 3(仍可读取 v1/v2 文件;新运行请写入 v3)。
找到你的验证器(写入任何内容之前)
下面的检查循环是你能获得的唯一反馈,因此请先解析 dagr
二进制文件,并在无法找到时停止:
- 如果已设置,使用
$DAGR_BIN; - 使用
command -v dagr(在 PATH 中查找); - 使用插件/仓库构建产物:
<dagr repo>/target/release/dagr——在 herdr 窗格中,运行时会注入$HERDR_PLUGIN_ROOT,它就是 dagr 仓库根目录; - 备用方案:
cargo run --manifest-path <dagr repo>/Cargo.toml -- check …。
如果没有可用的验证器 → 不要开始编写运行文件;说明情况并 停止。未经验证的运行文件正是整个系统旨在防止的那种悄无声息的错误。
循环(不可妥协)
write run.json.tmp → dagr check run.json.tmp --strict --json → fix → repeat until []
→ THEN rename over run.json
先验证候选文件,再发布——绝不能反过来。
窗格会立即渲染 run.json 中的内容;将无效候选文件重命名覆盖该文件,会在你迭代期间把错误状态展示给所有查看者。安全的事务流程:
- 将完整的新文档写入
run.json.tmp(位于同一目录——重命名必须是原子的,因此必须在同一文件系统中)。 dagr check run.json.tmp --strict --json。退出码为 0 且输出为[]表示通过;退出码为 1 表示存在问题(E 代码表示错误;W 代码表示可表示但可疑——除非你能说明原因,否则应修复;--strict会将其视为失败,建议使用)。退出码 2 表示验证器根本无法读取文件——这是路径或工具问题,而不是文档问题;这种情况下标准输出为空,因此绝不能将空输出视为通过。停止,重新执行预检,退出码非零时绝不发布。- 只有通过后才执行:
mv run.json.tmp run.json(原子重命名——窗格会在 mtime 变化时重新加载,绝不会看到写入一半或无效的文件)。 - 失败时:修复临时文件并重新检查。之前的实时文件保持不变——绝不要让
run.json本身处于错误状态。
写入位置:窗格会依次查找 $DAGR_RUN、.dagr/run.json,然后是工作区 cwd 下的 run.json;如果这些文件都不存在,则会等待 .dagr/run.json。默认写入工作区根目录下的 .dagr/run.json(将其加入 gitignore),每个文件对应一次运行:运行中的并行泳道只是 deps 互不相交的任务,而独立工作流应使用单独的运行文件和单独的窗格。
用一句话说明对象模型
- Project = 递归的可视化范围。运行是隐式根节点;子项目只是带有
parent的项目。阶段或工作流也只是项目,而非另一种实体。每个任务有一个可选的project归属,而其依赖边可以跨越任意项目边界。 - Task = 工作项。
id稳定,由你选择(绝不能用窗格 ID)。kind是开放集合 —impl · review · test · gate · question · docs · ship · …— 选择最贴切的一种(question用于需要由人回答的任务,gate用于汇聚多个依赖的任务;两者都会改变 dagr 的绘制方式)。 状态:queued · working · review · blocked · done · failed · rejected · canceled · settled_unverified。 - Attempt = 对任务的一次尝试。
id格式为T·aN,n从 1 开始。 状态:queued · working · done · failed · rejected · settled_unverified · lost。重试绝不会改写某次尝试 — 而是追加一次带有cause的新尝试。 任务的历史记录绝不会倒退 — 你不会改写或删除任何尝试。任务的状态取决于最新一次尝试,因此退回会让一个done任务在新尝试开启时回到working。 - Event = 只追加的来源记录:
attempt_started · attempt_settled · promoted · directive · message_resolved · note,按at时间戳升序排列。绝不改写或重新排列事件。 - 每个终态结果都带有证据等级:
verified(经机械检查 — 测试运行、提交回执) ·reported(来自执行者的类型化信封) ·heuristic(推断所得) ·asserted(未经佐证的声明)。“Agent 说已完成”最多算作reported。缺少信封?那就是settled_unverified— 一种真正的终态,而不是宽松意义上的done。
dagr check 会强制遵守的不变量
- 任务状态是对各次尝试的投影:
working需要存在正在进行的尝试;done/rejected/settled_unverified需要与最新尝试匹配;failed接受最新尝试为failed或lost;queued不允许存在正在进行的尝试,也不允许最新尝试为done或settled_unverified——任务在一次failed或rejected尝试后重新排队是正确且符合预期的;review至少需要一次尝试。canceled仅适用于任务:它会撤回计划中的工作,而不会改写或凭空创建一次尝试。 - 原因指向过去:第 n>1 次尝试带有
cause(sent_back · gate_failed · followup · superseded),其ref指向更早的一次尝试。只有 n=1 时才使用initial。 - 运行是一个 DAG:
deps或 gate 的inputs中不能存在环。gate 的汇入集合就是其deps;只有当汇入集合与依赖集合不同时,才使用inputs覆盖。将真正的顺序工作编码为依赖项;任务声明顺序仅用于在没有尝试的同级任务之间打破平局。 - 终态尝试必须带有
outcome,其中result==state,并且证据等级有效;时间戳必须是有效的 ISO-8601 格式,且尝试的结束时间晚于开始时间。 - ID 由你指定且必须唯一:任务 ID 不能与尝试 ID 冲突。herdr 窗格 ID 应放在
locator中,绝不能放在id中。 - 进行中的尝试必须可定位且仍然存活:进行中的尝试需要
locator({"pane": "wX:pN"})和已填充的liveness—prompt_acknowledged(布尔值)、last_output_at(时间戳字符串)、queued_input(已输入但尚未提交的 composer 行数,若无则为0——必须是数字,不能是布尔值;布尔值会导致整个文档被拒绝)。当你的 Agent 产生输出时,更新last_output_at;界面会显示过时状态,沉默才是敌人。 - 受阻状态要指出解除阻塞的对象;晋升是一种事件,不能靠推断——当汇入完成时,发出
{"type": "promoted", "task": ...}。 - **项目归属不等于依赖关系。**为每个任务指定一个真实的可视化归属;将所有阻塞项都放入
deps,包括跨项目的边。绝不要为了同时显示两边的影响,就把一个任务重复放入两个项目。 - **操作员消息保留其权限和 ID。**窗格会向你传递一个
[DAGR OPERATOR MESSAGE]信封。遵守recommend_and_return与may_decide_and_continue的区别;在解决事件中保留message_id。dagr 负责传递请求,但不会替你处理。
操作示例
{
"dagr": 3,
"run": {
"id": "run-myjob-v01", "title": "what this run is",
"started_at": "2026-02-01T09:00:00Z",
"orchestrator": {"pane": "wX:p1"}
},
"generated_at": "2026-02-01T09:00:00Z",
"projects": [],
"tasks": [],
"events": []
}
每次写入时都刷新 generated_at——它是屏幕上所有“Nm ago”的计时基准,值过时会显示过时状态横幅。
如果可用,请自动将 run.orchestrator 设置为你自己的 $HERDR_PANE_ID(否则使用稳定的 Herdr Agent 目标)。m 会在这里排入操作员消息;不要将其指向工作器。无需用户引导步骤或额外的控制器。
在任务前规划项目和子项目
使用能够提供清晰视觉归属的最精简层级。不要创建单独的 phases 或 workstreams 数组:
"projects": [
{"id": "APP", "title": "Application"},
{"id": "API", "title": "API stream", "parent": "APP", "owner": "api-lead"},
{"id": "UI", "title": "UI stream", "parent": "APP", "owner": "ui-lead"}
]
API 中的任务使用 "project": "API"。如果 UI 任务依赖它,就将 UI 任务保留在 UI 中,并将 API 任务 ID 放入其 deps;渲染器会显示跨项目依赖边。不要复制任务,也不要强行将其归入共同的父项目。只有真正属于运行级别的工作才省略 project。
先从粗略规划开始:声明有用的项目骨架、当前工作和有意义的关卡。随着发现新任务,在相关时添加;发布操作人员可见的工作,而不是每个内部 Agent、工具或运行时步骤。Dagr 根据 deps、owner/actor 和 kind 推导排队任务行的 waits、ready、unassigned 和 needs answer;绝不要将这些编码为额外字段。
打开任务并启动首次尝试
{
"id": "L1", "title": "impl: core lane", "kind": "impl",
"owner": "l1-dev", "state": "working", "deps": [],
"attempts": [{
"id": "L1·a1", "n": 1, "cause": {"type": "initial"},
"actor": "l1-dev", "model": "fable",
"locator": {"pane": "wX:p3"},
"state": "working", "started_at": "2026-02-01T09:05:00Z",
"liveness": {"prompt_acknowledged": true, "last_output_at": "2026-02-01T09:05:00Z"}
}]
}
将 {"at": ..., "type": "attempt_started", "task": "L1", "attempt": "L1·a1", "actor": "l1-dev"}
追加到 events。
model 是一个自由字符串,会原样渲染——dagr 不会改写它,因此显示形式由你选择。使用简短的 model·effort 标签:fable·xhigh、sol5.6·max、luna5.6·max、terra5.6·max。面板为该列提供 12 个字符单元(terra5.6·max 刚好填满);更长的字符串会截断并显示省略号,而窄屏布局会先隐藏标签,不会裁切标题。
结算一次尝试(附带证据)
设置尝试的 state、ended_at 和 outcome(result 必须与 state 相同);同步任务的 state;追加一个 attempt_settled 事件。
(ended_at 是工作停止的时间;结算事件的 at 是判定得出的时间——两者可能不同,而且时间差本身包含真实信息:一个于 09:50 停止的面板,其拒绝结果于 10:12 才得出,就会同时记录这两个时间。)优先使用你掌握的最有力证据。
判定规则:等级描述的是你对结算声明所掌握的证据。done 要求有人声称已完成——无论声明多么薄弱;等级用于评估该声明的证据。当无人声称任何内容,而你是根据运行时信号(面板退出、没有响应、绿色提示符)推断时,状态应为 settled_unverified——这正是该状态的用途。四种如实的结算方式在
examples/06-evidence-tiers.json 中有完整说明:
- verified — 机械凭据:
{"result": "done", "evidence": "verified", "receipt": "cargo test 40/40 ✓ @ a1b2c3d"} - reported — 来自执行者的类型化信封(结构化结果数据,而非聊天文本):
{"result": "done", "evidence": "reported", "receipt": "result envelope: {files: 4, status: complete}"} - asserted — 执行者声称已完成,但仅以文本形式声明:
{"result": "done", "evidence": "asserted", "reason": "actor asserted completion in chat; no typed envelope to verify against"} - 无人声称任何内容 — 这不是较弱的
done,而是不同的终态settled_unverified,并且仍需要ended_at和匹配的结果:"state": "settled_unverified", "ended_at": ..., "outcome": {"result": "settled_unverified", "evidence": "heuristic", "reason": "no claim of completion from the actor; inferring from clean pane exit"}。 它绝不会将未经证实的工作升级为成功。
退回并重新进入
审查者拒绝工作时会涉及三条记录——审查尝试结算为 done(审查本身成功),被审查的尝试结算为 rejected,修复轮次则是一个新尝试,其 cause 指向审查尝试:
{"id": "L1·a2", "n": 2,
"cause": {"type": "sent_back", "by": "rev-1", "ref": "R1·a1", "reason": "error paths untested"},
"locator": {"pane": "wX:p3"}, "state": "working", "started_at": "...",
"liveness": {"prompt_acknowledged": true, "last_output_at": "..."}}
cause.ref 必须指向一个已声明且更早的尝试——如果文件中没有 R1·a1,检查就会失败;即使是可正常运行的重试,也仍需要定位信息和存活状态。将任务的状态设回 state: "working";dagr 会根据此 cause 绘制 ↩ 重新进入路径。完整文档:
examples/03-send-back.json。
门控汇合
门控的汇合输入就是它的 deps——这样每条门控边都会纳入环路检查。只有当汇合输入集确实与依赖集不同时,才使用 inputs:
{"id": "G1", "title": "gate: merge lanes", "kind": "gate", "owner": "orchestrator",
"project": "APP", "criteria": "API and UI reviews are clean",
"state": "queued", "deps": ["L1", "L2", "L3"], "attempts": []}
按符合预期的人类阅读顺序声明门控输入,并确保整个 tasks 数组也符合预期:对于没有尝试记录的同级任务,以及门控中承载状态的汇合区,dagr 都会保留声明顺序。不要为了排序而重命名 id,不要把门控挂到某条泳道上作为布局变通方案,也不要添加合成的“汇合”任务。声明真实的汇合输入。带有 project 的门控是该项目中的里程碑;不带 project 时,dagr 会将它放在所有输入共同所属的最近项目中。因此,局部于 API 的门控会留在 API 中,汇合 API 和 UI 的门控位于它们的父级 APP 中,而汇合互不相关的顶层项目的门控则是运行级里程碑。输入尝试的时间戳不会决定门控的父级。每个直接输入会显示为 ○ 等待、◎ 进行中或 ● 已满足(以及常规的阻塞/待审核/失败标记)。在较窄的窗格中,这些标记会汇总显示;选中门控时,仍可查看确切的输入 id。
已知组织归属时,优先显式指定 project;从输入所属位置推断才符合实际时,则省略它。切勿仅为改变门控在屏幕上的位置而添加虚假依赖。
最后一个输入到达时,追加一条 promoted 事件——并带上它的 at 时间戳,与每条事件一样:
{"at": "...", "type": "promoted", "task": "G1", "detail": "fan-in complete: L1 ✓, L2 ✓"}
“推进门控”意味着仍须遵循投影规则:状态为 working/done 的门控需要有自身处于该状态的尝试——门控工作开始时,开启 G1·a1(如果处于活动状态,则附上定位器和存活状态),并像其他尝试一样用证据将其结算。attempts: [] 的门控可以处于 queued 状态;如果门控已撤回,则可以处于 canceled 状态。完整文档:
examples/04-gate-promotion.json。
取消计划中的工作
将任务设为 "state": "canceled",并填写简短的 note;保留所有先前的尝试和事件,不作更改。不存在已取消的尝试结果。由于取消不等于成功,请更新或取消任何仍依赖该任务的任务。
声明循环策略(后续轮次),不要只暗示
"policy": {"rounds_max": 3, "futures": [
{"on": "pass", "ref": "G1"},
{"on": "fail", "node": {"id": "L1·a2", "title": "fix round", "actor": "l1-dev"}, "loop_back": true},
{"on": "fail", "streak": 2,
"node": {"id": "L1x·a1", "title": "escalate: fresh approach", "attribution": "predicted"},
"after": "L1·a2", "source": "two-strikes rule"}
]}
ref 指向现有任务(显示为 »);node 声明尚未实际存在的节点(显示为 ○,带有 loop_back 时显示为 ⟲,当 attribution 为 predicted 时显示为 ≈);after 将后续内容接到同一策略的同级后续节点上。dagr 仅根据此区块渲染后续节点,而且只渲染处于 working 或 blocked 状态的节点。完整文档:
examples/05a-policy-declared.json。
将后续节点实体化(预测的轮次实际开始时)
policy 表示当前意图,而非历史记录——这是唯一要原地编辑的区块。历史记录保存在 attempts 和 events 中;这些内容只能追加,而策略是你需要保持真实的预测。当已声明的后续节点成为现实后,请在同一份候选文档中完成以下所有操作(然后进行检查 → 重命名):
- 从
policy.futures中移除已消耗的后续节点。若保留它,就会与真实尝试的 id 冲突,并被检查拒绝。 - 修复指向已移除节点的
after链:原先以"after": "L1·a2"接续的同级后续节点,现在应直接接到任务上(删除其after),或接到另一个仍未实现的同级节点上。 - 追加真实尝试,使用后续节点预测的 id,并通过
cause指向触发项(循环返回修复轮次的followup应引用失败的尝试;门控退回时使用gate_failed);如果处于活动状态,还要附上定位器和存活状态。 - 追加
attempt_started事件。切勿修改先前的尝试或事件。
前后对照,两份文档均通过严格检查:
examples/05a-policy-declared.json →
examples/05b-policy-materialized.json。
记录人工决策
{"at": "...", "type": "directive", "verb": "reject", "by": "operator",
"task": "L1", "detail": "error paths untested"}
动词:reject · unblock · answer · rule。指令是决策日志;聊天内容不是。
回答问题(通过指令完成任务)
依赖项已完成的排队 question 会在关注队列中显示为 needs answer;生产方不会声明单独的就绪字段。
单独的指令事件无法完成任务——任务状态是基于尝试的投影,因此人工回答需要有一次 actor 为人工的尝试。将两者写入同一个候选项:
{"id": "Q1", "title": "question: retry budget?", "kind": "question",
"owner": "operator", "state": "done", "deps": [],
"attempts": [{
"id": "Q1·a1", "n": 1, "cause": {"type": "initial"},
"actor": "operator", "state": "done",
"started_at": "2026-02-01T09:40:00Z", "ended_at": "2026-02-01T09:42:00Z",
"outcome": {"result": "done", "evidence": "reported",
"receipt": "directive answer: retry budget 2 rounds"}}]}
再加上事件:
{"at": "2026-02-01T09:42:00Z", "type": "directive", "verb": "answer",
"by": "operator", "task": "Q1", "detail": "retry budget 2 rounds"}
回执会引用答案;指令则是决策日志条目。
同样的模式也适用于任何由人工解决的任务(例如通过解除阻塞来关闭 question,或通过规则将任务退役)。完整文档:
examples/07-answer-question.json。
处理操作员消息
面板的默认操作是一个带上下文的消息编辑器。Herdr 会将已完成的消息直接排入 run.orchestrator 定位器,因此无需另建收件箱守护进程。你会收到如下信封:
[DAGR OPERATOR MESSAGE]
message_id: msg-0123456789abcdef
run: run-myjob-v01
revision: 2026-02-01T09:42:00Z
target: G1
starter: get-guidance
authority: recommend_and_return
Ask sol5.6·max and fable·xhigh independently, then combine their opinions.
请执行以下操作:
- 确认收到消息。将原始文本视为针对指定目标的指令,并受现有运行范围约束。
- 不受文本内容影响,独立遵循权限:
recommend_and_return表示进行分析并返回选项;may_decide_and_continue允许你自行决策并继续。绝不要仅凭“最佳猜测”之类的措辞推断出第二种权限。 - 将模型、推理和多 Agent 请求视为普通的可编辑指令,并使用常规编排工具。dagr 不会运行这些请求。
- 在后续处理中保留
message_id。问题解决后,追加一个事件:
{"at":"2026-02-01T09:50:00Z","type":"message_resolved","task":"G1",
"message_id":"msg-0123456789abcdef",
"detail":"recommended option B after two independent reviews; awaiting operator"}
如果有多条消息为同一项决策提供依据,请在指令/解决事件中设置 source_messages。不要编辑 messages.jsonl;dagr 负责维护该只追加的投递日志。你的事件是指向该日志的持久项目记忆链接。
按需自定义三个提示词起始项
没有入门步骤。内置项(Use judgment、Get guidance、Snooze)默认可用。如果用户要求添加或更改操作,请在运行文件旁以原子方式写入 actions.json:
{
"version": 1,
"include_defaults": true,
"actions": [
{"id":"architecture-council", "label":"Architecture council",
"prompt":"Ask two independent architecture reviewers and synthesize.",
"authority":"recommend"}
]
}
每个操作都只是一个预填的可编辑提示词,加上 recommend|decide 权限。列表应保持精简;优先选择一个灵活的起始项,而不是许多僵化按钮。与内置项匹配的 id 会覆盖对应内置项。include_defaults: false 会替换内置项。支持的配置格式为版本 1;最多显示九个起始项,标签上限为 80 字节,提示词上限为 32 KiB。面板会自动重新加载此文件,并在配置无效或不受支持时显示横幅。
旧版顶层运行 actions 值可读取,但不起作用。请勿添加这些值。
运行时丢失
面板在工作中途崩溃了?该尝试状态为 lost(无需结果),任务会投影为 failed 或 blocked。不要删除该尝试;追踪记录就是凭据。
请勿执行的操作
- 不要重新编号、改写或删除尝试或事件。只追加。
- 不要将 herdr 面板/Agent id 放入任务或尝试 id 中——它们是
locator数据,设计上具有时效性。 - 没有机械回执时,不要声称状态为
verified。事后不要升级证据,除非追加新事件说明原因。 - 不要根据 herdr 对当前状态的判断来编码任务状态(面板处于运行状态 ≠ 工作已完成)。herdr 表示位置,契约表示事实。
- 不要镜像记录每个内部 Agent/工具步骤;只纳入操作员需要了解、引导或验证的工作。
- 不要手动计算“完成百分比”——请写入每次尝试的
progress({"done": 3, "total": 7, "note": "..."})和时间戳;分析数据通过查询获得。
字段参考
完整架构见 dagr 仓库中的 CONTRACT.md(Schema v3 部分);其中也列出了 findings 代码。当 dagr check --json 给出你不认识的代码时,请阅读其消息——每条 finding 都会附上出错字段的 JSON 路径。