Language Reference

Complete syntax for .dip workflow files.

File Structure

Every .dip file contains exactly one workflow. The top-level structure has up to five sections, in this order:

workflow <name>
Header
goal, requires, start, exit
Defaults (optional)
model, provider, ...
Vars (optional)
key: value, ...
Node Definitions
agent, human, tool, ...
Edges (optional)
A -> B when ...

Dippin uses indentation-sensitive syntax (like Python). Use 2 spaces or tabs consistently. The canonical formatter always outputs 2-space indentation.

Workflow Header

The workflow declaration is the first line, followed by required and optional header fields:

workflow my_pipeline
  goal: "Ask user for a task, implement it, review, ship"
  start: AskUser
  exit: Done
FieldRequiredDescription
workflow <name>YesDeclares the workflow and its identifier
goal: <text>NoHuman-readable objective for this pipeline
requires: <id>[, <id>...]NoWorkflow-level declared prerequisites (advisory; comma-separated identifiers — tools, MCP servers, env vars). Mirrors node-level reads: / writes: for shape.
start: <NodeID>YesEntry point node — execution begins here
exit: <NodeID>YesTerminal node — execution ends here

Defaults Block

The optional defaults block sets graph-level configuration that applies to all nodes unless overridden at the node level.

  defaults
    model: claude-opus-4-6
    provider: anthropic
    retry_policy: standard
    max_retries: 3
    fidelity: high
    max_restarts: 5
    cache_tools: true
    compaction: summary
FieldTypeDescription
modelStringDefault LLM model for all agent nodes
providerStringDefault LLM provider (e.g., “openai”, “anthropic”)
retry_policyStringDefault retry strategy name
max_retriesIntegerDefault max retry attempts per node
fidelityStringDefault checkpoint fidelity level
max_restartsIntegerMax loop restarts before pipeline failure (default: 5)
cache_toolsBooleanWhether to cache tool call results
compactionStringContext compaction mode for long pipelines

Tool safety

Tool nodes that shell out can be constrained by two defaults consumed by the tracker runtime:

  • tool_commands_allow — comma-separated glob allowlist. When set, tracker rejects tool-node commands that do not match any pattern.
  • tool_denylist_add — comma-separated globs appended to tracker’s default denylist (on top of tracker’s built-in blocks).
workflow Safe
  goal: "Constrained tool execution"
  start: A
  exit: A

  defaults
    tool_commands_allow: "git *,make *"
    tool_denylist_add: "rm -rf /,dd *"

  # ...

Values pass through to tracker verbatim; dippin-lang does not validate glob syntax.

Vars Block

The optional vars block declares user-defined variables that are substituted wherever $key placeholders appear in prompts and commands.

  vars
    source_ref: "references/claude-agent-sdk-python/src"
    target_name: claude-agents-rs
    target_module: "claude-agents-rs/src/"

Values can be quoted strings or bare identifiers. Keys must be unique — duplicate keys cause a parse error.

Vars are exported as graph-level DOT attributes so they round-trip through dippin export-dot and dippin migrate.

Node Kinds

There are 8 node kinds, each with its own syntax and configuration:

agent
LLM interaction
human
Decision gate
tool
Shell command
parallel
Fan-out
fan_in
Join
subgraph
Sub-pipeline
conditional
Pure routing
manager_loop
Child supervisor

agent

Agent nodes invoke an LLM. They are the most configurable node kind. Key fields include model, provider, prompt, system_prompt, max_turns, auto_status, and goal_gate.

  agent Analyze
    label: "Analyze the request"
    model: claude-opus-4-6
    provider: anthropic
    goal_gate: true
    auto_status: true
    reads: human_response
    writes: analysis
    prompt:
      You are a senior software architect.
      Analyze the following request carefully.
FieldTypeDescription
modelStringLLM model to use (overrides defaults)
providerStringLLM provider (e.g., “anthropic”, “openai”)
backendStringPer-node backend override (e.g., native, claude-code, acp)
working_dirStringPer-node working directory override for isolated execution.
promptBlockMultiline prompt text sent to the model
system_promptBlockSystem-level instructions prepended before the prompt
reasoning_effortStringExtended thinking effort level: none, minimal, low, medium, high, xhigh, max. Controls how much reasoning budget the LLM spends.
fidelityStringCheckpoint fidelity level for state persistence.
max_turnsIntegerMaximum conversation turns before the node exits
auto_statusBooleanAutomatically extract STATUS: success/fail from model output into ctx.outcome
goal_gateBooleanMarks this node as a goal gate — requires retry_target or fallback_target for recovery
readsStringContext key this node reads as input (advisory metadata)
writesStringContext key this node writes as output (advisory metadata)
response_formatStringStructured output mode: json_object or json_schema. Instructs the model to return valid JSON.
response_schemaBlockJSON Schema definition enforced when response_format: json_schema is set. Must be valid JSON.
paramsBlockArbitrary key-value pairs forwarded to the provider API. Keys must not duplicate first-class fields (see DIP133).
cmd_timeoutDurationMaximum wall-clock time for this agent call before the runtime cancels and errors (e.g., 30s, 2m).

human

