Skip to main content
The Task Graph turns an approved plan into a persisted DAG executed by parallel squad workers — with one non-negotiable rule: done is never the executor’s self-report.
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

The embedded task-graph skill teaches the model this ruler, the plan schema and the discipline (“you never declare done”).

The plan schema

One JSON object describes the whole delivery:
  • 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 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.

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 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.

Surfaces

@taskgraph (for the AI)

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)

/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

/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.
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.

Persistence

Each run owns ~/.chatcli/taskgraph/<runID>/: Runs appear in /agents (and the Conversation Hub) as a parent run with each executor and reviewer registered as children — cancellation propagates down the tree.
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.