diff --git a/docs/agents/outputs.mdx b/docs/agents/outputs.mdx
index 2942524a8..f5b24f435 100644
--- a/docs/agents/outputs.mdx
+++ b/docs/agents/outputs.mdx
@@ -21,7 +21,7 @@ Every agent and prompt node sets three context keys from its response:
These keys are available to downstream nodes via the [context](/execution/context). The `last_response` key provides a quick preview, while `response.{node_id}` preserves the complete output for nodes that need it.
-```graphviz
+```dot
plan -> implement -> review -> exit
// In the review node's prompt, you can reference prior outputs:
@@ -64,7 +64,7 @@ JSON objects without recognized fields are ignored. If no routing directive is f
Arc does not automatically instruct agents to emit routing JSON. You must include instructions in your prompt:
-```graphviz
+```dot
review [
label="Review",
shape=tab,
diff --git a/docs/agents/prompts.mdx b/docs/agents/prompts.mdx
index 734930b25..872c0747b 100644
--- a/docs/agents/prompts.mdx
+++ b/docs/agents/prompts.mdx
@@ -13,13 +13,13 @@ The `prompt` attribute on a node defines the task instructions for that stage. I
Short prompts can be written directly in the DOT file:
-```graphviz
+```dot
plan [label="Plan", prompt="Analyze the codebase and write a step-by-step plan."]
```
Use `\` for multi-line strings:
-```graphviz
+```dot
review [
label="Review",
shape=tab,
@@ -35,7 +35,7 @@ review [
For longer prompts, use the `@` prefix to load from a Markdown file relative to the DOT file:
-```graphviz
+```dot
plan [label="Plan Implementation", prompt="@prompts/implement/plan.md"]
implement [label="Implement", prompt="@prompts/implement/implement.md"]
review [label="Review", prompt="@prompts/implement/review.md"]
@@ -47,7 +47,7 @@ The `@` prefix tells the engine to read the file contents and use them as the pr
Prompts support `$variable` placeholders that expand at runtime. Currently the only built-in variable is `$goal`, which resolves to the graph-level `goal` attribute:
-```graphviz title="pipeline.dot"
+```dot title="pipeline.dot"
digraph Pipeline {
graph [goal="Add a /health endpoint to the API server"]
@@ -67,7 +67,7 @@ A `$` not followed by an identifier character (e.g. `$5`) is left as-is. An unde
If a node has neither a `prompt` attribute nor an empty one, Arc uses the `label` as the prompt:
-```graphviz
+```dot
// "Do work" is both the display label and the prompt
work [label="Do work"]
```
diff --git a/docs/changelog/2026-02-27.mdx b/docs/changelog/2026-02-27.mdx
index fbed943eb..2414bffc9 100644
--- a/docs/changelog/2026-02-27.mdx
+++ b/docs/changelog/2026-02-27.mdx
@@ -15,7 +15,7 @@ Two new built-in tools bring real-time information into workflow decisions. `web
Individual workflow nodes can now delegate work to external AI coding assistants. Set the backend to `claude-code`, `codex`, or `gemini-cli` and the node will use that CLI tool instead of the built-in agent loop.
-```graphviz
+```dot
implement [handler=codergen, cli_backend=codex]
review [handler=codergen, cli_backend=claude-code]
```
diff --git a/docs/changelog/2026-03-05.mdx b/docs/changelog/2026-03-05.mdx
index 42ff717f1..864726ae4 100644
--- a/docs/changelog/2026-03-05.mdx
+++ b/docs/changelog/2026-03-05.mdx
@@ -27,7 +27,7 @@ Transition conditions in DOT workflows now support `||` (or), `!` (not), numeric
Previously, conditions were limited to `&&`-joined equality checks. Now you can write expressive guards like:
-```graphviz
+```dot
stage_a -> stage_b [condition="score > 80 || result contains 'approved'"];
```
@@ -98,7 +98,7 @@ verbose = true
Handler types have been renamed for clarity: `agent_loop` is now `agent`, and `one_shot` is now `prompt`. The old names still work as aliases, so existing workflows won't break.
-```graphviz
+```dot
stage_a [type="agent"];
stage_b [type="prompt"];
```
diff --git a/docs/core-concepts/agents.mdx b/docs/core-concepts/agents.mdx
index 0aebca67d..c3148f33c 100644
--- a/docs/core-concepts/agents.mdx
+++ b/docs/core-concepts/agents.mdx
@@ -43,7 +43,7 @@ The CLI is selected automatically based on the node's provider:
Set the CLI backend on a node with `backend="cli"` or via a [model stylesheet](/workflows/stylesheets):
-```graphviz
+```dot
implement [label="Implement", backend="cli", llm_provider="anthropic"]
```
@@ -91,7 +91,7 @@ See [Tools](/agents/tools) for the full reference.
The agent's behavior is shaped by its prompt — the task instructions set in the `prompt` attribute of the workflow node. Prompts can be inline strings or references to external Markdown files:
-```graphviz
+```dot
// Inline prompt
plan [label="Plan", prompt="Analyze the codebase and write a step-by-step plan."]
diff --git a/docs/core-concepts/models.mdx b/docs/core-concepts/models.mdx
index 96056995d..41641e7a1 100644
--- a/docs/core-concepts/models.mdx
+++ b/docs/core-concepts/models.mdx
@@ -48,7 +48,7 @@ When no model is specified, the `arc agent` command uses a default model based o
Assign models to workflow nodes using [model stylesheets](/workflows/stylesheets), which use a CSS-like syntax:
-```graphviz title="example.dot"
+```dot title="example.dot"
digraph Example {
graph [
model_stylesheet="
diff --git a/docs/core-concepts/workflows.mdx b/docs/core-concepts/workflows.mdx
index badb8feef..f105f47d4 100644
--- a/docs/core-concepts/workflows.mdx
+++ b/docs/core-concepts/workflows.mdx
@@ -13,7 +13,7 @@ Every workflow is a `digraph` with a `goal`, a `start` node, an `exit` node, and
-```graphviz title="my-workflow.dot"
+```dot title="my-workflow.dot"
digraph MyWorkflow {
graph [goal="Describe the project"]
rankdir=LR
@@ -36,19 +36,19 @@ Each node's Graphviz **shape** determines how it executes. The three most import
**Agents** (default `box` shape) run an LLM with access to tools — bash, file editing, sub-agents — looping autonomously until the task is complete:
-```graphviz
+```dot
implement [label="Implement", prompt="Read plan.md and implement every step."]
```
**Commands** (`parallelogram`) run shell scripts and capture output for downstream nodes:
-```graphviz
+```dot
validate [label="Run Tests", shape=parallelogram, script="cargo test 2>&1 || true"]
```
**Human gates** (`hexagon`) pause the workflow and wait for a person to choose a path. Edge labels define the options:
-```graphviz
+```dot
approve [shape=hexagon, label="Approve Plan"]
approve -> implement [label="[A] Approve"]
@@ -65,7 +65,7 @@ Arc supports additional node types for one-shot prompts, conditional branching,
Edges can have **conditions** that route execution based on outcomes:
-```graphviz
+```dot
gate [shape=diamond, label="Tests passing?"]
gate -> exit [label="Pass", condition="outcome=success"]
@@ -74,7 +74,7 @@ gate -> implement [label="Fix"]
Loops are natural — just point an edge back to an earlier node. Use `max_visits` on a node to prevent infinite loops:
-```graphviz
+```dot
fix [label="Fix Failures", prompt="Fix the failing tests.", max_visits=3]
```
@@ -86,7 +86,7 @@ fix [label="Fix Failures", prompt="Fix the failing tests.", max_visits=3]
Fan out to run branches concurrently, then merge the results:
-```graphviz
+```dot
fork [label="Fan Out", shape=component]
merge [label="Merge", shape=tripleoctagon]
@@ -103,7 +103,7 @@ merge -> report -> exit
Mark critical nodes with `goal_gate=true`. The workflow fails if any goal gate doesn't succeed — even if execution reaches the exit node:
-```graphviz
+```dot
validate [label="Validate", prompt="Run the test suite and verify all tests pass.", goal_gate=true]
```
diff --git a/docs/docs.json b/docs/docs.json
index 310618f9b..4a5bca544 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -286,6 +286,13 @@
"vscode"
]
},
+ "styling": {
+ "codeblocks": {
+ "languages": {
+ "custom": ["/languages/dot.json"]
+ }
+ }
+ },
"footer": {
"socials": {
"x": "https://x.com/mintlify",
diff --git a/docs/execution/context.mdx b/docs/execution/context.mdx
index 45c5c3a6d..62882adda 100644
--- a/docs/execution/context.mdx
+++ b/docs/execution/context.mdx
@@ -80,7 +80,7 @@ The engine sets several keys automatically. These are prefixed with `internal.`
Edge [conditions](/workflows/transitions#conditions) can read context values to route execution:
-```graphviz
+```dot
gate -> deploy [condition="outcome=success && context.tests_passed=true"]
gate -> fix [condition="outcome=fail"]
```
@@ -105,17 +105,17 @@ When a new agent or prompt node starts, Arc assembles a **preamble** — a summa
Fidelity can be set at three levels. The first match wins:
1. **Edge attribute** — Set `fidelity` on an edge to control the transition into a specific node:
- ```graphviz
+ ```dot
plan -> implement [fidelity="full"]
```
2. **Node attribute** — Set `fidelity` on a node to control all transitions into it:
- ```graphviz
+ ```dot
implement [fidelity="full"]
```
3. **Graph default** — Set `default_fidelity` on the graph for a run-wide default:
- ```graphviz
+ ```dot
digraph Example {
graph [default_fidelity="summary:medium"]
}
@@ -127,7 +127,7 @@ If none of these are set, fidelity defaults to `compact`.
`full` fidelity is typically used with `thread_id` to create a shared conversation across multiple nodes. Nodes with the same `thread_id` share a single LLM session, preserving full context continuity:
-```graphviz
+```dot
subgraph cluster_impl {
node [fidelity="full", thread_id="impl"]
plan [label="Plan"]
diff --git a/docs/execution/failures.mdx b/docs/execution/failures.mdx
index ab24d2acb..f0d790b3e 100644
--- a/docs/execution/failures.mdx
+++ b/docs/execution/failures.mdx
@@ -20,7 +20,7 @@ When a node fails, Arc classifies the failure into one of six categories. These
Classification happens automatically. Arc inspects SDK error types, HTTP status codes, and error message patterns to assign the right class. The `failure_class` is written to [context](/execution/context) after each stage, so you can route on it in edge conditions:
-```graphviz
+```dot
implement -> fix [condition="failure_class=transient_infra"]
implement -> escalate [condition="failure_class=deterministic"]
```
@@ -53,7 +53,7 @@ When a node handler fails (after LLM retries are exhausted), the engine can retr
Set a retry policy on a node with the `retry_policy` attribute:
-```graphviz
+```dot
implement [retry_policy="standard"]
```
@@ -71,7 +71,7 @@ All policies apply random jitter (0.5x–1.5x) and cap individual delays at 60 s
You can also set just the retry count using `max_retries`:
-```graphviz
+```dot
implement [max_retries="5"]
```
@@ -132,7 +132,7 @@ Arc has two independent mechanisms for detecting stuck loops: **node visit limit
The `max_node_visits` graph attribute sets the maximum number of times any single node can execute before the run is terminated:
-```graphviz title="example.dot"
+```dot title="example.dot"
digraph Example {
graph [max_node_visits="20"]
// ...
@@ -203,7 +203,7 @@ Loop restart edges also have their own separate circuit breaker (`restart_failur
Goal gates are quality checkpoints that are enforced when the workflow reaches an exit node. A node marked with `goal_gate=true` must have completed with `success` or `partial_success` — otherwise the run cannot finish.
-```graphviz
+```dot
verify [shape=box, goal_gate="true"]
```
@@ -214,7 +214,7 @@ When a goal gate is unsatisfied at the exit node, Arc looks for a **retry target
3. Graph-level `retry_target` attribute
4. Graph-level `fallback_retry_target` attribute
-```graphviz title="example.dot"
+```dot title="example.dot"
digraph Example {
graph [retry_target="plan"]
verify [shape=box, goal_gate="true", retry_target="implement"]
@@ -238,7 +238,7 @@ Arc runs a background watchdog that monitors event activity. If no events are em
| `stall_timeout` | 600 seconds (10 minutes) |
| Set to `0` | Disables the watchdog |
-```graphviz title="example.dot"
+```dot title="example.dot"
digraph Example {
graph [stall_timeout="300"] // 5 minutes
}
diff --git a/docs/execution/interviews.mdx b/docs/execution/interviews.mdx
index 9b537e9b7..f81f6e3b4 100644
--- a/docs/execution/interviews.mdx
+++ b/docs/execution/interviews.mdx
@@ -110,7 +110,7 @@ Questions can have a `timeout_seconds` field. When set, Arc wraps the interviewe
The human handler then checks the node's `human.default_choice` attribute. If set, execution continues to the default target. Otherwise, the stage retries.
-```graphviz
+```dot
approve [shape=hexagon, label="Approve?", human.default_choice="deploy"]
```
diff --git a/docs/execution/run-configuration.mdx b/docs/execution/run-configuration.mdx
index 0745ba2dc..5733dbc18 100644
--- a/docs/execution/run-configuration.mdx
+++ b/docs/execution/run-configuration.mdx
@@ -181,7 +181,7 @@ language = "rust"
Variables can be used anywhere in the DOT file with `$name` syntax:
-```graphviz title="c-i.dot"
+```dot title="c-i.dot"
digraph CI {
graph [goal="Run tests for $repo_name"]
clone [shape=parallelogram, script="git clone $repo_url repo"]
diff --git a/docs/getting-started/why-arc.mdx b/docs/getting-started/why-arc.mdx
index 51b005acb..166e78015 100644
--- a/docs/getting-started/why-arc.mdx
+++ b/docs/getting-started/why-arc.mdx
@@ -48,7 +48,7 @@ Workflows are defined in Graphviz DOT, a simple graph description language. Here
-```graphviz title="plan-implement.dot"
+```dot title="plan-implement.dot"
digraph PlanImplement {
graph [goal="Plan, approve, implement, and simplify a change"]
diff --git a/docs/languages/dot.json b/docs/languages/dot.json
new file mode 100644
index 000000000..18d9081c4
--- /dev/null
+++ b/docs/languages/dot.json
@@ -0,0 +1,100 @@
+{
+ "name": "dot",
+ "scopeName": "source.dot",
+ "fileTypes": ["dot", "DOT", "gv"],
+ "firstLineMatch": "digraph.*",
+ "patterns": [
+ {
+ "match": " ?(digraph)[ \\t]+([A-Za-z0-9]+) ?(\\{)",
+ "captures": {
+ "1": { "name": "storage.type.dot" },
+ "2": { "name": "variable.other.dot" },
+ "3": { "name": "punctuation.section.dot" }
+ }
+ },
+ {
+ "match": "(<|-)(>|-)",
+ "name": "keyword.operator.dot"
+ },
+ {
+ "match": "\\b(node|edge|graph|digraph|subgraph|strict)\\b",
+ "name": "storage.type.dot"
+ },
+ {
+ "match": "\\b(bottomlabel|color|comment|distortion|fillcolor|fixedsize|fontcolor|fontname|fontsize|group|height|label|layer|orientation|peripheries|regular|shape|shapefile|sides|skew|style|toplabel|URL|width|z)\\b",
+ "name": "support.constant.attribute.node.dot"
+ },
+ {
+ "match": "\\b(arrowhead|arrowsize|arrowtail|color|comment|constraint|decorate|dir|fontcolor|fontname|fontsize|headlabel|headport|headURL|label|labelangle|labeldistance|labelfloat|labelcolor|labelfontname|labelfontsize|layer|lhead|ltail|minlen|samehead|sametail|splines|style|taillabel|tailport|tailURL|weight)\\b",
+ "name": "support.constant.attribute.edge.dot"
+ },
+ {
+ "match": "\\b(bgcolor|center|clusterrank|color|comment|compound|concentrate|fillcolor|fontname|fontpath|fontsize|label|labeljust|labelloc|layers|margin|mclimit|nodesep|nslimit|nslimit1|ordering|orientation|page|pagedir|quantum|rank|rankdir|ranksep|ratio|remincross|rotate|samplepoints|searchsize|size|style|URL)\\b",
+ "name": "support.constant.attribute.graph.dot"
+ },
+ {
+ "match": "\\b(box|polygon|ellipse|circle|point|egg|triangle|plaintext|diamond|trapezium|parallelogram|house|pentagon|hexagon|septagon|octagon|doublecircle|doubleoctagon|tripleoctagon|invtriangle|invtrapezium|invhouse|Mdiamond|Msquare|Mcircle|rect|rectangle|none|note|tab|folder|box3d|component|max|min|same)\\b",
+ "name": "variable.other.dot"
+ },
+ {
+ "begin": "\"",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.string.begin.dot" }
+ },
+ "end": "\"",
+ "endCaptures": {
+ "0": { "name": "punctuation.definition.string.end.dot" }
+ },
+ "name": "string.quoted.double.dot",
+ "patterns": [
+ {
+ "match": "\\\\.",
+ "name": "constant.character.escape.dot"
+ }
+ ]
+ },
+ {
+ "begin": "(^[ \\t]+)?(?=//)",
+ "beginCaptures": {
+ "1": { "name": "punctuation.whitespace.comment.leading.dot" }
+ },
+ "end": "(?!\\G)",
+ "patterns": [
+ {
+ "begin": "//",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.dot" }
+ },
+ "end": "\\n",
+ "name": "comment.line.double-slash.dot"
+ }
+ ]
+ },
+ {
+ "begin": "(^[ \\t]+)?(?=#)",
+ "beginCaptures": {
+ "1": { "name": "punctuation.whitespace.comment.leading.dot" }
+ },
+ "end": "(?!\\G)",
+ "patterns": [
+ {
+ "begin": "#",
+ "beginCaptures": {
+ "0": { "name": "punctuation.definition.comment.dot" }
+ },
+ "end": "\\n",
+ "name": "comment.line.number-sign.dot"
+ }
+ ]
+ },
+ {
+ "begin": "/\\*",
+ "captures": {
+ "0": { "name": "punctuation.definition.comment.dot" }
+ },
+ "end": "\\*/",
+ "name": "comment.block.dot"
+ }
+ ],
+ "repository": {}
+}
diff --git a/docs/reference/dot-language.mdx b/docs/reference/dot-language.mdx
index 1131c4e91..dccfbd6f6 100644
--- a/docs/reference/dot-language.mdx
+++ b/docs/reference/dot-language.mdx
@@ -9,7 +9,7 @@ Arc workflows are written in a subset of the [Graphviz DOT language](https://gra
Every workflow is a `digraph` (directed graph) with a name and a body of statements:
-```graphviz title="my-workflow.dot"
+```dot title="my-workflow.dot"
digraph MyWorkflow {
graph [goal="Describe the project"]
rankdir=LR
@@ -28,7 +28,7 @@ Only `digraph` is supported — `graph` (undirected) and `strict` are not. The g
## Comments
-```graphviz
+```dot
// Line comment — everything to end of line
/* Block comment
@@ -63,7 +63,7 @@ The body of a digraph can contain these statement types:
Set workflow-level configuration:
-```graphviz
+```dot
// Block syntax
graph [goal="Build a feature", model_stylesheet="* { llm_model: claude-haiku-4-5; }"]
@@ -88,7 +88,7 @@ rankdir=LR
Apply default attributes to all subsequently declared nodes:
-```graphviz
+```dot
node [shape=box, timeout="900s"]
```
@@ -98,7 +98,7 @@ Defaults are scoped to their enclosing subgraph. Explicit attributes on individu
Apply default attributes to all subsequently declared edges:
-```graphviz
+```dot
edge [weight=5]
```
@@ -106,7 +106,7 @@ edge [weight=5]
Declare a node with optional attributes:
-```graphviz
+```dot
plan [label="Plan", prompt="Create an implementation plan."]
```
@@ -118,7 +118,7 @@ Nodes referenced in edges are auto-created if not explicitly declared.
Connect nodes with directed edges:
-```graphviz
+```dot
start -> plan -> implement -> exit
```
@@ -126,7 +126,7 @@ Chained edges like `A -> B -> C` expand to individual edges `A -> B` and `B -> C
Edges can have attributes:
-```graphviz
+```dot
gate -> exit [label="Pass", condition="outcome=success"]
gate -> implement [label="Fix"]
```
@@ -135,7 +135,7 @@ gate -> implement [label="Fix"]
Group nodes visually and apply scoped defaults:
-```graphviz
+```dot
subgraph cluster_impl {
label = "Implementation"
node [thread_id="impl", fidelity="full"]
@@ -280,7 +280,7 @@ A bare key with no operator is a **truthiness check** — it passes if the value
### Examples
-```graphviz
+```dot
// Simple outcome check
gate -> exit [condition="outcome=success"]
@@ -310,7 +310,7 @@ gate -> slow_path
Instead of inlining long prompts, reference an external file:
-```graphviz
+```dot
simplify [label="Simplify", prompt="@docs-internal/prompts/simplify.md"]
```
@@ -334,7 +334,7 @@ Arc validates workflows at parse time and reports diagnostics. Key rules:
## Complete example
-```graphviz title="implement-feature.dot"
+```dot title="implement-feature.dot"
digraph ImplementFeature {
graph [
goal="Implement a feature with tests and code review",
diff --git a/docs/workflows/human-in-the-loop.mdx b/docs/workflows/human-in-the-loop.mdx
index c34202a46..4f9df92db 100644
--- a/docs/workflows/human-in-the-loop.mdx
+++ b/docs/workflows/human-in-the-loop.mdx
@@ -9,7 +9,7 @@ Not every decision should be made by an LLM. Arc provides several mechanisms for
A human gate is a node with shape `hexagon` that pauses the workflow and presents the user with a choice. The options are derived from the outgoing edge labels:
-```graphviz
+```dot
approve [shape=hexagon, label="Approve Plan"]
approve -> implement [label="[A] Approve"]
@@ -35,7 +35,7 @@ When matching the user's selection to an edge, Arc strips the accelerator prefix
In addition to fixed choices, a human gate can accept freeform text input. Add an edge with `freeform=true`:
-```graphviz
+```dot
review [shape=hexagon, label="Review Changes"]
review -> implement [label="[A] Approve"]
@@ -49,7 +49,7 @@ If the user types something that doesn't match any fixed option, their input rou
If a human gate has a timeout configured, you can specify a default choice using the `human.default_choice` attribute:
-```graphviz
+```dot
approve [shape=hexagon, label="Approve?", human.default_choice="approve"]
```
diff --git a/docs/workflows/stages-and-nodes.mdx b/docs/workflows/stages-and-nodes.mdx
index fdcc4f3a3..135fe7598 100644
--- a/docs/workflows/stages-and-nodes.mdx
+++ b/docs/workflows/stages-and-nodes.mdx
@@ -19,7 +19,7 @@ Every node's Graphviz `shape` attribute determines its execution behavior. If no
The entry point of the workflow. Every workflow must have exactly one start node.
-```graphviz
+```dot
start [shape=Mdiamond, label="Start"]
```
@@ -29,7 +29,7 @@ start [shape=Mdiamond, label="Start"]
The terminal node. When execution reaches exit, the workflow completes. Every workflow must have exactly one exit node.
-```graphviz
+```dot
exit [shape=Msquare, label="Exit"]
```
@@ -39,7 +39,7 @@ exit [shape=Msquare, label="Exit"]
Runs an LLM with access to tools — bash, file editing, sub-agents — in an agentic loop. The agent works autonomously, calling tools as needed, until it decides the task is complete.
-```graphviz
+```dot
implement [label="Implement", prompt="Read plan.md and implement every step."]
```
@@ -71,7 +71,7 @@ Fidelity can also be set at the graph level (`default_fidelity`) or on individua
Setting `thread_id` on multiple nodes (e.g. `thread_id="impl"`) groups them into a shared conversation thread, preserving context continuity across nodes as if they were part of the same session. This is an advanced feature typically used within subgraph clusters:
-```graphviz
+```dot
subgraph cluster_impl {
node [fidelity="full", thread_id="impl"]
plan [label="Plan"]
@@ -86,7 +86,7 @@ subgraph cluster_impl {
Makes a single LLM call with no tool use. Useful for analysis, summarization, generation, and lightweight reasoning where tools aren't needed.
-```graphviz
+```dot
spec [label="Write Spec", shape=tab, prompt="Write a brief spec for a string utility module."]
```
@@ -98,7 +98,7 @@ Prompt nodes accept the same attributes as agent nodes (`prompt`, `reasoning_eff
Runs a shell script and captures its output. The output is available to downstream nodes as context.
-```graphviz
+```dot
test [label="Run Tests", shape=parallelogram, script="cargo test 2>&1 || true"]
```
@@ -113,7 +113,7 @@ test [label="Run Tests", shape=parallelogram, script="cargo test 2>&1 || true"]
Pauses the workflow and waits for a person to choose a path. The outgoing edge labels define the available options:
-```graphviz
+```dot
approve [shape=hexagon, label="Approve Plan"]
approve -> implement [label="[A] Approve"]
@@ -128,7 +128,7 @@ In the web UI, human gates appear as interactive prompts. From the CLI, they app
Pauses the workflow for a configured duration before proceeding. Useful for rate limiting between API calls or waiting for external processes to complete.
-```graphviz
+```dot
cooldown [label="Wait 30s", shape=insulator, duration="30s"]
```
@@ -142,7 +142,7 @@ cooldown [label="Wait 30s", shape=insulator, duration="30s"]
Routes execution to different edges based on conditions evaluated against the current run context:
-```graphviz
+```dot
gate [shape=diamond, label="Tests passing?"]
gate -> exit [label="Pass", condition="outcome=success"]
@@ -157,7 +157,7 @@ Conditions support `=`, `!=`, `&&`, and context variable lookups (e.g. `context.
Fans out to execute multiple branches concurrently. Each branch gets its own isolated context.
-```graphviz
+```dot
fork [label="Fan Out", shape=component, join_policy="wait_all", error_policy="continue"]
fork -> security
@@ -194,7 +194,7 @@ fork -> quality
Collects results from parallel branches into a single context. Typically paired with a parallel fan-out node:
-```graphviz
+```dot
merge [label="Merge Results", shape=tripleoctagon]
security -> merge
diff --git a/docs/workflows/stylesheets.mdx b/docs/workflows/stylesheets.mdx
index 7da1f32c3..0d65f8212 100644
--- a/docs/workflows/stylesheets.mdx
+++ b/docs/workflows/stylesheets.mdx
@@ -9,7 +9,7 @@ Model stylesheets let you assign LLM models, providers, and settings to workflow
Stylesheets are set in the `model_stylesheet` graph attribute:
-```graphviz title="example.dot"
+```dot title="example.dot"
digraph Example {
graph [
goal="Build and review a utility function",
@@ -52,7 +52,7 @@ Each rule starts with a selector that determines which nodes it applies to:
Set the `class` attribute on a node to target it with class selectors. Multiple classes are space-separated:
-```graphviz
+```dot
implement [label="Implement", class="coding critical"]
```
@@ -95,7 +95,7 @@ If two rules have the same specificity, the **last one** in the stylesheet wins.
A model set directly on a node attribute always takes precedence over stylesheets, regardless of specificity:
-```graphviz
+```dot
implement [label="Implement", class="coding", llm_model="claude-opus-4-6"]
```
diff --git a/docs/workflows/transitions.mdx b/docs/workflows/transitions.mdx
index d0cda404e..642a5d7c3 100644
--- a/docs/workflows/transitions.mdx
+++ b/docs/workflows/transitions.mdx
@@ -28,7 +28,7 @@ If no edge matches at all, the workflow halts with an error.
Edge conditions are boolean expressions evaluated against the stage outcome and run context. Conditions go in the `condition` attribute on an edge:
-```graphviz
+```dot
gate -> exit [label="Pass", condition="outcome=success"]
gate -> implement [label="Fix", condition="outcome=fail"]
```
@@ -57,7 +57,7 @@ gate -> implement [label="Fix", condition="outcome=fail"]
A bare key with no operator is a **truthiness check** — it passes if the value is non-empty, not `"false"`, and not `"0"`:
-```graphviz
+```dot
gate -> next [condition="my_flag"]
```
@@ -65,7 +65,7 @@ gate -> next [condition="my_flag"]
Use `&&` (AND), `||` (OR), and `!` (NOT) to build compound expressions. `&&` binds tighter than `||`:
-```graphviz
+```dot
// Both must be true
gate -> deploy [condition="outcome=success && context.tests_passed=true"]
@@ -99,7 +99,7 @@ Agent and prompt nodes can influence which edge is taken by including a JSON obj
Arc automatically scans LLM output for these JSON objects — no special configuration is needed. However, you do need to instruct the LLM to emit the JSON in your prompt. For example:
-```graphviz
+```dot
review [
label="Review",
shape=tab,
@@ -120,7 +120,7 @@ The LLM's natural language response can contain other text — Arc finds the las
Human gates use edge labels to present options to the user. The selected label becomes the `preferred_label` in the outcome, and Arc matches it to the corresponding edge:
-```graphviz
+```dot
approve [shape=hexagon, label="Approve Plan"]
approve -> implement [label="[A] Approve"]
@@ -134,13 +134,13 @@ The `[A]`, `[R]`, `[S]` prefixes are keyboard accelerators — Arc strips them w
An edge without a `condition` attribute always matches. When a node has a single outgoing edge, it doesn't need a condition:
-```graphviz
+```dot
start -> plan -> implement -> exit
```
When mixing conditional and unconditional edges, conditional matches take priority. An unconditional edge acts as the default fallback:
-```graphviz
+```dot
gate -> fast_path [condition="outcome=success"]
gate -> slow_path
```
@@ -149,7 +149,7 @@ gate -> slow_path
When multiple edges match (e.g. two unconditional edges), `weight` determines the winner. Higher weight wins:
-```graphviz
+```dot
node -> preferred [weight=10]
node -> fallback [weight=1]
```
diff --git a/docs/workflows/variables.mdx b/docs/workflows/variables.mdx
index 80757e463..cf5a2a020 100644
--- a/docs/workflows/variables.mdx
+++ b/docs/workflows/variables.mdx
@@ -22,7 +22,7 @@ language = "rust"
These variables are expanded into the DOT source **before** the graph is parsed. You can use `$variable` anywhere in the DOT file — goals, prompts, labels, scripts, or any other attribute:
-```graphviz title="check.dot"
+```dot title="check.dot"
digraph Check {
graph [goal="Run tests for $repo_name"]
@@ -48,7 +48,7 @@ A bare `$` not followed by an identifier character (e.g. `costs $5`) is left as-
Inside agent and prompt node prompts, Arc automatically expands `$goal` to the workflow's `goal` attribute. This happens at runtime, after graph parsing:
-```graphviz title="example.dot"
+```dot title="example.dot"
digraph Example {
graph [goal="Implement the login feature"]