If your team has existing pipelines authored in
Graphviz DOT, Dippin provides
an automated migration path. dippin migrate converts DOT files
to Dippin syntax, and dippin validate-migration verifies structural parity
between the original and the converted file.1
DOT was never designed for AI pipelines. The pain points stack up:
| Problem in DOT | How Dippin fixes it |
|---|---|
Prompts need \n escaping and \" quoting |
Indentation-based multi-line blocks, no escaping |
| No validation beyond syntax | 34 diagnostic codes: structural errors + semantic warnings |
| No testing framework | Scenario testing with .test.json files |
| No formatting standard | Idempotent dippin fmt with canonical field ordering |
| No cost estimation | dippin cost and dippin optimize |
| Node types inferred from shape attribute | Explicit agent, tool, human keywords |
We'll migrate consensus_task.dot, a 17-node multi-model consensus
workflow with conditional edges and restart loops.
$ dippin migrate --output consensus_task.dip consensus_task.dot OK Migrated consensus_task.dot -> consensus_task.dip 17 nodes, 18 edges converted 2 warnings: - condition prefix "context." converted to "ctx." - condition prefix "context.internal." converted to "ctx.internal."
The converter handles the mechanical translation: DOT shape attributes map to
Dippin node kinds, escaped strings become multi-line blocks,
edge attributes become when clauses, and graph-level attributes
become workflow header fields.
How DOT constructs map to Dippin:
| DOT | Dippin |
|---|---|
shape=box | agent |
shape=hexagon | human |
shape=component | tool |
shape=Mdiamond | agent (start node) |
shape=Msquare | agent (exit node) |
llm_provider="anthropic" | provider: anthropic |
llm_model="claude-opus-4-6" | model: claude-opus-4-6 |
prompt="text\nmore text" | prompt: + indented block |
condition="outcome=success" | when ctx.outcome = success |
loop_restart=true | restart: true |
A key section of the consensus workflow in both formats:
ReviewConsensus [ shape=box, label="Review Consensus", llm_provider="anthropic", llm_model="claude-opus-4-6", reasoning_effort="high", max_retries=1, retry_target="Implement", prompt="Produce final consensus verdict. Use success when ready to exit, retry when rework is required, fail when blocked." ]; ReviewConsensus -> Exit [condition="outcome=success", label="pass"]; ReviewConsensus -> Postmortem [condition="outcome=retry", label="retry"]; ReviewConsensus -> Exit;
agent ReviewConsensus label: "Review Consensus" model: claude-opus-4-6 provider: anthropic reasoning_effort: high max_retries: 1 retry_target: Implement prompt: Produce final consensus verdict. Use success when ready to exit, retry when rework is required, fail when blocked. edges ReviewConsensus -> Exit when ctx.outcome = success label: pass ReviewConsensus -> Postmortem when ctx.outcome = retry label: retry ReviewConsensus -> Exit
The Dippin version reads more clearly, and you can validate, lint, test, and format it with the toolchain.
validate-migration compares the DOT original against
the Dippin conversion to verify nothing was lost:
$ dippin validate-migration consensus_task.dot consensus_task.dip PASS Migration parity verified Nodes: 17/17 matched Edges: 18/18 matched Conditions: 4/4 matched
Parity checking compares node count, node names, edge connections, condition expressions, and key attributes (model, provider, labels). It catches dropped nodes and mangled conditions.
$ dippin validate consensus_task.dip PASS consensus_task.dip
$ dippin lint consensus_task.dip PASS consensus_task.dip (0 errors, 0 warnings)
$ dippin fmt --write consensus_task.dip
The formatter normalizes field ordering, indentation, and spacing, so the migrated file follows the same conventions as hand-written Dippin code.
DOT conditions often use context. as the variable prefix. Dippin uses
ctx.. The migrator handles the translation automatically:
[condition="context.internal. loop_restart_count=0", loop_restart=true]
when ctx.internal.loop_restart_count = 0 restart: true
DOT files often omit fields that Dippin encourages. After migration, run
dippin lint to find the gaps:
$ dippin lint migrated.dip WARN migrated.dip DIP111: tool node "RunScript" has no timeout DIP103: node "Process" has no prompt
These warnings are actionable -- add the missing fields to strengthen the pipeline.
DOT graph attributes like default_fidelity and default_max_retry
become Dippin defaults block fields:
# DOT: graph [ default_fidelity="truncate", default_max_retry=3 ] # Becomes: defaults fidelity: truncate max_retries: 3
Migrate an entire directory of DOT files:
$ for f in pipelines/*.dot; do dippin migrate --output "${f%.dot}.dip" "$f" done
Then verify parity for all of them:
$ for f in pipelines/*.dot; do dip="${f%.dot}.dip" [ -f "$dip" ] && dippin validate-migration "$f" "$dip" done PASS pipelines/consensus_task.dot <-> pipelines/consensus_task.dip PASS pipelines/code_review.dot <-> pipelines/code_review.dip PASS pipelines/deploy_check.dot <-> pipelines/deploy_check.dip
The Dippin CI workflow includes a
migration parity step on every push. It finds all .dot files with a
corresponding .dip file and verifies they remain structurally equivalent:
# From .github/workflows/ci.yml
- name: Validate migration parity
run: |
failed=0
for f in examples/*.dot; do
base=$(basename "$f" .dot)
dip="examples/${base}.dip"
if [ -f "$dip" ]; then
if ! ./dippin validate-migration "$f" "$dip" 2>&1; then
failed=1
fi
fi
done
exit $failed
As you improve the Dippin version, the CI check confirms it stays structurally compatible with the DOT original. Once you're confident, delete the DOT file and the check stops running for that pair.
After migration, you can take advantage of features DOT doesn't support:
| Feature | What to do |
|---|---|
| Scenario tests | Create a .test.json file to test execution paths |
| Goal gates | Add goal_gate: true to critical quality check nodes |
| Data flow declarations | Add reads/writes to enable DIP106/DIP107/DIP112 checks |
| Cost estimation | Run dippin cost and dippin optimize |
| Health reports | Run dippin doctor for a letter-grade assessment |
| Watch mode | Run dippin watch pipeline.dip for live feedback |
Your DOT pipelines are now Dippin files with full toolchain support.
dot installed. But DOT's string-quoting requirements made multi-line prompts painful to write and impossible to diff-review in PRs. The tipping point was a 40-line prompt that required 80+ characters of escape sequences -- at that point, building a purpose-built language was cheaper than the ongoing authoring tax.