Human nodes pause execution and wait for human input. Two modes: choice (predefined options from edge labels) and freeform (open text input).

  human Approve
    label: "Ship it?"
    mode: choice
    default: "yes"

tool

Tool nodes execute shell commands. The command’s stdout is captured as ctx.tool_stdout and stderr as ctx.tool_stderr. Always include a timeout.

  tool RunTests
    label: "Run test suite"
    timeout: 60s
    command:
      #!/bin/sh
      set -eu
      pytest --tb=short 2>&1

parallel

Parallel nodes fan execution out to multiple branches that run concurrently. Every parallel must have a matching fan_in.

  parallel FanOut -> TaskA, TaskB, TaskC

fan_in

Fan-in nodes join concurrent branches back together. Sources must match the targets of a corresponding parallel node.

  fan_in Join <- TaskA, TaskB, TaskC

subgraph

Subgraph nodes embed another workflow as a single step. Parameters are passed via the params.* namespace.

  subgraph ReviewProcess
    ref: review_pipeline
    params:
      strict: true
      model: gpt-5.4

manager_loop

Manager loop nodes supervise a child sub-pipeline: they spawn it, poll it on a configurable cadence, and can steer it by injecting additional context during execution. They map to Tracker’s stack.manager_loop construct and export as DOT shape house.

  manager_loop QualityGate
    label: "Quality Gate Supervisor"
    subgraph_ref: quality_loop.dip
    poll_interval: 30s
    max_cycles: 12
    stop_condition: stack.child.outcome = success
    steer_condition: stack.child.cycles = 5
    steer_context:
      hint: halfway_through
      priority: high
FieldTypeDescription
subgraph_refStringRequired. Path to the child .dip file (DIP135 if missing or not found)
poll_intervalDurationHow often to poll the child (e.g. 30s, 5m). 0 means event-driven
max_cyclesIntegerMaximum poll cycles before the node exits. 0 = unbounded — triggers DIP137
stop_conditionConditionExpression over stack.child.* evaluated each cycle; when true the loop exits
steer_conditionConditionWhen true, inject steer_context into the running child
steer_contextmap[string]stringKey-value pairs injected on steer. Inline k=v, k=v or indented block form. Inline values may not contain commas

Runtime state exposed as context variables: stack.child.cycles, stack.child.outcome, stack.child.status.

Lint codes: DIP135 (subgraph_ref missing or file not found), DIP136 (invalid control field value), DIP137 (unbounded loop — max_cycles: 0).

See docs/nodes.md for the complete field reference.

Conditional Nodes

Conditional nodes evaluate outgoing edge conditions without making an LLM call — pure routing with zero token cost.

conditional CheckOutcome
  label: "Route by Result"

Conditional nodes accept only common fields (label, class, reads, writes). No prompt, model, or provider. Maps to diamond shape in DOT export.

Edges

The edges block defines connections between nodes. Each edge is a single line:

<FromID> -> <ToID> [when <condition>] [label: <text>] [weight: <int>] [restart: true]

Basic and Conditional Edges

  edges
    AskUser -> Interpret
    Interpret -> Validate
    Validate -> Approve   when ctx.outcome = success
    Validate -> Retry     when ctx.outcome = fail

Edge Attributes

AttributeTypeDescription
when <expr>ConditionBoolean guard — edge only traversed if true
label: <text>StringHuman-readable label (used for human gate choices)
weight: <int>IntegerPriority hint — higher wins among competing edges
restart: trueBooleanMarks this as a back-edge (loop restart)

Restart Edges

Restart edges create controlled loops. When followed, the engine increments a restart counter (max controlled by max_restarts), clears downstream nodes, resets retry budgets, and resumes from the target.

    Task -> Start when ctx.outcome = fail label: "retry" restart: true

Conditions

Conditions appear on edges after the when keyword. All operators perform string comparison.

Comparison Operators

OperatorMeaningExample
=, ==String equalityctx.outcome = success
!=String inequalityctx.outcome != fail
containsSubstring matchctx.response contains "approved"
not containsNegated substringctx.tool_stdout not contains all-done
startswithPrefix matchctx.response startswith "yes"
endswithSuffix matchctx.response endswith "done"
inValue in listctx.status in "pass,fail,skip"

Logical Operators

OperatorMeaningPrecedence
notLogical negationHighest
andLogical ANDMedium
orLogical ORLowest

Parentheses control precedence:

    A -> B when ctx.outcome = success and ctx.score = high
    A -> C when ctx.outcome = fail or ctx.status = blocked
    A -> D when (ctx.x = 1 or ctx.y = 2) and ctx.z = 3

Multiline Blocks

Fields like prompt and command support multiline content. Write the key followed by :, then indent the content on subsequent lines:

  agent MyAgent
    prompt:
      You are a code reviewer.

      ## Rules
      - Check for bugs
      - Check for security issues

      ## Context
      ${ctx.last_response}

The first content line’s indentation sets the baseline. All content is de-indented by that amount. Empty lines are preserved. The block ends when indentation returns to or above the field’s level. No quoting or escaping needed.

  tool RunTests
    timeout: 60s
    command:
      #!/bin/sh
      set -eu
      if pytest --tb=short 2>&1; then
        printf 'pass'
      else
        printf 'fail'
        exit 1
      fi