> ## Documentation Index
> Fetch the complete documentation index at: https://chatcli.edilsonfreitas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Task Graph

> Execute an approved multi-task plan as a verified DAG: parallel squad workers per task, validation gates the engine runs itself, and an independent reviewer verdict before anything counts as done — with a live browser dashboard.

The **Task Graph** turns an approved plan into a persisted DAG executed by parallel [squad workers](/agents/agent-squad) — with one non-negotiable rule: **done is never the executor's self-report**.

```text theme={"system"}
plan → task graph → parallel execution → engine-run gate → independent review → done | retry
```

With several agents working in parallel, a task wrongly self-declared as complete unblocks its dependents on a false premise. The task graph makes verification structural instead of narrative: the orchestrator is deterministic Go code (not an LLM), the validation commands are executed by the engine itself, and the verdict comes from a **fresh reviewer worker** that never shares context with the executor.

***

## When to use it

| Situation | Right tool |
| - | - |
| 1–4 tasks, or strictly serial work | Direct `agent_call` workers or the [squad playbook](/agents/agent-squad) |
| **5+ tasks with real independence**, an approved plan, a verifiable deliverable | **`@taskgraph`** — the parallelism pays for the orchestration overhead |

The embedded `task-graph` [skill](/tools/builtin-skills) teaches the model this ruler, the plan schema and the discipline ("you never declare done").

<Info>You don't have to ask for it by name. A substantial task (complexity ≥ 6) is **auto-routed** here by default — see [Plan-and-Solve → Routing to the task graph](/agents/harness/plan-and-solve#routing-to-the-task-graph). The routing steers, it doesn't force: genuinely serial or small work still runs directly.</Info>

***

## The plan schema

One JSON object describes the whole delivery:

```json theme={"system"}
{
  "name": "feature-x",
  "require_review": true,
  "phases": [{"id": "F1", "title": "Server"}, {"id": "F2", "title": "Client"}],
  "tasks": [
    {"id": "T1", "phase": "F1", "title": "Add /foo endpoint", "agent": "coder",
     "prompt": "Implement GET /foo in server/handler.go returning ...",
     "validation": [{"run": "go test ./server/...", "expect": "all green, includes a /foo case"}]},
    {"id": "T2", "phase": "F2", "title": "CLI client", "deps": ["T1"],
     "prompt": "Add the /foo client call. Server contract: #T1",
     "validation": [{"run": "go build ./...", "expect": "builds clean"}]}
  ]
}
```

* `deps` may only reference tasks declared **earlier** (which also rules out cycles).
* `prompt` must be self-contained — workers do not see the conversation. `#<depID>` is replaced with that dependency's output.
* `validation[].run` commands are executed by the **engine** (inheriting the [coder sandbox](/coder/coder-security) and the unsafe-command denylist) — never by the executor. `expect` is prose for the reviewer. A bare string is a prose-only contract (reviewer verifies by inspection).
* `agent` defaults to `coder` (any squad worker type works); `max_attempts` defaults to 3; `require_review` defaults to **true**, per graph or per task.
* `tools` optionally grants the task's **executor** worker session plugins — `"tools": ["@browser", "@websearch"]`. The reviewer never receives them. See [Worker capabilities](#worker-capabilities) below.

***

## Execution: ready-set scheduling

Tasks fire the moment their dependencies complete — no level barriers, no wasted wall-clock. Concurrency is capped by the squad's existing `CHATCLI_AGENT_MAX_WORKERS` knob (the graph's own `max_parallel` can lower it); **no new environment variables** were added for this feature.

Each task runs through an attempt loop:

