From 3bf0ff9ed80b221f2f553e8fd4c51265b00e9c09 Mon Sep 17 00:00:00 2001 From: Lex Date: Sat, 15 Aug 2026 15:58:40 +0800 Subject: [PATCH] docs: restructure README around the graph, features, and usage Reorganize both language READMEs: a plain-language 'What is the graph' section (node/wave/gate vocabulary) up front, a Features section that collects orchestration, reliability, observability, and beyond-the-graph capabilities (including the v1.0.13-v1.0.15 additions: draft action, execution-location authority, revision view, output file refs), the usage guide, then the engine reference. Engine gains Revisions and Node outputs subsections; the historical/doctrine material moves to a Background section. Wording pass to drop promotional phrasing and inline-bold-label list patterns; workflow-reports/ added to the config table. --- README.md | 108 ++++++++++++++++++++++++++++++++++++--------------- README.zh.md | 95 +++++++++++++++++++++++++++++++------------- 2 files changed, 146 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 1035e64faf..028106958e 100644 --- a/README.md +++ b/README.md @@ -25,34 +25,51 @@ team.** --- -## Graph engineering, before it had a name +## What is the graph -GraphAgent treats a graph as an **executable, durable, inspectable contract** — not a diagram of agents and not a prompt chain with arrows added afterward. The public history landed the first complete DAG-engine commit on **2026-07-02**. The paper [*What makes prompts a graph: necessary and sufficient conditions for prompt graph engineering*](https://arxiv.org/abs/2607.27578), which formalized explicit structure, prompt/topology separation, executable semantics, and the graph as a first-class artifact, appeared on **2026-07-30**. The implementation was already running four weeks before the vocabulary caught up. +A workflow is a set of nodes connected by dependency edges. Each node is a real child session with its own agent and context window; an edge means the downstream node consumes the upstream node's output. Nodes run wave by wave in dependency order, so independent work executes in parallel and dependent work waits. -Our graph-engineering doctrine is operational: +The parent agent (your main conversation) owns the graph. It designs the graph for a task, starts it, and gets woken when a node reports or the workflow finishes — it never polls. When a wave fails, the parent rewrites the failed segment and the engine continues; the view always shows the current graph, not the history of rewrites (more on this under [Revisions](#revisions)). -1. **Edges must carry work.** A dependency exists only when the downstream node consumes the upstream artifact. Delete ceremonial sequencing and run truly independent work in parallel. -2. **A template is a reference topology, not a cage.** The parent agent may expand or prune lanes to match the task, but every prune records its reason and replacement coverage. -3. **Prediction, verification, and merge have different owners.** A `reasoner` simulates likely execution paths, a fresh-context reviewer checks the preceding local wave, and exactly one arbiter owns the verdict. These gates cannot be pruned. -4. **Iteration is a bounded local graph rewrite.** `PASS` finalizes, `LOOP` adds a new correction/review wave through pause → replan → resume, and `BLOCKED` stops with evidence. Completed nodes never form a hidden cycle. -5. **Reality outranks self-report.** State is event-sourced, recovery follows durable evidence, tests and code settle claims, and humans retain pause/step/cancel/replan authority where mistakes are expensive. +Three terms worth knowing: -Curated reference topologies — design decision deep-dive, parallel project delivery, deep review of an existing subsystem, compact change review — ship through the workflow library's global scope (curated by the [`opencode-dag-config`](https://github.com/LeXwDeX/opencode-dag-config) repo) and a builtin tier embedded in release binaries; the project `.opencode/workflows/` directory holds repo-specific specs. `/dag-flow` picks the closest shape by name, injects the current task, and derives the actual DAG while preserving its fail-closed gates. See the [Graph Engineering workflow catalog](./.opencode/workflows/GRAPH-ENGINEERING.md). +- **Node** — one unit of work: an `explore`, `build`, `general`, or custom agent running one prompt, with optional timeout, retry budget, and structured-output contract. +- **Wave** — the set of nodes whose dependencies are all satisfied; a wave runs in parallel up to a concurrency limit. +- **Gate** — a node whose job is judgment (review, verification, arbitration). Gates emit verdicts (`ACCEPT` / `REVISE` / `REJECT` / `BLOCKED`) and downstream nodes can be conditioned on the verdict. -## Why a DAG +## Features -A single agent loop struggles once a task has staged dependencies, parallelizable independent work, or a quality gate in the middle. Four judgments shaped this engine: +**Orchestration** -1. **Split decisions from volume.** Work that must be correct (decomposition, gates, arbitration, final synthesis) runs on an advanced model tier; volume work (exploration, implementation, per-angle analysis) fans out on a standard tier. The standard tier buys accuracy with redundancy: breadth means independent parallel slices fanning into one arbiter, depth means claims get re-verified against code and tests across waves. -2. **Ask before building the graph.** Complex work (`deep` mode) goes through a bounded Q&A pass first (1, 3, or 5 rounds), producing a versioned, fingerprinted Requirement Brief with a `READY` / `NOT_READY` / `WAIVED` verdict. If the question budget runs out with blockers still open, the verdict is `NOT_READY`. There is no silent pass. -3. **Gate verdicts need a follow-up.** When a checkpoint returns `REVISE` / `REJECT` / `BLOCKED`, the parent agent has to dispose of it in the same wake turn: extend, replan, start a new workflow, or stop with stated reasons. Summarizing the verdict and ending the turn counts as an orchestration failure under the contract. -4. **Recover from evidence, not guesses.** Every state change is a durable event, transitions go through a declared state machine's guards, terminal states are irreversible (one exception, written into the spec), and the read model is a CQRS projection. After a crash, recovery reconciles from durable evidence and never fabricates provider work. +- Composable blocks (`explore`, `plan`, `prototype`, `debug`, `coding`, `verify`, `review`, `synthesize`) compile into the node graph; low-level node fields remain available for anything blocks cannot express. +- `workflow(action="draft")` renders a structured graph through the tool schema into a validated YAML spec — field-name mistakes are rejected by the provider, not discovered at validation time. +- Saved workflow libraries at three scopes (project / global / builtin), startable by name; the `/dag-flow` command picks a curated reference topology and retargets it to the task at hand. +- Model tiers in `dag.jsonc` separate decisions from volume: critical nodes on the `advanced` model, fan-out work on `standard`. + +**Reliability** + +- Event-sourced state with a declared state machine; the SQLite read model is projected inside the publish transaction. Crash recovery reconciles from durable evidence and never fabricates provider work. +- Execution-location authority: each workflow row carries the directory stamp of the session that created it, re-read from the database on every ownership check, so sibling worktrees of one project cannot act on each other's workflows. Stamps move with the session when it moves. +- Revision view: a replan supersedes the replaced segment — the inspector, status output, and summary counts render the current graph only. Live failures on the current graph (quota exhausted, API error, timeout cap) stay visible; superseded ones do not count toward the workflow's terminal state. +- Node outputs that are a single absolute file path are captured as `{content_ref, size, sha256, summary}`; the result action returns the pointer and the parent reads the file. Inline and structured payloads unchanged. + +**Observability & control** + +- TUI DAG inspector (`dag.open` in the command palette): workflow list, wave-ordered node view with live status, node detail with deadline countdown; `p`/`r`/`s`/`x` for pause/resume/step/cancel, `enter` drops into a node's child session. +- Sidebar panel with per-session progress; HTTP API mirroring every tool action (see [below](#observing--controlling)). +- Deep mode admission: a bounded Q&A pass (1/3/5 rounds) produces a fingerprinted Requirement Brief with a `READY` / `NOT_READY` / `WAIVED` verdict before an expensive graph starts. + +**Beyond the graph** + +- Autonomous goal loop (`/goal`): one durable goal worked across turns of a single session, judged externally, budgeted and resumable. +- Claude Code hooks compatibility (26 events × 5 execution types), CJK/IME terminal fixes, per-workflow worktree isolation, and a standalone Go configuration assistant. ## Using workflows Nothing has to be configured to try it: ask for work that has stages, parallel -parts, or a review gate in the middle, and the agent designs a graph and runs -it. Three things turn that into a repeatable setup of your own. +parts, or a review gate in the middle (`/dag-flow `), and the agent +designs a graph and runs it. Three things turn that into a repeatable setup of +your own. ### 1. Choose the model tiers — `.opencode/dag.jsonc` @@ -130,14 +147,16 @@ scopes, the file shape, the rules a saved spec must respect (no pinned models, `worker_type` must exist, required template variables must be supplied), and how to verify it. Ask to "save this as a reusable workflow" and the agent establishes the phases and gates with you, writes the file into the scope you -pick, and proves it by starting it once. +pick, and proves it by starting it once. For one-off graphs, `workflow(action="draft")` +takes the structured graph as tool parameters and hands back a validated +`spec_path`, so field drift never reaches the file. Node prompts come from `.opencode/dag-prompts/*.md` — 12 templates ship in-repo, referenced by `prompt_template.id`. Add your own `.md` file there to make a new template available; a global workflow should prefer `inline` prompts so it does not depend on a repo-local template. -## DAG workflow engine +## The engine The engine lives in [`packages/core/src/dag`](./packages/core/src/dag) (state machine, dependency graph, scheduling, event projection, SQLite read model) and [`packages/opencode/src/dag`](./packages/opencode/src/dag) (workflow service, execution loop, node spawn, admission, review lifecycle, crash recovery, templates). Agents drive it through a single `workflow` tool; humans watch and control it through the TUI or HTTP API. @@ -162,16 +181,26 @@ Workflow-level knobs: `max_concurrency` (default 5), `max_node_replan_attempts` ### Scheduling & execution - Nodes spawn as real child sessions through the same code path as the `task` tool, wave by wave in dependency order, bounded by a concurrency semaphore. A node is durably `queued` at admission and the child session only materializes inside the permit, so a 100-node fan-out never creates 100 sessions at once. -- **Dynamic replanning**, pause-first: `pause` freezes scheduling instantly, `replan` merges a fragment (add / replace / cancel / restart nodes) atomically against the live graph, `resume` continues. Terminal nodes are immutable; retrying a failed node means adding a replacement under a new id. `extend` appends nodes, and may reopen a naturally-completed workflow (the single sanctioned exception to terminal irreversibility). -- **Step mode** runs one node at a time for debugging. -- **The parent does not poll.** Synthetic messages wake it when a `report_to_parent` node or the workflow terminalizes. Checkpoint nodes emit a normalized verdict (`ACCEPT` / `REVISE` / `REJECT` / `BLOCKED`), and the disposal contract governs what happens next. Iteration is a bounded, verdict-driven replan wave; the graph never contains a cyclic edge. +- Dynamic replanning, pause-first: `pause` freezes scheduling instantly, `replan` merges a fragment (add / replace / cancel / restart nodes) atomically against the live graph, `resume` continues. Terminal nodes are immutable; retrying a failed node means adding a replacement under a new id. `extend` appends nodes, and may reopen a naturally-completed workflow (the single sanctioned exception to terminal irreversibility). +- Step mode runs one node at a time for debugging. +- The parent does not poll. Synthetic messages wake it when a `report_to_parent` node or the workflow terminalizes. Checkpoint nodes emit a normalized verdict (`ACCEPT` / `REVISE` / `REJECT` / `BLOCKED`), and the disposal contract governs what happens next. Iteration is a bounded, verdict-driven replan wave; the graph never contains a cyclic edge. + +### Revisions + +Rewriting a graph does not erase history, but it does retire it. A replan that supersedes nodes marks them; the workflow row carries a `graph_rev` counter and every view — status output, HTTP API, TUI inspector, summary counts — filters to the current revision. Completed nodes and their outputs survive into the new revision untouched. Audit of superseded nodes stays possible through the result store (an agent can look it up by node id); the TUI exposes no entry to it. + +Two things this fixes. A workflow whose failed segment was rewritten now reports `completed` when the replacement succeeds, instead of dragging the old failure along. And the failure counts you see are the failures that exist — a quota exhaustion or API error on the current graph stays visible until it is actually fixed. ### State machine & persistence - Declared transition tables for workflow and node status; every mutation goes through a guard, invalid transitions and terminal violations are typed errors (HTTP 409, not 500). - All changes are published as durable `dag.*` events; a projector writes the SQLite read model *inside* the publish transaction. History is event replay, not a log table. A drift test fails whenever the projector's guards and the declared transition tables are edited out of sync. -- **Crash recovery** is lazy, per-workflow, and evidence-based: nodes left `running` are reconciled against their child session's durable state. Sessions that finished back-fill their captured output; when execution ownership was genuinely lost, the workflow pauses and the parent decides disposition (replan / resume / cancel). Recovery never adopts or restarts provider work on its own. -- **Failure triage**: every failed node carries a failure class (`timeout` / `exec_failed` / `verdict_fail`) surfaced in `workflow(action=status)` and in the parent's wake — including a failed-nodes attribution digest when a workflow terminalizes failed — so the parent agent repairs the specific node (replan with a replacement under a new id, or a continuation workflow reusing completed outputs) instead of restarting the graph. +- Crash recovery is lazy, per-workflow, and evidence-based: nodes left `running` are reconciled against their child session's durable state. Sessions that finished back-fill their captured output; when execution ownership was genuinely lost, the workflow pauses and the parent decides disposition (replan / resume / cancel). Recovery never adopts or restarts provider work on its own. +- Failure triage: every failed node carries a failure class (`timeout` / `exec_failed` / `verdict_fail`) surfaced in `workflow(action=status)` and in the parent's wake — including a failed-nodes attribution digest when a workflow terminalizes failed — so the parent agent repairs the specific node (replan with a replacement under a new id, or a continuation workflow reusing completed outputs) instead of restarting the graph. + +### Node outputs + +A node's final reply can be plain text, a structured payload (`output_schema` + `submit_result`), or a file. When the reply is a single absolute path to an existing non-empty file, the runtime captures `{content_ref, size, sha256, summary}`; `workflow(action="result")` returns the pointer with a short summary and the parent reads the file itself. This keeps long reports out of the transcript while preserving integrity (the hash is recorded at capture time). Report files written under `.opencode/workflow-reports/` get an append-only `.gitignore` entry on first write. ### Deep mode: admission & review @@ -180,9 +209,9 @@ Workflow-level knobs: `max_concurrency` (default 5), `max_node_replan_attempts` ### Observing & controlling -- **TUI DAG inspector** (command palette → `dag.open`): workflow list, wave-ordered node view with live status, node detail (deps, errors, output preview, deadline countdown), and `p`/`r`/`s`/`x` for pause/resume/step/cancel; `enter` drops into a node's child session. -- **Sidebar panel**: per-session workflow progress (completed/running/failed/queued), expandable node list, driven by ephemeral summary events, with a fetch-on-open safety net instead of polling. -- **HTTP API** (same code path as the tool surface): +- TUI DAG inspector (command palette → `dag.open`): workflow list, wave-ordered node view with live status, node detail (deps, errors, output preview, deadline countdown), and `p`/`r`/`s`/`x` for pause/resume/step/cancel; `enter` drops into a node's child session. +- Sidebar panel: per-session workflow progress (completed/running/failed/queued), expandable node list, driven by ephemeral summary events, with a fetch-on-open safety net instead of polling. +- HTTP API (same code path as the tool surface): ``` GET /dag list workflows @@ -206,10 +235,27 @@ Everything else inherits the main opencode configuration. | `.opencode/dag.jsonc` | Model tiers (`advanced` / `standard`) and `thinking_depth` for child sessions | `/dag.jsonc`, seeded with comments on first use | | `.opencode/workflows/*.yaml` | Saved workflow specs, startable by name | `/workflows/*.yaml` | | `.opencode/dag-prompts/*.md` | Node prompt templates referenced by `prompt_template.id` | — (project-scoped) | +| `.opencode/workflow-reports/` | Node report files (auto-gitignored) | — | Both `dag.jsonc` and the workflow library are read lazily, so an edit applies to the next workflow start without a restart. +## Background + +GraphAgent treats a graph as an executable, durable, inspectable contract — not a diagram of agents, and not a prompt chain with arrows added afterward. The first complete DAG-engine commit landed on **2026-07-02**; the paper [*What makes prompts a graph*](https://arxiv.org/abs/2607.27578), which formalized explicit structure, prompt/topology separation, executable semantics, and the graph as a first-class artifact, appeared on **2026-07-30**. + +The operating doctrine that came out of it: + +1. Edges must carry work. A dependency exists only when the downstream node consumes the upstream artifact; ceremonial sequencing gets deleted, independent work runs in parallel. +2. A template is a reference topology, not a cage. The parent agent may expand or prune lanes to match the task, but every prune records its reason and replacement coverage. +3. Prediction, verification, and merge have different owners. A `reasoner` simulates likely execution paths, a fresh-context reviewer checks the preceding local wave, and exactly one arbiter owns the verdict. These gates cannot be pruned. +4. Iteration is a bounded local graph rewrite. `PASS` finalizes, `LOOP` adds a new correction/review wave through pause → replan → resume, and `BLOCKED` stops with evidence. Completed nodes never form a hidden cycle. +5. Reality outranks self-report. State is event-sourced, recovery follows durable evidence, tests and code settle claims, and humans retain pause/step/cancel/replan authority where mistakes are expensive. + +Why a DAG at all: a single agent loop struggles once a task has staged dependencies, parallelizable independent work, or a quality gate in the middle. Splitting decisions from volume (advanced vs. standard tiers), asking before building (deep-mode admission), gating verdicts with mandatory disposal, and recovering from evidence rather than guesses are the four judgments this engine is built on. + +Curated reference topologies — design decision deep-dive, parallel project delivery, deep review of an existing subsystem, compact change review — ship through the workflow library's global scope (curated by the [`opencode-dag-config`](https://github.com/LeXwDeX/opencode-dag-config) repo) and a builtin tier embedded in release binaries. See the [Graph Engineering workflow catalog](./.opencode/workflows/GRAPH-ENGINEERING.md). + --- ## Autonomous goal loop (`/goal`) @@ -218,10 +264,10 @@ Graph orchestration decomposes a task across child sessions; the goal loop is its single-session complement: one durable goal that the agent works toward autonomously across turns of the current session. -- **Commands**: `/goal ` sets a goal and starts the loop; `/goal status|pause|resume|done|clear|stop` controls it; `/subgoal |list|remove |clear` manages subgoals attached to the active goal. -- **Judge loop**: after each turn an external judge evaluates progress — `done` clears the goal, `continue` injects the next continuation turn against a configurable turn budget (budget exhaustion pauses the goal; it stays resumable). The agent can self-declare completion with the `goal(action: "complete")` tool, which bypasses the judge; `goal(action: "status")` inspects state. -- **Visibility**: while a goal is active or paused, the system prompt carries a live goal block (text, status, turns used/remaining, subgoals, last judge verdict); the TUI sidebar shows a compact goal widget; `GET /session/:sessionID/goal` exposes the state (`404` when no goal is set). -- **Durability**: goal state is persisted per session (`goal_state`), survives restarts, and is cleared automatically when the session is deleted. +- Commands: `/goal ` sets a goal and starts the loop; `/goal status|pause|resume|done|clear|stop` controls it; `/subgoal |list|remove |clear` manages subgoals attached to the active goal. +- Judge loop: after each turn an external judge evaluates progress — `done` clears the goal, `continue` injects the next continuation turn against a configurable turn budget (budget exhaustion pauses the goal; it stays resumable). The agent can self-declare completion with the `goal(action: "complete")` tool, which bypasses the judge; `goal(action: "status")` inspects state. +- Visibility: while a goal is active or paused, the system prompt carries a live goal block (text, status, turns used/remaining, subgoals, last judge verdict); the TUI sidebar shows a compact goal widget; `GET /session/:sessionID/goal` exposes the state (`404` when no goal is set). +- Durability: goal state is persisted per session (`goal_state`), survives restarts, and is cleared automatically when the session is deleted. --- diff --git a/README.zh.md b/README.zh.md index e576001031..babc3017b5 100644 --- a/README.zh.md +++ b/README.zh.md @@ -19,32 +19,48 @@ GraphAgent 是本项目对外的产品名;仓库以 **OpenCode-GraphAgent** --- -## 「Graph Engineering」这个名字出现前,GraphAgent 已经在跑 +## graph 是什么 -在 GraphAgent 里,graph 是一份**可执行、可持久化、可观测、可控制的契约**。节点干活,边传产物,运行时负责调度和恢复。公开 Git 历史显示,第一版完整 DAG 引擎在 **2026-07-02** 已经提交;论文 [*What makes prompts a graph: necessary and sufficient conditions for prompt graph engineering*](https://arxiv.org/abs/2607.27578) 到 **2026-07-30** 才把显式结构、prompt/拓扑分离、可执行语义和 graph 一等制品化归纳出来。术语晚了四周,工具没有等它。 +一个工作流就是一组节点加依赖边。每个节点是一个真实的子会话,有自己的智能体和上下文窗口;一条边意味着下游节点要消费上游节点的产出。节点按依赖顺序逐波执行:能并行的并行跑,有依赖的等着。 -这套东西落到了五条运行规则里: +父智能体(你的主对话)拥有这张图。它为任务设计图、启动图,然后在节点汇报或工作流收尾时被唤醒——全程不轮询。某个波次失败了,父智能体重写出错的那一段,引擎接着跑;你看到的永远是当前的图,不是改写历史(见[修订视图](#修订视图))。 -1. **每条边都要有用。** 如果下游不读上游产物,这条依赖就该删。真正独立的工作直接并行。 -2. **模板是参考拓扑,不是固定脚本。** 主 Agent 可以按任务扩展或剪枝,但每次剪枝都必须写明理由和替代覆盖证据。 -3. **推演、复审、裁决分开做。** `reasoner` 模拟潜在执行路径,fresh-context reviewer 复审前一个局部波次,最后由唯一 arbiter 给出裁决;这些门禁不可剪掉。 -4. **迭代是有界的局部改图。** `PASS` 才能定稿,`LOOP` 通过 pause → replan → resume 增加新的修正与复审波次,`BLOCKED` 带证据停止;终态节点不会被伪装成环。 -5. **代码和测试说了算。** 状态变更写入事件,崩溃恢复只认持久化证据。到了代价高的边界,人可以 pause、step、cancel 或 replan。 +三个值得知道的词: + +- **节点(node)**——一个工作单元:`explore`、`build`、`general` 或自定义智能体跑一个 prompt,可以带超时、重试预算和结构化输出契约。 +- **波次(wave)**——依赖全部就绪的那批节点;一次波次内并行执行,受并发上限约束。 +- **门禁(gate)**——职责是做判断的节点(审查、验证、仲裁)。门禁输出裁定(`ACCEPT` / `REVISE` / `REJECT` / `BLOCKED`),下游节点可以基于裁定条件执行。 + +## 特性 -强约束参考拓扑——设计决策深挖、并行项目落地、已完成子系统深度 Review、轻量变更审查——随工作流库分发:全局作用域由 [`opencode-dag-config`](https://github.com/LeXwDeX/opencode-dag-config) 仓库维护,正式版的二进制里还有内嵌的 builtin 层;项目的 `.opencode/workflows/` 存放本仓库专属 spec。`/dag-flow` 按名字选最接近的样板,注入本次任务,再派生实际 DAG;可以扩展和剪枝,但不能绕过 fail-closed 门禁。入口见 [Graph Engineering 工作流目录](./.opencode/workflows/GRAPH-ENGINEERING.md)。 +**编排** -## 为什么是 DAG +- 可组合块(`explore`、`plan`、`prototype`、`debug`、`coding`、`verify`、`review`、`synthesize`)编译成节点图;块表达不了的还有低级节点字段兜底。 +- `workflow(action="draft")` 通过工具参数传结构化图,harness 渲染并校验出 YAML spec——字段写错在 provider 侧就被拒,不用等到校验才发现。 +- 三级作用域的工作流库(项目 / 全局 / 内嵌),按名字启动;`/dag-flow` 命令挑选合适的参考拓扑并注入当前任务。 +- `dag.jsonc` 的模型分层把决策和跑量分开:关键节点用 `advanced` 模型,扇出跑量用 `standard`。 -任务一旦涉及分阶段依赖、可并行的独立工作,或者中间需要一道质量门禁,单智能体循环就不太够用了。这个引擎的设计基于四个判断: +**可靠性** -1. **决策和跑量分开。** 必须做对的事(任务分解、门禁、仲裁、最终综合)交给 advanced 模型层;量大的事(探索、实现、分角度分析)在 standard 层扇出。standard 层靠冗余换精度:横向是独立并行的切片汇入一个仲裁节点,纵向是结论跨波次对照代码和测试重新验证。 -2. **先把需求问清楚,再建图。** 复杂任务(`deep` 模式)建图前要过一轮有界问答(1、3 或 5 轮),产出带版本号和指纹的 Requirement Brief,裁定只有 `READY`、`NOT_READY`、`WAIVED` 三种。轮数用完还有阻塞问题,结果就是 `NOT_READY`,不会悄悄放行。 -3. **门禁结论必须有下文。** 检查点返回 `REVISE` / `REJECT` / `BLOCKED` 时,父智能体要在同一个唤醒回合里处置它:extend、replan、开新工作流,或者说明理由后停下。只复述结论就结束回合,按契约算编排失败。 -4. **恢复靠证据,不靠猜。** 所有状态变更都是持久化事件,状态转换要过声明式状态机的守卫,终态不可逆(只有一个写进规范的例外),读模型是 CQRS 投影。崩溃后只依据持久化证据和解现场,不会凭空重放模型调用。 +- 事件溯源状态加声明式状态机;SQLite 读模型在发布事务内投影。崩溃恢复只认持久化证据,不会凭空重放模型调用。 +- 执行位置所有权:每条工作流行带着创建会话的目录戳,每次所有权检查都重读数据库,同一项目的兄弟 worktree 之间不能替对方收养、唤醒、派生。会话迁移时戳跟着走。 +- 修订视图:replan 替换掉的段落自动退场——检查器、状态输出、摘要计数都只渲染当前图。当前图上活着的失败(配额耗尽、API 报错、超时上限)照常显示;被替换的失败不再计入工作流终态。 +- 节点输出是一个绝对文件路径时,捕获为 `{content_ref, size, sha256, summary}`;result 动作返回指针,父智能体自己去读文件。内联文本和结构化输出不受影响。 + +**可观测与控制** + +- TUI DAG 检查器(命令面板 → `dag.open`):工作流列表、按波次排序的节点实时视图、带截止倒计时的节点详情;`p`/`r`/`s`/`x` 对应暂停/恢复/单步/取消,`enter` 进入节点的子会话。 +- 侧边栏面板按会话展示进度;HTTP API 覆盖全部工具动作(见[下文](#观察与控制))。 +- deep 模式准入:昂贵的图启动前先过一轮有界问答(1/3/5 轮),产出带指纹的 Requirement Brief,裁定只有 `READY` / `NOT_READY` / `WAIVED` 三种。 + +**图之外** + +- 自主目标循环(`/goal`):单会话内跨回合推进一个持久目标,外部评审、有预算、可恢复。 +- Claude Code hooks 兼容(26 事件 × 5 种执行类型)、CJK/IME 终端修复、按工作流的 worktree 隔离、独立的 Go 配置助手。 ## 工作流怎么用 -不配置也能直接试:给它一件有阶段、有可并行部分、或者中间需要一道审查门禁的活,智能体自己会建图并跑起来。想把它变成你自己的一套固定流程,有三件事: +不配置也能直接试:给它一件有阶段、有可并行部分、或者中间需要一道审查门禁的活(`/dag-flow <任务>`),智能体自己会建图并跑起来。想把它变成你自己的一套固定流程,有三件事: ### 1. 选定模型分层 —— `.opencode/dag.jsonc` @@ -104,11 +120,11 @@ config: ### 3. 让智能体替你写 -内置的 **`create-dag-workflow`** skill 覆盖 spec 编写:工作流库作用域、文件结构、存盘 spec 必须守的规矩(不能钉死模型、`worker_type` 必须存在、模板必需变量必须给全),以及怎么验证。说一句「把这个存成可复用的工作流」,智能体会先跟你确认阶段和门禁,把文件写进你选的作用域,再真跑一次证明它能用。 +内置的 **`create-dag-workflow`** skill 覆盖 spec 编写:工作流库作用域、文件结构、存盘 spec 必须守的规矩(不能钉死模型、`worker_type` 必须存在、模板必需变量必须给全),以及怎么验证。说一句「把这个存成可复用的工作流」,智能体会先跟你确认阶段和门禁,把文件写进你选的作用域,再真跑一次证明它能用。临时图则用 `workflow(action="draft")`:结构化图走工具参数,返回校验过的 `spec_path`,字段漂移到不了文件。 节点 prompt 来自 `.opencode/dag-prompts/*.md` —— 随仓库附带 12 个,通过 `prompt_template.id` 引用。往那儿加一个 `.md` 就多一个模板;全局工作流建议用 `inline` prompt,否则会依赖某个仓库本地的模板。 -## DAG 工作流引擎 +## 引擎 引擎位于 [`packages/core/src/dag`](./packages/core/src/dag)(状态机、依赖图、调度、事件投影、SQLite 读模型)和 [`packages/opencode/src/dag`](./packages/opencode/src/dag)(工作流服务、执行循环、节点生成、准入、审查生命周期、崩溃恢复、模板)。智能体通过单个 `workflow` 工具驱动它;人通过 TUI 或 HTTP API 观察和控制它。 @@ -133,16 +149,26 @@ config: ### 调度与执行 - 节点通过与 `task` 工具相同的代码路径生成真实子会话,按依赖顺序逐波执行,由并发信号量约束。节点在准入时持久化为 `queued`,子会话拿到并发许可后才创建,所以 100 个节点的扇出不会一次拉起 100 个会话。 -- **动态重规划**,暂停优先:`pause` 立即冻结调度,`replan` 将片段(添加 / 替换 / 取消 / 重启节点)原子性合并进运行中的图,`resume` 继续。终态节点不可变,想重试失败的节点,就换个新 id 加一个替代节点。`extend` 追加节点,也允许重新打开一个自然完成的工作流(终态不可逆的唯一例外,写进了规范)。 -- **单步模式**逐节点执行,便于调试。 -- **父智能体不轮询。** `report_to_parent` 节点或工作流到达终态时,引擎用合成消息唤醒父智能体。检查点节点输出规范化裁定(`ACCEPT` / `REVISE` / `REJECT` / `BLOCKED`),下一步走向由处置契约约束。迭代是一轮轮有界的、由裁定触发的重规划,图里不存在环形边。 +- 动态重规划,暂停优先:`pause` 立即冻结调度,`replan` 将片段(添加 / 替换 / 取消 / 重启节点)原子性合并进运行中的图,`resume` 继续。终态节点不可变,想重试失败的节点,就换个新 id 加一个替代节点。`extend` 追加节点,也允许重新打开一个自然完成的工作流(终态不可逆的唯一例外,写进了规范)。 +- 单步模式逐节点执行,便于调试。 +- 父智能体不轮询。`report_to_parent` 节点或工作流到达终态时,引擎用合成消息唤醒父智能体。检查点节点输出规范化裁定(`ACCEPT` / `REVISE` / `REJECT` / `BLOCKED`),下一步走向由处置契约约束。迭代是一轮轮有界的、由裁定触发的重规划,图里不存在环形边。 + +### 修订视图 + +改图不会抹掉历史,但会让历史退场。replan 替换节点时会把它们标记为已替代;工作流行带一个 `graph_rev` 计数器,所有视图——状态输出、HTTP API、TUI 检查器、摘要计数——都只过滤当前修订。已完成节点和它们的产出原样存活进新修订。被替代节点的审计仍然可达(智能体按节点 id 从 result store 查),TUI 不提供入口。 + +这修好了两件事。出过错但已改写的段落,替换者成功后工作流报 `completed`,不再拖着旧失败。你看到的失败计数就是实际存在的失败——当前图上的配额耗尽或 API 报错会一直显示,直到它真的被修掉。 ### 状态机与持久化 - 工作流和节点状态各有声明式转换表;所有变更先过守卫,非法转换和终态违规是类型化错误(HTTP 返回 409 而非 500)。 - 所有变更以持久化 `dag.*` 事件发布;投影器在发布事务*内部*写入 SQLite 读模型。历史来自事件回放,没有日志表。另有一个漂移测试盯着投影器守卫和声明的转换表,改了一边没改另一边,测试会挂。 -- **崩溃恢复**是惰性的、按工作流、基于证据:残留 `running` 的节点对照其子会话的持久化状态和解。子会话已经跑完的,回填捕获输出;执行权确实丢了的,工作流转入暂停,交给父智能体决定处置(replan / resume / cancel)。恢复过程不会自行接管或重启模型调用。 -- **失败分诊**:每个失败节点都带失败分类(`timeout` / `exec_failed` / `verdict_fail`),暴露在 `workflow(action=status)` 和父智能体收到的唤醒里——工作流终态失败时还带失败节点归因摘要——父智能体据此定向修复具体节点(replan 换新 id 的替代节点,或复用已完成输出开续跑工作流),而不是整图重启。 +- 崩溃恢复是惰性的、按工作流、基于证据:残留 `running` 的节点对照其子会话的持久化状态和解。子会话已经跑完的,回填捕获输出;执行权确实丢了的,工作流转入暂停,交给父智能体决定处置(replan / resume / cancel)。恢复过程不会自行接管或重启模型调用。 +- 失败分诊:每个失败节点都带失败分类(`timeout` / `exec_failed` / `verdict_fail`),暴露在 `workflow(action=status)` 和父智能体收到的唤醒里——工作流终态失败时还带失败节点归因摘要——父智能体据此定向修复具体节点(replan 换新 id 的替代节点,或复用已完成输出开续跑工作流),而不是整图重启。 + +### 节点输出 + +节点的最终回复可以是纯文本、结构化结果(`output_schema` + `submit_result`),或者一个文件。回复是一个存在的非空文件的绝对路径时,运行时捕获 `{content_ref, size, sha256, summary}`;`workflow(action="result")` 返回指针和短摘要,父智能体自己去读文件。长报告不再挤进转录,完整性由捕获时记录的哈希保证。写到 `.opencode/workflow-reports/` 下的报告文件,首次写入时自动补一条追加式的 `.gitignore` 条目。 ### deep 模式:准入与审查 @@ -151,9 +177,9 @@ config: ### 观察与控制 -- **TUI DAG 检查器**(命令面板 → `dag.open`):工作流列表、按波次排序的节点视图(实时状态)、节点详情(依赖、错误、输出预览、截止倒计时),`p`/`r`/`s`/`x` 对应暂停/恢复/单步/取消,`enter` 进入节点的子会话。 -- **侧边栏面板**:按会话展示工作流进度(完成/运行/失败/排队),可展开节点列表,由瞬态摘要事件驱动,打开时再拉一次兜底,不做轮询。 -- **HTTP API**(与工具入口共用同一代码路径): +- TUI DAG 检查器(命令面板 → `dag.open`):工作流列表、按波次排序的节点视图(实时状态)、节点详情(依赖、错误、输出预览、截止倒计时),`p`/`r`/`s`/`x` 对应暂停/恢复/单步/取消,`enter` 进入节点的子会话。 +- 侧边栏面板:按会话展示工作流进度(完成/运行/失败/排队),可展开节点列表,由瞬态摘要事件驱动,打开时再拉一次兜底,不做轮询。 +- HTTP API(与工具入口共用同一代码路径): ``` GET /dag 列出工作流 @@ -175,9 +201,26 @@ DAG 相关的东西都放在 `.opencode/` 下,在 opencode 配置目录(`OPE | `.opencode/dag.jsonc` | 模型分层(`advanced` / `standard`)与子会话 `thinking_depth` | `<配置目录>/dag.jsonc`,首次使用时生成带注释的默认文件 | | `.opencode/workflows/*.yaml` | 存盘的工作流 spec,可按名字启动 | `<配置目录>/workflows/*.yaml` | | `.opencode/dag-prompts/*.md` | 由 `prompt_template.id` 引用的节点 prompt 模板 | ——(仅项目级) | +| `.opencode/workflow-reports/` | 节点报告文件(自动 gitignore) | —— | `dag.jsonc` 和工作流库都是惰性读取,改完下一次启动工作流就生效,不用重启。 +## 背景 + +在 GraphAgent 里,graph 是一份可执行、可持久化、可观测、可控制的契约。公开 Git 历史显示,第一版完整 DAG 引擎在 **2026-07-02** 已经提交;论文 [*What makes prompts a graph*](https://arxiv.org/abs/2607.27578) 到 **2026-07-30** 才把显式结构、prompt/拓扑分离、可执行语义和 graph 一等制品化归纳出来。术语晚了四周,工具没有等它。 + +落到五条运行规则: + +1. **每条边都要有用。** 如果下游不读上游产物,这条依赖就该删。真正独立的工作直接并行。 +2. **模板是参考拓扑,不是固定脚本。** 主 Agent 可以按任务扩展或剪枝,但每次剪枝都必须写明理由和替代覆盖证据。 +3. **推演、复审、裁决分开做。** `reasoner` 模拟潜在执行路径,fresh-context reviewer 复审前一个局部波次,最后由唯一 arbiter 给出裁决;这些门禁不可剪掉。 +4. **迭代是有界的局部改图。** `PASS` 才能定稿,`LOOP` 通过 pause → replan → resume 增加新的修正与复审波次,`BLOCKED` 带证据停止;终态节点不会被伪装成环。 +5. **代码和测试说了算。** 状态变更写入事件,崩溃恢复只认持久化证据。到了代价高的边界,人可以 pause、step、cancel 或 replan。 + +至于为什么是 DAG:任务一旦涉及分阶段依赖、可并行的独立工作,或者中间需要一道质量门禁,单智能体循环就不太够用了。决策和跑量分开(advanced/standard 分层)、建图前先问清楚(deep 准入)、门禁结论必须有下文(处置契约)、恢复靠证据不靠猜——这四个判断就是这个引擎的地基。 + +强约束参考拓扑——设计决策深挖、并行项目落地、已完成子系统深度 Review、轻量变更审查——随工作流库分发:全局作用域由 [`opencode-dag-config`](https://github.com/LeXwDeX/opencode-dag-config) 仓库维护,正式版的二进制里还有内嵌的 builtin 层。入口见 [Graph Engineering 工作流目录](./.opencode/workflows/GRAPH-ENGINEERING.md)。 + --- ## 自主目标循环(`/goal`)