dagr-producer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dagr producer — write the run, prove the run

dagr 生产者——编写运行文件,验证运行过程

You are the producer: the single writer of a run file that
dagr view
renders live. dagr is a representation kernel: you assert task truth, and it derives only defined view signals from those facts. Missing or wrong facts still produce a missing or wrong graph; there is no workflow engine to repair it.
Contract version:
"dagr": 3
(v1/v2 files remain readable; write v3 for new runs).
你作为生产者:是
dagr view
实时渲染的运行文件的唯一编写者。dagr是一个表示核心:你断言任务的真实状态,它仅从这些事实中推导定义好的视图信号。缺失或错误的事实仍会生成缺失或错误的图;没有工作流引擎会自动修复它。
契约版本:
"dagr": 3
(v1/v2版本的文件仍可读取;新运行请使用v3版本)。

Find your validator (before you write anything)

找到你的验证器(编写任何内容之前)

The check loop below is the only feedback you get, so resolve the
dagr
binary FIRST and stop if you can't:
  1. $DAGR_BIN
    if set;
  2. command -v dagr
    (on PATH);
  3. the plugin/repo build:
    <dagr repo>/target/release/dagr
    — inside a herdr pane the runtime injects
    $HERDR_PLUGIN_ROOT
    , which IS the dagr repo root;
  4. fallback:
    cargo run --manifest-path <dagr repo>/Cargo.toml -- check …
    .
No validator available → do not start writing run files; say so and stop. An unvalidated run file is exactly the silent wrongness this whole system exists to prevent.
下面的检查循环是你能获得的唯一反馈,因此请先确定
dagr
二进制文件的位置,若无法找到则停止操作:
  1. 若已设置
    $DAGR_BIN
    ,则使用该路径;
  2. 使用
    command -v dagr
    (在PATH环境变量中查找);
  3. 插件/仓库构建版本:
    <dagr repo>/target/release/dagr
    ——在herdr面板中,运行时会注入
    $HERDR_PLUGIN_ROOT
    ,该路径即为dagr仓库根目录;
  4. 备选方案:
    cargo run --manifest-path <dagr repo>/Cargo.toml -- check …
若没有可用的验证器→请勿开始编写运行文件;告知相关情况并停止操作。未经验证的运行文件正是这套系统要避免的隐性错误。

The loop (non-negotiable)

核心循环(不可妥协)

write run.json.tmp → dagr check run.json.tmp --strict --json → fix → repeat until []
                                                             → THEN rename over run.json
Validate the candidate, then publish it — never the other way around. The pane renders whatever
run.json
holds, immediately; renaming an invalid candidate over it shows your error state to every viewer while you iterate. The safe transaction:
  1. Write the complete next document to
    run.json.tmp
    (same directory — rename must be atomic, so same filesystem).
  2. dagr check run.json.tmp --strict --json
    . Exit 0 with
    []
    is clean; exit 1 means findings (E-codes are errors; W-codes mean representable- but-suspect — fix them unless you can say why not;
    --strict
    treats them as failures, prefer it). Exit 2 means the validator could not read the file at all — a path or tooling problem, never a document problem; stdout is empty on that path, so never treat empty output as clean. Stop, re-run the preflight, and never publish on a non-zero exit.
  3. Only when clean:
    mv run.json.tmp run.json
    (atomic rename — the pane reloads on mtime and never sees a half-written or invalid file).
  4. On failure: fix the temp file and re-check. The previous live file stays untouched — never leave
    run.json
    itself in an error state.
Where to write it: the pane looks for
$DAGR_RUN
first, then
.dagr/run.json
, then
run.json
under the workspace cwd — and waits on
.dagr/run.json
when none exists yet. Default to
.dagr/run.json
in the workspace root (gitignore it), and one run per file: parallel lanes inside a run are just tasks with disjoint
deps
, but a separate workflow gets its own run file and its own pane.
write run.json.tmp → dagr check run.json.tmp --strict --json → fix → repeat until []
                                                             → THEN rename over run.json
先验证候选文件,再发布——绝不能颠倒顺序。 面板会立即渲染
run.json
中的内容;将无效候选文件重命名覆盖原文件会在你迭代过程中向所有查看者展示错误状态。安全的操作流程:
  1. 将完整的下一版文档写入
    run.json.tmp
    (需在同一目录下——重命名操作必须是原子性的,因此要在同一文件系统中)。
  2. 执行
    dagr check run.json.tmp --strict --json
    。返回码为0且输出
    []
    表示验证通过;返回码为1表示存在问题(E开头的代码为错误;W开头的代码表示内容可表示但存在疑点——除非你能说明无需修复的理由,否则请修复;
    --strict
    会将其视为失败,推荐使用该参数)。返回码为2表示验证器根本无法读取文件——这是路径或工具问题,而非文档问题;此时标准输出为空,因此绝不能将空输出视为验证通过。停止操作,重新执行预检查,返回码非零时绝不能发布。
  3. 仅当验证通过后:执行
    mv run.json.tmp run.json
    (原子重命名——面板会根据修改时间重新加载,绝不会看到半写入或无效的文件)。
  4. 若验证失败:修复临时文件并重新检查。之前的在线文件会保持不变——绝不能让
    run.json
    处于错误状态。
