Migrating from DOT to Dippin

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

Why Migrate?

DOT was never designed for AI pipelines. The pain points stack up:

Problem in DOTHow 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

The Migration Process

We'll migrate consensus_task.dot, a 17-node multi-model consensus workflow with conditional edges and restart loops.

Run the Converter

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

Understand the Mapping

How DOT constructs map to Dippin:

DOTDippin
shape=boxagent
shape=hexagonhuman
shape=componenttool
shape=Mdiamondagent (start node)
shape=Msquareagent (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=truerestart: true

Compare Side by Side

A key section of the consensus workflow in both formats:

DOT
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;
Dippin
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.

Verify Structural Parity

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.

Validate and Lint the Result

$ dippin validate consensus_task.dip
PASS  consensus_task.dip
$ dippin lint consensus_task.dip
PASS  consensus_task.dip  (0 errors, 0 warnings)

Format for Canonical Style

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

Common Parity Issues

Condition Prefix Differences

DOT conditions often use context. as the variable prefix. Dippin uses ctx.. The migrator handles the translation automatically:

DOT
[condition="context.internal.
loop_restart_count=0",
 loop_restart=true]
Dippin
when ctx.internal.loop_restart_count = 0
  restart: true

Missing Fields

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.

Graph-Level Defaults

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

Batch Migration

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 CI Safety Net

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.

Post-Migration Improvements

After migration, you can take advantage of features DOT doesn't support:

FeatureWhat 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

What's Next?

Your DOT pipelines are now Dippin files with full toolchain support.

Notes

  1. DOT was chosen as the original authoring format because Graphviz is ubiquitous and every developer already has 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.