Skip to main content
Not every step in a workflow needs an LLM. Sometimes you need to run a shell command to collect system information, post-process an agent’s JSON output with a Python script, or call an external tool that doesn’t have an API wrapper. dagraph’s bash and python_exec node types let you mix code execution with LLM calls in the same DAG, with stdout becoming the node’s artifact that downstream nodes can reference just like any agent output.

How exec nodes fit into a DAG

Exec nodes follow the same dependency model as agent nodes. Their output (stdout) is stored as an artifact and referenced by downstream nodes via {{ node_id }}. A non-zero exit code fails the node and halts any downstream nodes that depend on it. The code_and_llm.yaml example collects system information with bash, parses it with Python, then summarizes it with an LLM:
Run it:
Any DAG that contains bash or python_exec nodes requires the --sandbox flag. Without it, dagraph refuses to run the DAG to prevent accidental code execution.

The bash node

Use bash to run shell commands. The command field is a Jinja template — you can interpolate DAG inputs and upstream node outputs just like in an agent prompt.
Key fields:

The python_exec node

Use python_exec to run Python code inline. The code field is also Jinja-rendered, which means curly braces in Python code (f-strings, dict literals, format strings) need to be wrapped in {% raw %}...{% endraw %} to prevent Jinja from interpreting them:
Without {% raw %}, Jinja sees {{ collect | tojson }} as a template expression (which is correct), but it also tries to interpret {k: v for k, v in parsed.items()} as a template block and fails.
The print() call is how your Python code returns its result. Whatever you print to stdout becomes the node’s artifact. Print a JSON string if downstream nodes or agents need structured data.

Sandbox backends

dagraph supports two sandbox backends, selected via --sandbox:
Runs the command or script as a child process of the dagraph scheduler. Fast and simple — no Docker required.
Use inprocess when you own the code being executed — your own scripts, trusted utilities, or commands you’ve written yourself.
Use --sandbox docker for any code you did not write yourself or that comes from user-controlled input. The inprocess sandbox runs code in the same process space as dagraph, so malicious code can access environment variables, API keys, and the filesystem. Docker containers are isolated by default.

Output size limit

Both bash and python_exec nodes default to a 1 MB stdout limit. If a node’s output exceeds max_output_bytes, dagraph truncates it and fails the node with an error. Override the limit per node if you expect large outputs:
Downstream agent nodes receive this text in their context window — keep outputs as compact as possible to avoid inflating token costs.

Passing exec output to an LLM

Exec node output is available in downstream agent prompts via {{ node_id }} — exactly like any other node:

Parallel agents

Run exec nodes and agent nodes in parallel waves across the same DAG.

Multi-provider fallback

Add fallback chains to the agent nodes downstream of your exec steps.