运行文件的写入位置:面板会优先查找
$DAGR_RUN
,其次是
.dagr/run.json
,最后是工作区当前目录下的
run.json
——当以上路径都不存在时,会等待
.dagr/run.json
的创建。默认写入工作区根目录下的
.dagr/run.json
(将其加入git忽略列表),每个运行对应一个文件:运行内的并行分支只需设置不相交的
deps
即可,而独立的工作流需要单独的运行文件和面板。

Object model in one breath

一句话总结对象模型

  • Project = the recursive visual scope. The run is the implicit root;
    projects[]
    may have
    parent
    . A phase or workstream is just a project, not another entity. Each task has one optional
    project
    home, while its dependency edges may cross any project boundary.
  • Task = the work item. Stable
    id
    you choose (never a pane id).
    kind
    is an open set —
    impl · review · test · gate · question · docs · ship · …
    — pick the honest one (
    question
    for a task that exists to be answered by a human,
    gate
    for fan-ins; both change how dagr draws it). States:
    queued · working · review · blocked · done · failed · rejected · canceled · settled_unverified
    .
  • Attempt = one try at a task.
    id
    styled
    T·aN
    , 1-based
    n
    . States:
    queued · working · done · failed · rejected · settled_unverified · lost
    . A retry never rewrites an attempt — it appends a new one with a
    cause
    .
    A task's history never moves backward — you never rewrite or delete an attempt. A task's state follows its latest attempt, so a send-back moves a
    done
    task back to
    working
    as its new attempt opens.
  • Event = append-only provenance:
    attempt_started · attempt_settled · promoted · directive · message_resolved · note
    , ascending
    at
    timestamps. Never rewrite or reorder events.
  • Evidence tier on every terminal outcome:
    verified
    (mechanically checked — test run, commit receipt) ·
    reported
    (typed envelope from the actor) ·
    heuristic
    (inferred) ·
    asserted
    (bare claim). "The agent said done" is at best
    reported
    . Missing envelope? That's
    settled_unverified
    — a real terminal state, not a soft
    done
    .
  • Project(项目) = 递归可视化范围。运行文件是隐式根节点;
    projects[]
    可包含
    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
    时间戳升序排列。绝不能重写或重新排序事件。
  • Evidence tier(证据层级) 适用于所有最终结果:
    verified
    (机械检查——测试运行、提交回执)·
    reported
    (来自执行者的结构化信封)·
    heuristic
    (推断得出)·
    asserted
    (无证据声明)。“Agent称已完成”最多属于
    reported
    层级。若缺少信封?则状态为
    settled_unverified
    ——这是一个真实的最终状态,而非宽松的
    done

Invariants dagr check will hold you to

