Skip to main content

Dagic

A minimal workflow DAG (Directed Acyclic Graph) definition language and an asynchronous execution engine, implemented in Python.

Dagic lets you describe a computation graph using a tiny, deliberately limited language. The graph's nodes are functions supplied by your host program; the edges are the function arguments. At compile time the graph is type-checked, and at run time it is executed concurrently across its independent branches.

Why

Dagic is aimed at bringing piping capabilities to LLMs.

In traditional tool calling, the agent is itself responsible for shuttling state between tools. Every intermediate result has to round-trip through the model — as an argument (or part of it) — which wastes turns: the agent spends tokens describing values it already produced instead of deciding what to do next.

The common fix is a code execution tool, which lets the model chain calls with ordinary variable assignments. That is trivial in a local harness like a coding CLI, but complicated and expensive for server-side harnesses, which must sandbox arbitrary code, manage runtimes, and defend a large attack surface.

Dagic sits in the gap between simple tool calls and full code execution: a workflow orchestration layer that is more expressive than a single tool call (arbitrary chains, parallelism, type checking) yet far safer and simpler to host than arbitrary code. The language is stripped down to just the two operations you need to define a DAG — assignment and function calls — so the model composes existing tools into controlled, typed, verifiable workflows instead of running free-form code.

Installation

Requires Python 3.10+.

pip install dagic

Syntax

A Dagic program is a sequence of statements. Each statement is one of:

  • an assignment: name = <expression>;
  • a function call: func(<expression>, ...);
result = add(create("1"), create("2"));
store(result);

joined = join(["Hello", "World!"], " ");
print(joined);
  • The functions are the nodes of the DAG.
  • The edges are the function arguments.
  • Every edge has a strict type. Passing a value of the wrong type to a function is a compile-time error.
  • Function definitions and types are provided by your host program (see below).

Data types

The only built-in types are:

  • strings"Hello, World!"
  • arrays["Hello", "World!"] (items must share the exact same type, and the array's type is inferred from them)

Every other type (numbers, objects, custom types, ...) is defined by the host program. An array's type edges come from functions registered for it, so passing a List[float] where a List[int] is expected is rejected at compile time.

Execution model

  1. A Dagic program compiles into a DAG.
  2. Execution starts from the terminal nodes — top-level function calls that return nothing (like print or store) — and runs backwards, resolving every dependency.
  3. Independent branches execute concurrently.
  4. A program without at least one terminal node is invalid. It would have no exit point, so it is rejected at compile time.
  5. Similarly, every named subgraph must be referenced at least once; unused ("orphan") subgraphs are rejected.

Defining functions (the host side)

Functions and their types are defined in Python and given to Dagic when it runs. Register them on a Module with the @register decorator. A registered function must:

  • have type annotations on every parameter and the return value;
  • have no *args, **kwargs, default parameters, or keyword-only parameters.
from dagic import Module

math = Module(name="math", desc="Basic arithmetic.")


@math.register
def create(value: str) -> float:
    """Create a float from a string."""
    return float(value)


@math.register
def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b

Functions whose -> None return type are terminals. Everything else produces a value that must be consumed by another call.

Using the engine

The package ships a small builtin module, float_math (float arithmetic: add, subtract, multiply, divide, power, modulus, floor_divide, absolute, negate, and a create from-string constructor). It is a Module instance exported as float_math.float_math. Pass any host modules you need to Dagic:

import asyncio
from dagic import Dagic, Module
from dagic.builtins import float_math

sink = []

io = Module(name="io", desc="I/O helpers.")


@io.register
def store(value: float) -> None:
    sink.append(value)


async def main():
    dagic = Dagic([float_math.float_math, io])
    await dagic.run('result = add(create("1"), create("2")); store(result);')
    print(sink)  # [3.0]


asyncio.run(main())

Dagic.run is async: it compiles the source against the registered modules, builds the graph, and executes it concurrently.

Development

make test    # run the test suite (pytest)
make format  # format with ruff

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dagic-0.2.0.tar.gz (28.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dagic-0.2.0-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

File details

Details for the file dagic-0.2.0.tar.gz.

File metadata

  • Download URL: dagic-0.2.0.tar.gz
  • Upload date:
  • Size: 28.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dagic-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b2c120370397223d55653afb3bb158cd9920b9995d0197e1f3cc9112f985d83c
MD5 bb70cb611a37965d5150ce4048b583c2
BLAKE2b-256 637d4975aad5ef16899f83b9e4257d7e69fbbce34806e1eb560a42f7f5b3db80

See more details on using hashes here.

Provenance

The following attestation bundles were made for dagic-0.2.0.tar.gz:

Publisher: publish.yml on RohitEdathil/dagic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dagic-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: dagic-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 16.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dagic-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7fc875e35b6d1207aef15d413d01074dc2675daa1f0af55b426bee4e60eb6a5f
MD5 aeb988a4f0fdbffec5f12e096615f80f
BLAKE2b-256 246908468da10809bf2b0b7b4cb818641587e47466e57e5d2873cb7bfd109566

See more details on using hashes here.

Provenance

The following attestation bundles were made for dagic-0.2.0-py3-none-any.whl:

Publisher: publish.yml on RohitEdathil/dagic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.1

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page