1. **Checkpoint** — a [shadow-git snapshot](/coder/coder-plugin#checkpoints) of the workspace, best-effort, before the executor starts.
2. **Executor** — a fresh worker receives the prompt (plus reviewer feedback on retries).
3. **Gate** — the engine runs every `validation[].run` command itself and records output; a non-zero exit fails the attempt without spending a reviewer.
4. **Review** — a fresh `reviewer` worker (read-only tools) receives the contract, the gate outputs and the executor's report, and must end with `VERDICT: PASS — evidence` or `VERDICT: FAIL — what is missing`.
5. **Promote or retry** — gate green + PASS promotes to `done` with the evidence recorded; FAIL retries with the feedback injected, up to `max_attempts`, then the task fails and its successors become `blocked`.

The engine **refuses**: dependency cycles, starting a task with unmet deps, promoting without gate + verdict when review is required, and reviewer = executor (each is a distinct dispatch — both run IDs are recorded on the attempt as proof).

States: `pending → running → reviewing → done | failed | blocked`.

Every attempt also carries its **real cost**: each worker LLM call is attributed to the graph node that spawned it, using the same per-call accounting as [cost tracking](/providers/cost-tracking).

***

## Surfaces

### `@taskgraph` (for the AI)

```json theme={"system"}
{"cmd":"run","args":{"file":"/tmp/plan.json"}}  // run a plan from a file (RECOMMENDED)
{"cmd":"run","args":{"graph":{...}}}   // plan + execute inline (small graphs only)
{"cmd":"run","args":{"id":"tg-..."}}   // run/resume a persisted plan
{"cmd":"plan","args":{"file":"..."}}   // validate + persist only (file or inline graph)
{"cmd":"status"}                       // per-task status, attempts, verdicts, cost
{"cmd":"show","args":{"task":"T3"}}    // one task in full: gate outputs, evidence, run ids
{"cmd":"retry","args":{"task":"T3"}}   // re-open a failed task (+1 attempt) and resume
{"cmd":"cancel"}                       // stop the active run
{"cmd":"list"}                         // persisted runs
{"cmd":"dash"}                         // serve the live dashboard, returns its URL
{"cmd":"prune","args":{"older_than":"7d"}}  // remove old runs ("all" keeps only the active one)
```

<Note>**File first for real graphs.** Inline `graph` args beyond \~2KB get truncated by output-token limits ("unexpected end of JSON input"). The reliable form is two calls: write the plan JSON with `@coder write`, then `run` with `file` — the file may hold the bare plan, `{"graph":{...}}` or the full envelope. The embedded skill teaches the model exactly this.</Note>

`run` executes the whole graph in a single tool call, streaming events as they happen. Resume is inherent: completed tasks stay done, so re-running a failed graph continues where it stopped.

### `/taskgraph` (for humans)

```bash theme={"system"}
/taskgraph status [id]     # per-task status, verdicts, cost
/taskgraph show T3 [id]    # one task: attempts, gate outputs, evidence
/taskgraph list            # persisted runs
/taskgraph dash [id]       # open the live browser dashboard
/taskgraph prune [7d|all]  # remove old runs (default 30d; the active run is never removed)
/taskgraph cancel          # stop the active run
```

`/taskgraph` is allowlisted as a **mid-run side command** — type it while the graph executes to inspect progress without touching the orchestrator loop.

***

## The live dashboard

<Info>This dashboard goes deep on **one task graph run**. To watch the whole runtime live (every agent, LLM request, tool, skill, MCP server, pattern and background job, across processes), use [`/dash`](/usage/live-dashboard); task graph runs show up there too.</Info>

`/taskgraph dash` (or `{"cmd":"dash"}`) serves a browser dashboard from an ephemeral `127.0.0.1` port — a single embedded file, zero CDN, zero configuration:

* **Animated DAG canvas** — phase swimlanes, dependency béziers with animated dashes into running tasks, status-colored cards with attempt count and per-task cost; drag to pan, ctrl/⌘+wheel to zoom, `0` to fit, lineage highlight on hover.
* **Evidence popovers** — click a task for its prompt, validation contract, gate outputs, reviewer verdict + evidence and the executor/reviewer run IDs.
* **Results tab** — critical path, wall-clock vs agent time, parallelism factor and the real cost per task.
* **Live** — the page polls the persisted state every second and tails the event feed.

<Frame caption="The same run from start to finish: tasks light up as the ready set advances, a gate failure sends a task back for a second attempt, a click opens its evidence with both attempts, and the Results tab sums critical path, times and cost. (Synthetic data.)">
  <img src="https://mintcdn.com/encom/4V8E1JPPjwsxz-ga/images/task-graph.gif?s=03be9e8f63695c1edd7a8c40b0f122d6" alt="Animated ChatCLI task graph dashboard: tasks moving from pending to running, reviewing and done, a retry after a failed gate, the evidence popover and the Results tab" width="1500" height="900" data-path="images/task-graph.gif" />
</Frame>

The server is **strictly read-only**: it reads `state.json` and `events.ndjson` from disk on every request. Closing the dashboard never affects a run, and finished runs render just as well.

***

## Worker capabilities

Each executor and reviewer runs with the session's own context — proactive memory/session recall, CCR expansion of truncated results, and read-only memory/session/knowledge tools — so a worker picks up what the session already learned instead of starting blind.

Beyond that, two capabilities are opt-in per task:

* **Tool grants.** `"tools": ["@browser", "@websearch"]` on a task gives its **executor** those session plugins (builtins or `mcp_*`), gated by the same security policy — a `@browser click` prompts exactly as it would in `/coder`. The reviewer never gets tools (its job is inspection). `@browser` drives one shared page, so grant it to a **single** task at a time; parallel browser tasks would corrupt each other's element refs.
* **User skills.** A worker's task text is matched against your own [skills](/tools/builtin-skills) the same way the orchestrator matches them: pinned skills always apply, trigger-matched ones are injected (capped and budgeted). Your curated knowledge reaches every executor.

When a run finishes, a compact **learning digest** feeds the session's long-term memory (facts, episodes, self-evolving skill candidates) and a Reflexion lesson is queued for each failed task — so the graph teaches the session, not just delivers.

***

## Persistence

Each run owns `~/.chatcli/taskgraph/<runID>/`:

| File | Role |
| - | - |
| `state.json` | The full graph, written atomically on every transition (corrupt files are quarantined, never overwritten) |
| `events.ndjson` | Append-only audit trail — also the dashboard's feed |

The store is **bounded**: runs older than 30 days are pruned automatically on the session's first store access (age = last state write), and `prune` cleans up on demand — Go durations (`72h`), day suffixes (`30d`) or `all`. The active run is never removed.

Runs appear in `/agents` (and the [Conversation Hub](/gateway/conversation-hub)) as a parent run with each executor and reviewer registered as children — cancellation propagates down the tree.

<Tip>The task graph was inspired by the executor ≠ reviewer discipline of graph-style orchestration skills: self-declaration is not a verdict. In ChatCLI the orchestrator is native deterministic code, so the graph, the gates and the promotion to done are structurally out of reach of model hallucination.</Tip>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.