dagr check强制执行的不变规则

  1. Task state is a projection over attempts:
    working
    needs a working attempt;
    done
    /
    rejected
    /
    settled_unverified
    need the latest attempt to match;
    failed
    accepts a latest attempt of
    failed
    or
    lost
    ;
    queued
    forbids a working attempt, and forbids a latest attempt of
    done
    or
    settled_unverified
    — a task re-queued after a
    failed
    or
    rejected
    attempt is correct and expected;
    review
    needs at least one attempt.
    canceled
    is task-only: it withdraws planned work without rewriting or inventing an attempt.
  2. Causes point backward in time: attempt n>1 carries
    cause
    (
    sent_back · gate_failed · followup · superseded
    ) whose
    ref
    names an earlier attempt.
    initial
    only for n=1.
  3. The run is a DAG: no cycles through
    deps
    or gate
    inputs
    . A gate's fan-in IS its
    deps
    ;
    inputs
    exists only to override when the fan-in set differs from the dependency set. Encode true sequential work as dependencies; task declaration order is only the attempt-less sibling tiebreak.
  4. Terminal attempts carry
    outcome
    with
    result
    ==
    state
    and a real evidence tier; timestamps are real ISO-8601 and attempts end after they start.
  5. Ids are yours and unique: task ids never collide with attempt ids. herdr pane ids go in
    locator
    , never in
    id
    .
  6. Live attempts are locatable and alive: working attempts want a
    locator
    (
    {"pane": "wX:pN"}
    ) and a populated
    liveness
    prompt_acknowledged
    (bool),
    last_output_at
    (timestamp string),
    queued_input
    (count of composer lines typed but unsubmitted,
    0
    when none — a number, not a bool; a bool rejects the whole document). Update
    last_output_at
    when your agents produce output; staleness is rendered, silence is the enemy.
  7. Blocked names its unblocker; promotion is an event, not an inference — emit
    {"type": "promoted", "task": ...}
    when a fan-in completes.
  8. Project containment is not dependency. Give each task one truthful visual home; keep every blocker in
    deps
    , including cross-project edges. Never duplicate a task into two projects to make both impacts visible.
  9. Operator messages retain their authority and id. The pane delivers a
    [DAGR OPERATOR MESSAGE]
    envelope to you. Respect
    recommend_and_return
    versus
    may_decide_and_continue
    ; preserve
    message_id
    in the resolution event. dagr transports the request but does not act on it for you.
  1. 任务状态是尝试记录的投影
    working
    状态需要存在一个处于working状态的尝试;
    done
    /
    rejected
    /
    settled_unverified
    状态需要最新尝试的状态与之匹配;
    failed
    状态允许最新尝试为
    failed
    lost
    queued
    状态禁止存在working状态的尝试,且禁止最新尝试为
    done
    settled_unverified
    ——任务在
    failed
    rejected
    尝试后重新进入queued状态是合理且符合预期的;
    review
    状态需要至少存在一个尝试。
    canceled
    是任务独有的状态:它会撤销计划中的工作,无需重写或创建尝试记录。
  2. 原因指向时间更早的记录:第n>1次尝试需携带
    cause
    sent_back · gate_failed · followup · superseded
    ),其
    ref
    字段指向更早的尝试记录。
    initial
    仅用于第1次尝试。
  3. 运行文件是DAG结构:通过
    deps
    或gate的
    inputs
    不会形成循环。Gate的扇入集合就是其
    deps
    ——这样可确保所有gate边都在循环检查范围内。仅当扇入集合与依赖集合确实不同时,才使用
    inputs
    字段。将真实的顺序工作编码为依赖关系;任务声明顺序仅用于无尝试记录的同级任务的排序,以及gate状态连接条的排序。
  4. 最终状态的尝试需携带
    outcome
    result
    必须与状态一致,且包含真实的证据层级;时间戳为有效的ISO-8601格式,且尝试的结束时间晚于开始时间。
  5. ID由你定义且唯一:任务ID绝不能与尝试ID冲突。herdr面板ID需放入
    locator
    字段,绝不能放入
    id
    字段。
  6. 活跃尝试需可定位且保持活跃:working状态的尝试需要
    locator
    {"pane": "wX:pN"}
    )和完整的
    liveness
    字段——
    prompt_acknowledged
    (布尔值)、
    last_output_at
    (时间戳字符串)、
    queued_input
    (已输入但未提交的编辑器行数计数,无内容时为
    0
    ——必须是数字,不能是布尔值;若为布尔值会导致整个文档被拒绝)。当你的Agent产生输出时,更新
    last_output_at
    字段;状态过期会被渲染出来,沉默是最大的问题。
  7. Blocked状态需指明解锁条件Promotion是一个事件,而非推断结果——当扇入完成时,需发送
    {"type": "promoted", "task": ...}
    事件。
  8. 项目归属不等于依赖关系。为每个任务指定一个真实的可视化归属;将所有阻塞项放入
    deps
    ,包括跨项目的依赖边。绝不能为了展示影响而将一个任务复制到两个项目中。
  9. 操作员消息保留其权限和ID。面板会向你传递
    [DAGR OPERATOR MESSAGE]
    信封。请区分
    recommend_and_return
    may_decide_and_continue
    ;在决议事件中保留
    message_id
    。dagr仅负责传输请求,不会替你执行操作。

Recipes

实践指南

Every recipe below has a complete, strict-clean companion document under
examples/
in this skill directory — held clean by the dagr test suite. The fragments here show the shape; when you assemble a real file, crib from the example, because the fragments alone omit cross-references (a
cause.ref
needs its referent declared, a working attempt needs locator + liveness) that
dagr check
will hold you to.
以下每个指南都配有完整、严格验证通过的示例文档,位于本技能目录下的
examples/
文件夹中——由dagr测试套件确保其有效性。此处的片段仅展示结构;当你组装真实文件时,请参考示例,因为单独的片段会遗漏
dagr check
要求的交叉引用(
cause.ref
需要指向已声明的记录,working状态的尝试需要locator + liveness字段)。

Initialize a run

初始化运行文件

