A coding agent that decomposes a task into a dependency graph of child agents and drives it to completion. State is durable, crashes are recoverable, and the whole graph can be inspected and controlled from the terminal.
GraphAgent is the product name for this project; the repository is published as OpenCode-GraphAgent, a fork of the MIT-licensed opencode terminal AI agent that adds the DAG workflow engine. Not affiliated with or endorsed by the OpenCode team.
Important
GraphAgent v1 is in focused maintenance. Maintenance is limited to DAG configuration, curated workflow templates, and reproducible defects reported through GitHub Issues. Architecture defects that affect stability, data integrity, or the core DAG experience will receive narrowly scoped compatibility patches; v1 will not take new platform features or large foundational refactors. A successor project will redesign the runtime independently, without extending v1's compatibility constraints.
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.
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).
Three terms worth knowing:
- 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.
Orchestration
- 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-flowcommand picks a curated reference topology and retargets it to the task at hand. - Model tiers in
dag.jsoncseparate decisions from volume: critical nodes on theadvancedmodel, fan-out work onstandard.
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.openin the command palette): workflow list, wave-ordered node view with live status, node detail with deadline countdown;p/r/s/xfor pause/resume/step/cancel,enterdrops into a node's child session. - Sidebar panel with per-session progress; HTTP API mirroring every tool action (see below).
- Deep mode admission: a bounded Q&A pass (1/3/5 rounds) produces a fingerprinted Requirement Brief with a
READY/NOT_READY/WAIVEDverdict 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.
Nothing has to be configured to try it: ask for work that has stages, parallel
parts, or a review gate in the middle (/dag-flow <task>), and the agent
designs a graph and runs it. Three things turn that into a repeatable setup of
your own.
Nodes never name their own model. The graph declares which nodes are critical and configuration decides what runs them:
A project .opencode/dag.jsonc overrides the global one in the opencode config
dir; a commented default is seeded there on first use. With only one tier
configured, it serves as the unified default. Resolution order per node is
tier → worker agent model → parent session model; when none of them yields a
model, the workflow is not created and you are asked to configure one.
A workflow spec is a YAML file describing the graph. Put it in a workflow directory and it gains a name:
| Scope | Path | Availability |
|---|---|---|
| Project | .opencode/workflows/<name>.yaml |
This repo, committed with it — the whole team gets the same procedure |
| Global | <opencode config dir>/workflows/<name>.yaml |
Every project on the machine |
| Builtin | Compiled into release binaries | Every release install — the last-resort tier |
Resolution takes the first match in that order, so a project file shadows a
global one with the same name, and both shadow the builtin tier. The global
scope is maintained by the opencode-dag-config
repository; the /dag-template-update command syncs it (preview of
new/changed/unchanged files, backup before overwrite, QA decision gate). A minimal spec:
title: Dependency audit
config:
name: dependency-audit
nodes:
- id: inventory
name: inventory
worker_type: explore
depends_on: []
required: true
prompt_template:
id: config-explore
input:
target: "package.json files and lockfiles"
- id: report
name: report
worker_type: general
depends_on: [inventory]
report_to_parent: true
prompt_template:
inline: "Flag outdated or duplicated dependencies in {{inventory}} and propose an upgrade order."Then just say "run the dependency-audit workflow" — the agent starts it by
name, no path needed. Ad-hoc specs still work exactly as before: a spec_path
that looks like a path (has a separator or a .yaml/.yml extension) is read
relative to the session directory instead of the library.
The built-in create-dag-workflow skill covers spec authoring: the library
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. 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.
The engine lives in packages/core/src/dag (state machine, dependency graph, scheduling, event projection, SQLite read model) and 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.
Each node declares:
| Field | Purpose |
|---|---|
depends_on |
Dependency edges; cycle detection and dangling-reference validation at creation |
worker_type |
Which agent runs the node (explore, build, general, or any configured agent) |
prompt_template |
Prompt by id (from .opencode/dag-prompts/, 12 templates ship in-repo) or inline, with {{var}} interpolation |
input_mapping |
Map upstream node outputs into template variables ("count": "node-b.output.count") |
condition |
Expression over upstream outputs; false → node skipped, pure descendants cascade-skip |
output_schema |
JSON Schema; the child agent must call submit_result with a matching structured payload |
required |
Failure of a required node fails the workflow |
report_to_parent |
Wake the parent agent when this node reaches a terminal state |
review |
design or diff review phase with an implementation-fingerprint contract (below) |
Workflow-level knobs: max_concurrency (default 5), max_node_replan_attempts (5), max_total_nodes (100), per-node timeout_ms (default 10 min, queue wait counts toward the deadline).
- Nodes spawn as real child sessions through the same code path as the
tasktool, wave by wave in dependency order, bounded by a concurrency semaphore. A node is durablyqueuedat 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:
pausefreezes scheduling instantly,replanmerges a fragment (add / replace / cancel / restart nodes) atomically against the live graph,resumecontinues. Terminal nodes are immutable; retrying a failed node means adding a replacement under a new id.extendappends 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_parentnode 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.
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.
- 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
runningare 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 inworkflow(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.
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.
- Admission Q&A covers six dimensions (goal, scope, constraints/assumptions, acceptance criteria, evidence, risks) under a bounded policy:
LIGHT(1 round),STANDARD(3),GRILL(5, adversarial). The resulting Requirement Brief is fingerprinted (SHA-256 over a canonical form); material changes invalidate the fingerprint and return admission to questioning. A consumed record is persisted with the workflow and never replayed. - Review nodes declare their phase honestly:
designreviews pre-implementation artifacts;diffreviews the actual implementation and requires the implementation node, a passing verification node, and a fingerprint echo. Changing the implementation changes the fingerprint, so a staleACCEPTcannot satisfy the gate.
-
TUI DAG inspector (command palette →
dag.open): workflow list, wave-ordered node view with live status, node detail (deps, errors, output preview, deadline countdown), andp/r/s/xfor pause/resume/step/cancel;enterdrops 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 POST /dag start a workflow GET /dag/session/:sessionID workflows for a session GET /dag/session/:sessionID/summary progress summaries GET /dag/:dagID workflow detail GET /dag/:dagID/nodes node list GET /dag/:dagID/nodes/:nodeID node detail POST /dag/:dagID/control pause/resume/cancel/replan/extend/step/complete
Everything DAG-specific lives under .opencode/, with a global counterpart in
the opencode config dir (OPENCODE_CONFIG_DIR, else ~/.config/opencode).
Everything else inherits the main opencode configuration.
| Path | Purpose | Global counterpart |
|---|---|---|
.opencode/dag.jsonc |
Model tiers (advanced / standard) and thinking_depth for child sessions |
<config dir>/dag.jsonc, seeded with comments on first use |
.opencode/workflows/*.yaml |
Saved workflow specs, startable by name | <config dir>/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.
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, 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:
- 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.
- 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.
- Prediction, verification, and merge have different owners. A
reasonersimulates likely execution paths, a fresh-context reviewer checks the preceding local wave, and exactly one arbiter owns the verdict. These gates cannot be pruned. - Iteration is a bounded local graph rewrite.
PASSfinalizes,LOOPadds a new correction/review wave through pause → replan → resume, andBLOCKEDstops with evidence. Completed nodes never form a hidden cycle. - 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 repo) and a builtin tier embedded in release binaries. See the Graph Engineering workflow catalog.
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 <text>sets a goal and starts the loop;/goal status|pause|resume|done|clear|stopcontrols it;/subgoal <text>|list|remove <n>|clearmanages subgoals attached to the active goal. - Judge loop: after each turn an external judge evaluates progress —
doneclears the goal,continueinjects the next continuation turn against a configurable turn budget (budget exhaustion pauses the goal; it stays resumable). The agent can self-declare completion with thegoal(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/goalexposes the state (404when no goal is set). - Durability: goal state is persisted per session (
goal_state), survives restarts, and is cleared automatically when the session is deleted.
- Hooks API: Claude Code hooks protocol compatibility. 26 hook events (
PreToolUse,PostToolUse,SessionStart,PermissionRequest,WorktreeCreate, …) × 5 execution types (command,mcp,http,prompt,agent), loaded from a global/project/worktreehooks.jsonchain or registered per-session over HTTP, with optional workspace-trust gating. See the hooks reference. - Tool robustness: JSON repair for broken multi-byte Unicode escapes in LLM output, structured validation errors with field-level hints, expanded tool docs, child-process pipe fixes.
- CJK & IME fixes: corrections for Chinese/Japanese/Korean input in the terminal UI (IME composition flushing, full-width text handling), plus a Korean IME fix script under
patches/. - Worktree isolation: per-workflow
git worktreeisolation, with experimental sandbox-worktree HTTP endpoints. - Configuration assistant: a standalone Go TUI under
config_assistantfor locating, validating, and editing opencode configuration (cd config_assistant && go run ./cmd/ocfg).
All upstream capabilities (multi-provider, built-in LSP, client/server architecture, TUI/desktop/web clients) are preserved.
Prebuilt CLI binaries (Linux / macOS / Windows, with SHA256SUMS) are published on the releases page. Builds from main are formal releases; builds from dev are prereleases.
From source (requires Bun 1.3+):
bun install
bun dev # TUI
bun dev serve # headless API server (port 4096)
# standalone binary
./packages/opencode/script/build.ts --singleThis fork is not published to npm/brew/scoop. The upstream
opencode-aipackage installs upstream opencode, not this fork.
- CI: typecheck on every PR; the
maingate additionally runs the Bun/Turbo unit suite and config-assistant Go tests (Linux), Playwright e2e (Linux + Windows), an HTTP API contract exerciser, and generated-SDK freshness checks. - DAG-specific tests: core scheduling unit tests, projector/state-machine drift tests, workflow lifecycle integration tests, and HTTP API exercise scenarios for every DAG route.
Mixed license model:
| Content | License | Text |
|---|---|---|
| Upstream opencode code (the vast majority) | MIT | LICENSE |
| DAG workflow engine (fork-authored) | AGPL-3.0-or-later | packages/core/src/dag/LICENSE, packages/opencode/src/dag/LICENSE |
Exact file boundaries are listed in NOTICE. The AGPL covers the DAG engine and derivatives of it, including network-server deployments. If you don't touch the DAG engine, the rest of the repository is plain MIT.
- Saved workflow authoring guide — the
create-dag-workflowskill body - Graph Engineering workflow catalog — reference topologies and adaptation contracts
docs/harness-dag.md— deep-mode admission & review lifecycle.opencode/dag-prompts— built-in node prompt templatesAGENTS.md— contribution & development guide
{ // Critical nodes: required: true, plus review/arbiter workers. "model": { "advanced": "anthropic/claude-sonnet-4-5", "standard": "anthropic/claude-haiku-4-5" }, // Reasoning variant for DAG child sessions, when the model defines one. "thinking_depth": "medium" }