json
{
  "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": []
}
Refresh
generated_at
on every write — it anchors every "Nm ago" on screen, and a stale value renders a staleness banner.
Set
run.orchestrator
automatically from your own
$HERDR_PANE_ID
when available (or a stable Herdr agent target otherwise). This is where
m
queues operator messages; do not point it at a worker. No user onboarding step or extra controller is required.
json
{
  "dagr": 3,
  "run": {
    "id": "run-myjob-v01", "title": "本次运行的描述",
    "started_at": "2026-02-01T09:00:00Z",
    "orchestrator": {"pane": "wX:p1"}
  },
  "generated_at": "2026-02-01T09:00:00Z",
  "projects": [],
  "tasks": [],
  "events": []
}
每次写入时更新
generated_at
字段——它是屏幕上所有“N分钟前”显示的锚点,过期的值会渲染出过期提示横幅。
若可用,自动从你自己的
$HERDR_PANE_ID
设置
run.orchestrator
(否则使用稳定的Herdr Agent目标)。这是操作员消息的队列位置;请勿将其指向工作节点。无需用户入门步骤或额外控制器。

Shape projects before tasks

先定义项目结构,再创建任务

Use the smallest hierarchy that provides honest visual homes. Do not create separate
phases
or
workstreams
arrays:
json
"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"}
]
A task in
API
uses
"project": "API"
. If a UI task depends on it, keep the UI task in
UI
and put the API task id in its
deps
; the renderer shows the cross-project edge. Do not duplicate the task or force it into the common parent. Omit
project
only for genuinely run-level work.
Start coarse: declare the useful project skeleton, immediate work, and meaningful gates. Add discovered tasks as they become relevant; publish operator-visible work, not every internal agent, tool, or runtime step. Dagr derives queued-row
waits
,
ready
,
unassigned
, and
needs answer
from
deps
,
owner
/
actor
, and
kind
; never encode those as extra fields.
使用最小的层级结构来提供真实的可视化归属。不要创建单独的
phases
workstreams
数组:
json
"projects": [
  {"id": "APP", "title": "应用程序"},
  {"id": "API", "title": "API工作流", "parent": "APP", "owner": "api-lead"},
  {"id": "UI", "title": "UI工作流", "parent": "APP", "owner": "ui-lead"}
]
归属
API
的任务需设置
"project": "API"
。若UI任务依赖该API任务,请将UI任务保留在
UI
项目中,并将API任务ID放入其
deps
字段;渲染器会显示跨项目的依赖边。绝不能复制任务或强制将其放入公共父项目。仅当任务属于运行级别的工作时,才省略
project
字段。
从粗粒度开始:声明有用的项目框架、即时工作和有意义的闸门。当发现新任务变得相关时再添加;发布操作员可见的工作,而非每个内部Agent、工具或运行时步骤。Dagr会根据
deps
owner
/
actor
kind
字段推导出队列行的
waits
ready
unassigned
needs answer
状态;绝不要将这些编码为额外字段。

Open a task and start its first attempt

创建任务并启动第一次尝试

json
{
  "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"}
  }]
}
Append
{"at": ..., "type": "attempt_started", "task": "L1", "attempt": "L1·a1", "actor": "l1-dev"}
to
events
.
model
is a free string rendered verbatim — dagr never rewrites it, so YOU pick the display form. Use a short
model·effort
chip:
fable·xhigh
,
sol5.6·max
,
luna5.6·max
,
terra5.6·max
. The pane gives the column 12 cells (
terra5.6·max
fills it exactly); longer strings get truncated with an ellipsis, and narrow layouts drop the chip before they clip your title.
json
{
  "id": "L1", "title": "实现:核心分支", "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"}
  }]
}
events
数组中追加
{"at": ..., "type": "attempt_started", "task": "L1", "attempt": "L1·a1", "actor": "l1-dev"}
model
是一个自由字符串,会被原样渲染——dagr绝不会重写它,因此由你选择显示形式。使用简短的
model·effort
标签:
fable·xhigh
sol5.6·max
luna5.6·max
terra5.6·max
。面板会为该列分配12个字符的宽度(
terra5.6·max
刚好填满);更长的字符串会被截断并添加省略号,窄布局会先隐藏标签再截断标题。

Settle an attempt (with proof)

完成尝试(附带证据)

Set attempt
state
,
ended_at
, and
outcome
(
result
must equal the state); mirror the task
state
; append an
attempt_settled
event. (
ended_at
is when the work stopped; the settled event's
at
is when the verdict landed — they may differ, and the gap is real information: a pane that stopped at 09:50 whose rejection landed at 10:12 records both.) Prefer the strongest evidence you have.
The decision rule: the tier describes your evidence for the settlement claim.
done
requires that someone claimed completion — however weakly; the tier grades the claim's evidence. When nobody claimed anything and you are inferring from a runtime signal (pane exit, silence, a green prompt), the state is
settled_unverified
— that is what the state is for. The four honest settlements, spelled out completely in
examples/06-evidence-tiers.json
:
  • verified — mechanical receipt:
    {"result": "done", "evidence": "verified", "receipt": "cargo test 40/40 ✓ @ a1b2c3d"}
  • reported — a typed envelope from the actor (structured result data, not chat prose):
    {"result": "done", "evidence": "reported", "receipt": "result envelope: {files: 4, status: complete}"}
  • asserted — the actor claimed completion, but only as prose:
    {"result": "done", "evidence": "asserted", "reason": "actor asserted completion in chat; no typed envelope to verify against"}
  • nobody claimed anything — that is not a soft
    done
    , it is the distinct terminal state
    settled_unverified
    , and it still needs
    ended_at
    and a matching outcome:
    "state": "settled_unverified", "ended_at": ..., "outcome": {"result": "settled_unverified", "evidence": "heuristic", "reason": "no claim of completion from the actor; inferring from clean pane exit"}
    . It never upgrades unproven work to success.
设置尝试的
state
ended_at
outcome
result
必须与状态一致);同步更新任务的
state
;追加
attempt_settled
事件。(
ended_at
工作停止的时间;settled事件的
at
是* verdict*下达的时间——两者可能不同,时间差是真实信息:比如面板在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": "执行者在聊天中声称已完成;无结构化信封可验证"}
  • 无人声称完成——这不是宽松的
    done
    ,而是明确的最终状态
    settled_unverified
    ,仍需
    ended_at
    和匹配的
    outcome
    "state": "settled_unverified", "ended_at": ..., "outcome": {"result": "settled_unverified", "evidence": "heuristic", "reason": "执行者未声称完成;从面板正常退出推断"}
    。它绝不会将未经验证的工作升级为成功。

Send back and re-enter

退回任务并重新执行

A reviewer rejecting work touches THREE records — the review attempt settles
done
(the review itself succeeded), the reviewed attempt settles
rejected
, and the fix round is a new attempt whose
cause
points at the review attempt:
json
{"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
must name a declared, earlier attempt — if
R1·a1
isn't in the file, the check fails; a working retry still needs locator + liveness. Task back to
state: "working"
; dagr draws the ↩ re-entry from exactly this cause. Complete document:
examples/03-send-back.json
.
审核者拒绝工作时需修改三个记录——审核尝试的状态设为
done
(审核本身成功),被审核的尝试状态设为
rejected
,修复轮次作为新尝试,其
cause
指向审核尝试:
json
{"id": "L1·a2", "n": 2,
 "cause": {"type": "sent_back", "by": "rev-1", "ref": "R1·a1", "reason": "错误路径未测试"},
 "locator": {"pane": "wX:p3"}, "state": "working", "started_at": "...",
 "liveness": {"prompt_acknowledged": true, "last_output_at": "..."}}
cause.ref
必须指向已声明的、更早的尝试——若
R1·a1
未在文件中声明,检查会失败;处于working状态的重试仍需要locator + liveness字段。任务状态设回
state: "working"
;dagr会通过该cause字段渲染↩重新执行的标记。完整文档:
examples/03-send-back.json

Gate a fan-in

扇入闸门

A gate's fan-in is its
deps
— that keeps every gate edge inside the cycle check. Use
inputs
only when the fan-in set genuinely differs from the dependency set:
json
{"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": []}
Declare gate inputs in the intentional human reading order, and keep the whole
tasks
array intentional too: dagr preserves declaration order for attempt-less siblings and for the gate's state-bearing join strip. Do not rename ids for sorting, attach the gate to one lane as a layout workaround, or add a synthetic "join" task. Declare the truthful fan-in. A gate with
project
is a milestone in that project; without one, dagr places it at the nearest project shared by all inputs. Therefore a gate local to
API
stays inside
API
, a gate joining
API
and
UI
lives in their parent
APP
, and a gate joining unrelated top-level projects is a run-level milestone. Input attempt timestamps never choose its parent. Each direct input renders as
waiting,
working, or
satisfied (plus the normal blocked/review/failure marks). On narrow panes it aggregates those marks; a selected gate still reveals the exact input ids.
Prefer an explicit
project
when the organizational ownership is known; omit it when inference from input homes is the truthful answer. Never add a fake dependency solely to move a gate on screen.
When the last input lands, append a
promoted
event — with its
at
timestamp
, like every event:
json
{"at": "...", "type": "promoted", "task": "G1", "detail": "fan-in complete: L1 ✓, L2 ✓"}
"Moving the gate forward" means the projection rules still apply: a gate at
working
/
done
needs its own attempt in that state — open
G1·a1
(with locator + liveness if live) when the gate's work starts, and settle it with evidence like any other attempt. A gate with
attempts: []
can be
queued
, or
canceled
when the gate was withdrawn. Complete document:
examples/04-gate-promotion.json
.
闸门的扇入集合就是其
deps
——这样可确保所有闸门边都在循环检查范围内。仅当扇入集合与依赖集合确实不同时,才使用
inputs
字段:
json
{"id": "G1", "title": "闸门:合并分支", "kind": "gate", "owner": "orchestrator",
 "project": "APP", "criteria": "API和UI审核通过",
 "state": "queued", "deps": ["L1", "L2", "L3"], "attempts": []}
按照人类阅读的逻辑顺序声明闸门输入,同时保持整个
tasks
数组的逻辑顺序:dagr会保留无尝试记录的同级任务的声明顺序,以及闸门状态连接条的顺序。不要为了排序而重命名ID,不要将闸门附加到某个分支作为布局变通方案,也不要添加合成的“合并”任务。声明真实的扇入集合。带有
project
的闸门是该项目中的里程碑;若没有
project
,dagr会将其放置在所有输入共同归属的最近项目中。因此,属于
API
的闸门会留在
API
中,连接
API
UI
的闸门会位于其父项目
APP
中,连接无关顶级项目的闸门则是运行级别的里程碑。输入尝试的时间戳不会决定其父项目。每个直接输入会渲染为
(等待)、
(进行中)或
(已完成)(加上正常的阻塞/审核/失败标记)。在窄面板上会聚合这些标记;选中闸门仍会显示确切的输入ID。
当组织归属已知时,优先使用显式的
project
字段;当从输入归属推断更符合实际时,省略该字段。绝不要仅为了在屏幕上移动闸门而添加虚假依赖。
当最后一个输入完成时,追加
promoted
事件——必须包含
at
时间戳
,如同所有事件:
json
{"at": "...", "type": "promoted", "task": "G1", "detail": "扇入完成:L1 ✓, L2 ✓"}
“推进闸门”意味着仍需遵循投影规则:处于
working
/
done
状态的闸门需要自身处于对应状态的尝试——当闸门工作开始时,创建
G1·a1
(若为活跃状态则需locator + liveness字段),并像其他尝试一样附带证据完成它。
attempts: []
的闸门可以是
queued
状态,或在闸门被撤销时设为
canceled
状态。完整文档:
examples/04-gate-promotion.json

Cancel planned work

取消计划中的工作

Set the task to
"state": "canceled"
and give a short
note
; keep all prior attempts and events unchanged. There is no canceled attempt outcome. Because cancellation is not success, update or cancel any task that still depends on it.
将任务状态设为
"state": "canceled"
并添加简短的
note
;保留所有之前的尝试和事件记录。不存在canceled状态的尝试结果。由于取消不等于成功,请更新或取消所有依赖该任务的其他任务。

Declare loop policy (futures), don't imply it

声明循环策略(未来任务),而非隐含

json
"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
points at an existing task (rendered
»
);
node
declares a not-yet-real one (
,
with
loop_back
,
when
attribution
is
predicted
);
after
chains onto a sibling future node of the same policy. dagr renders futures only from this block, and only for working or blocked nodes. Complete document:
examples/05a-policy-declared.json
.
json
"policy": {"rounds_max": 3, "futures": [
  {"on": "pass", "ref": "G1"},
  {"on": "fail", "node": {"id": "L1·a2", "title": "修复轮次", "actor": "l1-dev"}, "loop_back": true},
  {"on": "fail", "streak": 2,
   "node": {"id": "L1x·a1", "title": "升级:新方案", "attribution": "predicted"},
   "after": "L1·a2", "source": "两次失败规则"}
]}
ref
指向已存在的任务(渲染为
»
);
node
声明尚未存在的任务(
,带有
loop_back
时为
attribution
predicted
时为
);
after
连接到同一策略下的其他未来任务节点。dagr从该块渲染未来任务,且仅针对working或blocked状态的节点。完整文档:
examples/05a-policy-declared.json

Materialize a future (when the predicted round actually starts)

实现未来任务(当预测的轮次实际开始时)

policy
is current intent, not history — it is the one block you edit in place. History lives in
attempts
and
events
; those are append-only, the policy is a forecast you keep truthful. When a declared future comes real, do all of this in ONE candidate document (then check → rename):
  1. Remove the consumed future node from
    policy.futures
    . Leaving it would collide with the real attempt's id, which the check rejects.
  2. Repair
    after
    chains
    that targeted the removed node: a sibling future that chained
    "after": "L1·a2"
    now hangs off the task directly (drop its
    after
    ) or off another still-future sibling.
  3. Append the real attempt with the id the future predicted, a
    cause
    pointing at the trigger (
    followup
    ref'ing the failed attempt for a loop-back fix round;
    gate_failed
    when a gate bounced it), locator + liveness if live.
  4. Append the
    attempt_started
    event. Never touch prior attempts or events.
Before/after pair, both strict-clean:
examples/05a-policy-declared.json
examples/05b-policy-materialized.json
.
policy
当前意图,而非历史记录——这是唯一可原地编辑的块。历史记录存储在
attempts
events
中;这些是仅可追加的,而策略是你需要保持真实的预测。当已声明的未来任务变为现实时,请在同一个候选文档中完成以下所有操作(然后检查→重命名):
  1. policy.futures
    中移除已消耗的未来任务节点
    。保留它会与真实尝试的ID冲突,导致检查失败。
  2. 修复指向已移除节点的
    after
    :若某个同级未来任务的
    "after": "L1·a2"
    ,现在需直接连接到任务本身(删除其
    after
    字段)或其他仍为未来状态的同级任务。
  3. 追加真实尝试,使用未来任务预测的ID,
    cause
    指向触发事件(循环修复轮次使用
    followup
    指向失败的尝试;闸门退回时使用
    gate_failed
    ),若为活跃状态则需locator + liveness字段。
  4. 追加
    attempt_started
    事件。绝不要修改之前的尝试或事件记录。
前后对比的严格验证通过文档:
examples/05a-policy-declared.json
examples/05b-policy-materialized.json

Record a human decision

记录人工决策

json
{"at": "...", "type": "directive", "verb": "reject", "by": "operator",
 "task": "L1", "detail": "error paths untested"}
Verbs:
reject · unblock · answer · rule
. Directives are the decisions log; chat prose is not.
json
{"at": "...", "type": "directive", "verb": "reject", "by": "operator",
 "task": "L1", "detail": "错误路径未测试"}
动词包括:
reject · unblock · answer · rule
。指令是决策日志;聊天文本不属于此类。

Answer a question (settle a task by directive)

回答问题(通过指令完成任务)

A queued
question
whose dependencies are done appears as
needs answer
in the attention queue; the producer declares no separate readiness field. A directive event alone cannot settle a task — task state is a projection over attempts, so the human's answer needs an attempt whose actor is the human. Write both in the same candidate:
json
{"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"}}]}
plus the event:
json
{"at": "2026-02-01T09:42:00Z", "type": "directive", "verb": "answer",
 "by": "operator", "task": "Q1", "detail": "retry budget 2 rounds"}
The receipt quotes the answer; the directive is the decision-log entry. The same pattern settles any human-resolved task (an unblock that closes a
question
, a rule that retires a task). Complete document:
examples/07-answer-question.json
.
依赖项已完成的queued状态
question
任务会在关注队列中显示为
needs answer
;生产者无需声明单独的就绪字段。仅靠指令事件无法完成任务——任务状态是尝试记录的投影,因此人类的回答需要一个以人类为执行者的尝试记录。请在同一个候选文档中完成以下内容:
json
{"id": "Q1", "title": "问题:重试预算?", "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": "指令回答:重试预算为2轮"}}]}
加上事件:
json
{"at": "2026-02-01T09:42:00Z", "type": "directive", "verb": "answer",
 "by": "operator", "task": "Q1", "detail": "重试预算为2轮"}
回执引用回答内容;指令是决策日志条目。同一模式适用于任何人工解决的任务(解除阻塞以关闭
question
任务,通过规则终止任务)。完整文档:
examples/07-answer-question.json

Handle an operator message

处理操作员消息

The pane's default action is one contextual message composer. Herdr queues the finished message directly to the
run.orchestrator
locator, so do not build another inbox daemon. You receive an envelope like:
text
[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.
Do this:
  1. Acknowledge the message. Treat the raw prose as instructions about the named target, bounded by the existing run scope.
  2. Obey authority independently of prose:
    recommend_and_return
    means do the analysis and return the choice;
    may_decide_and_continue
    lets you decide and proceed. Never infer the second from wording such as “best guess”.
  3. Treat model, reasoning, and multi-agent requests as ordinary editable instructions and use your normal orchestration tools. dagr runs none of it.
  4. Keep the
    message_id
    through follow-ups. On resolution, append an event:
json
{"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"}
If several messages informed one decision, set
source_messages
on the directive/resolution event. Do not edit
messages.jsonl
; dagr owns that append-only delivery journal. Your event is the durable project-memory link back to it.
面板的默认操作是一个上下文消息编辑器。Herdr会将完成的消息直接发送到
run.orchestrator
指定的位置,因此无需构建其他收件箱守护进程。你会收到如下格式的信封:
text
[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

分别咨询sol5.6·max和fable·xhigh,然后汇总他们的意见。
请执行以下操作:
  1. 确认收到消息。将原始文本视为针对指定目标的指令,且需在现有运行范围内执行。
  2. 独立于文本内容遵守权限规则:
    recommend_and_return
    意味着需执行分析并返回选择;
    may_decide_and_continue
    允许你自行决策并继续。绝不能从“最佳猜测”等措辞中推断出第二种权限。
  3. 将模型、推理和多Agent请求视为普通可编辑指令,并使用你常用的编排工具。dagr不会执行这些操作。
  4. 在后续操作中保留
    message_id
    。完成决议后,追加事件:
json
{"at":"2026-02-01T09:50:00Z","type":"message_resolved","task":"G1",
 "message_id":"msg-0123456789abcdef",
 "detail":"两次独立审核后推荐选项B;等待操作员确认"}
若多个消息影响了一个决策,请在指令/决议事件中设置
source_messages
字段。不要编辑
messages.jsonl
;dagr拥有这个仅可追加的交付日志。你的事件是将决策与日志关联的持久化项目记忆链接。

Customize the three prompt starters when asked

根据需求自定义三个提示启动器

There is no onboarding step. The built-ins (Use judgment, Get guidance, Snooze) are available by default. If the user asks to add or change an action, atomically write
actions.json
beside the run file:
json
{
  "version": 1,
  "include_defaults": true,
  "actions": [
    {"id":"architecture-council", "label":"Architecture council",
     "prompt":"Ask two independent architecture reviewers and synthesize.",
     "authority":"recommend"}
  ]
}
Each action is only a prefilled editable prompt plus
recommend|decide
authority. Keep the list small; prefer one flexible starter over many rigid buttons. An id matching a built-in overrides it.
include_defaults: false
replaces the built-ins. Config version
1
is the supported shape; at most nine starters are shown, labels are capped at 80 bytes, and prompts at 32 KiB. The pane reloads this file automatically and shows a banner for invalid or unsupported configuration.
Old top-level run
actions
values are readable but inert. Do not add them.
无需入门步骤。默认提供内置启动器(Use judgment、Get guidance、Snooze)。若用户要求添加或修改操作,请在运行文件旁原子性地写入
actions.json
json
{
  "version": 1,
  "include_defaults": true,
  "actions": [
    {"id":"architecture-council", "label":"架构委员会",
     "prompt":"咨询两位独立的架构审核者并汇总意见。",
     "authority":"recommend"}
  ]
}
每个操作仅包含预填充的可编辑提示和
recommend|decide
权限。请保持列表简短;优先选择一个灵活的启动器而非多个固定按钮。与内置启动器ID匹配的项会覆盖内置启动器。
include_defaults: false
会替换内置启动器。配置版本
1
是支持的格式;最多显示9个启动器,标签上限为80字节,提示上限为32 KiB。面板会自动重新加载该文件,若配置无效或不支持会显示横幅提示。
旧版顶层运行文件中的
actions
字段仍可读取但已失效。请勿添加该字段。

Lose a runtime

运行时丢失

A pane died mid-work? The attempt is
lost
(no outcome needed), the task projects to
failed
or
blocked
. Do not delete the attempt; the trace is the record.
面板在工作中途崩溃?尝试状态设为
lost
(无需outcome),任务状态投影为
failed
blocked
。请勿删除尝试记录;跟踪记录是重要的历史凭证。

What NOT to do

禁止操作

  • Don't renumber, rewrite, or delete attempts or events. Append.
  • Don't put herdr pane/agent ids in task or attempt ids — they're
    locator
    data, volatile by design.
  • Don't claim
    verified
    without a mechanical receipt. Don't upgrade evidence after the fact without a new event explaining why.
  • Don't encode task state from herdr's view of the world (pane alive ≠ work done). herdr is where, the contract is what is true.
  • Don't mirror every internal agent/tool step; include work an operator needs to understand, steer, or verify.
  • Don't hand-compute "% complete" — write per-attempt
    progress
    (
    {"done": 3, "total": 7, "note": "..."}
    ) and timestamps; analytics are queries over those.
  • 不要重新编号、重写或删除尝试或事件记录。仅可追加。
  • 不要将herdr面板/Agent ID放入任务或尝试ID中——这些属于
    locator
    数据,本质上是易变的。
  • 不要在没有机械回执的情况下声称
    verified
    。若无新事件解释原因,不要事后升级证据层级。
  • 不要从herdr对世界的视图编码任务状态(面板活跃≠工作完成)。herdr是执行位置,契约是真实状态
  • 不要镜像每个内部Agent/工具步骤;仅包含操作员需要理解、控制或验证的工作。
  • 不要手动计算“完成百分比”——为每个尝试记录写入
    progress
    {"done": 3, "total": 7, "note": "..."}
    )和时间戳;分析是基于这些数据的查询。

Field reference

字段参考

The full schema is
CONTRACT.md
(Schema v3 section) in the dagr repo; findings codes are listed there too. When
dagr check --json
names a code you don't recognize, read its message — every finding carries the JSON path of the offending field.
完整的模式定义位于dagr仓库中的
CONTRACT.md
(Schema v3章节);问题代码也列在其中。当
dagr check --json
返回你不熟悉的代码时,请阅读其消息——每个问题都会包含违规字段的JSON路径。