Skip to main content

Aqueduct Logo

Aqueduct

The AI harness for data pipelines: the model proposes, the harness decides.
One blueprint. Spark or DuckDB. Your data never leaves your servers.

PyPI Python License
Test Suite Compatibility Matrix Project status: beta Stars


Why Aqueduct

A pipeline fails at 3 a.m. over a column rename upstream. Somebody gets paged, scrolls a four-kilobyte stack trace, and hand-writes a one-line fix. Aqueduct turns that night into a pull request waiting for review in the morning.

A failed run producing a pending patch

Wake up to a reviewable fix instead of a wall of errors.


  • Declarative, not DAG code. Pipelines are YAML Blueprints. No PySpark boilerplate, no operator classes. Bring your own scheduler; Aqueduct is the control plane on top of the engine.
  • Self-healing rather than alerting. On failure, an LLM agent diagnoses the root cause and emits a structured patch. The patch must clear guardrail, lineage, and sandbox gates before it touches your pipeline. The agent cannot generate code or touch a shell. A failure it has solved before heals from memory, with zero LLM tokens.
  • Observable by construction. Every run, heal attempt, and column-lineage edge lands in a queryable store. Nothing is added to the hot path to make that happen.
  • Model-agnostic and local-first. Anthropic natively, or any OpenAI-compatible endpoint: OpenRouter, DeepSeek, Groq, a local 7B on Ollama or LM Studio. Set provider and base_url, done. Point it at a model inside your perimeter and your data, schemas, and error traces never leave your servers. Multi-model cascades escalate to a bigger model only when the small one gets stuck.
  • The harness owns correctness; the model only proposes. The LLM does exactly one thing deterministic code cannot: turn an unstructured failure into a structured hypothesis. Everything after that is deterministic. A constrained patch grammar with 15 operations and no code generation. Validation gates. Budget caps. A signature cache. That division of labor is why a small local model holds up here when raw code-generating assistants don't.

From failure to pull request

$ aqueduct run blueprints/orders.yml
  ✗ orders.load  PATH_NOT_FOUND: data/in.csv
  agent: root cause identified (confidence 0.92), patch staged
  → patches/pending/orders-20260824T031412.json

$ aqueduct patch pr orders-20260824T031412 --blueprint blueprints/orders.yml
  ✓ gates passed  guardrails · compile · lineage · sandbox
  ✓ PR opened  branch=aqueduct/heal/orders-20260824T031412
  https://github.com/you/your-repo/pull/128

The patch is a Git-diffable JSON document, never code:

// patches/pending/orders-20260824T031412.json (abridged)
{
  "category": "config_error",
  "root_cause": "Ingress 'load' reads data/in.csv, but the upstream job renamed it to data/input.csv.",
  "confidence": 0.92,
  "rationale": "PATH_NOT_FOUND on data/in.csv; a sibling data/input.csv exists with a matching schema, so the path is stale rather than the data missing.",
  "operations": [
    { "op": "set_module_config_key", "module_id": "load", "key": "path", "value": "data/input.csv" }
  ]
}

The trust boundaries behind all of this, and what each gate actually enforces, are written down in the Threat Model.

Table of contents

Supported engines

Apache Spark

Apache Spark
Distributed batch at cluster scale.
Delta Lake, JDBC, custom data sources,
the full Blueprint grammar.
DuckDB

DuckDB
Single-node production, zero JVM.
Ships with the base install.
Same blueprint, same CLI, same healing loop.

aqueduct run pipeline.yml --set deployment.engine=duckdb

One blueprint format, one CLI, per-engine execution. Each engine declares a capability table, and the compiler checks your blueprint against it. If the target engine can't do something, you get a compile error naming the exact capability instead of a runtime surprise.

Three promises, each enforced by machinery rather than review discipline:

  • Every supported entry in the capability matrix is backed by a named test that runs on that engine in CI.
  • Nothing that can change your results is ever silently ignored. The engine honors it, warns about it, or refuses to compile.
  • Healed blueprints record which engine produced each patch. Deploy a DuckDB-healed blueprint to Spark and the compiler tells you.

The two engines do not promise identical values, and Aqueduct does not pretend they do. SQL dialects genuinely differ; the known differences are cataloged in the Compatibility Matrix. Your Assert rules run on both engines, so the properties you care about are the properties that get checked. That is your portability contract.

Polyglot pipelines

Engines can also mix inside one blueprint. Set engine: on a module and everything downstream inherits it until another module overrides:

modules:
  - id: extract        # heavy join across two warehouses
    type: Channel
    engine: spark
    config: { op: sql, query: "..." }

  - id: aggregate      # small result, no cluster needed
    type: Channel
    engine: duckdb
    config: { op: sql, query: "SELECT region, sum(amount) FROM extract GROUP BY region" }

The compiler partitions the DAG into engine islands and inserts a handoff at each boundary. The handoff materializes data as parquet at a configurable location, visible in observability like any other module, with bytes and duration recorded. Each handoff point is announced at compile time, because the extra I/O is a real cost you should see before the run. If an island fails, a rerun picks up the already-materialized upstream data instead of recomputing it.

The healing flow

When a pipeline fails, Aqueduct does not throw a stack trace at an LLM and hope. Healing is a staged, auditable pipeline. The model works inside a constrained grammar: it cannot write code, edit files, or run shell commands.

A generated patch clears five gates plus a compile-check before it ever touches the Blueprint. Gate 1 is guardrails: deterministic policy checks on paths, operations, and confidence. The compile-check follows immediately, and the patched Blueprint must still parse. Then Gate 2, lineage: does the patch break a downstream column consumer. Gate 3, sandbox: replay against representative data. Gate 4, plan regression. Gate 5, resolvability: any dependency the patch declares must actually be installable. They run in that order and the first failure wins:

✓ guardrails  →  ✓ compile-check  →  ✓ lineage  →  ✓ sandbox  →  ✓ plan-regression  →  ✓ resolvability  →  patch applied

aqueduct patch preview --sandbox runs the same pyramid on demand, before you decide to apply.

Figure 1: The Healing Flow

Approval modes

Who applies a generated patch. Deterministic guardrails (allowed paths, forbidden operations, minimum confidence) bound every patch regardless of mode.

Mode Who applies the patch When the Blueprint changes Use when
disabled LLM never fires Never Healing is intentionally off.
human Engineer reviews and applies (patch apply, or patch pr for review in a pull request) Only after human accepts Production. Default behind CI/CD.
ci External CI receives the patch and opens a PR; aqueduct patch pr does branch, push, and PR in one step Only after merge Production with code review.
auto Aqueduct applies in-memory, re-validates, writes only if the re-run succeeds Only on a successful re-run Trusted environments: dev, scoped pipelines. Requires an explicit allowed_paths allowlist; an unconfigured one refuses file-touching patches outright.

Low-confidence patches and any guardrail violation auto-escalate to human review.

Why it holds up

  • Every change is visible. A patch is a structured diff with a rationale and a confidence score. Low confidence escalates to a human.
  • Live data stays safe. The sandbox validates each patch against representative data before any live write.
  • Loops are bounded. A multi-axis budget caps wall-clock time, tokens, reprompts, and stuck-signature windows. A rolling rate limit caps heals per hour per blueprint.
  • Injection is assumed, not ignored. Error text and data samples reach the model framed as untrusted data, and everything the model produces must still clear the deterministic gates. The full analysis is in the Threat Model.
  • Decisions are auditable. Every LLM turn is recorded with the gate that rejected it, a stable error signature, and the prompt version. One run id joins every iteration of a heal.
  • Reach beyond the blueprint, with the same discipline. Healing extends past pipeline definitions into engine and session config (allowlisted keys only) and dependency declarations, each behind its own gate. Data mutation stays off, period: the patch grammar contains no operation that can touch your data, and a test enforces that the grammar stays that way.

For the stage-by-stage detail, see the Blueprint & Engine Spec.

Architecture

Where Aqueduct sits in a data platform: the control plane between your scheduler and the engine.

Figure 2: Aqueduct at a glance



Inside the box, Aqueduct is a single CLI that runs on the driver, with no servers and no daemons. Logic flows through four immutable layers:

Figure 3: Architecture
  • Parser validates YAML into an immutable AST.
  • Compiler resolves context, expands Arcades and macros, extracts column lineage, gates the blueprint against the target engine's capabilities, and assembles a fully-resolved Manifest.
  • Executor runs the Manifest on the target engine. Engines register through an entry-point protocol; Spark code is isolated under executor/spark/, DuckDB under executor/duckdb_/. The core never imports an engine by name.
  • Surveyor records runs, failures, and lineage to pluggable stores and triggers the Agent on failure.

Core concepts

Concept Purpose
Blueprint Your pipeline definition
Ingress Reads sources (CSV, Parquet, Delta, JDBC)
Channel Transformations (SQL or native ops)
Egress Writes sinks (overwrite, append, Delta merge)
Junction Fan-out (conditional, broadcast, partition)
Funnel Fan-in (unions, coalesce, zip)
Spillway Routes bad rows to error sink
Probe Non-blocking observability taps
Regulator Gate driven by Probe signals (skip / abort / trigger agent)
Assert Inline quality gates
Depot Cross-run state & watermarks
Arcade Reusable sub-pipelines

Full details in the References.

Getting started

Installation

pip install aqueduct-core              # DuckDB engine included, no JVM needed
pip install "aqueduct-core[spark]"     # adds Apache Spark + Delta Lake

Requires Python 3.11+ and, for the spark extra only, Java 17. Every release is CI-tested from Spark 3.5 through 4.1; the exact tested combos live in the Compatibility Matrix.

Extras exist for Airflow (airflow), cloud secret managers (secrets), networked stores (stores), and everything at once (all); compose them as needed, for example pip install "aqueduct-core[spark,airflow,aws]". The full list with rationale is in pyproject.toml and the Production Guide.

A first blueprint

aqueduct: "1.0"
id: hello.pipeline
name: Hello Pipeline

macros:
  active: "status = 'active' AND deleted_at IS NULL"

modules:
  - id: load
    type: Ingress
    label: Load orders
    config: { format: csv, path: "data/in.csv", options: { header: true } }

  - id: clean
    type: Channel
    label: Filter active
    config:
      op: sql
      query: "SELECT order_id, amount FROM load WHERE {{ macros.active }}"

  - id: save
    type: Egress
    label: Write parquet
    config: { format: parquet, path: "data/out/", mode: overwrite }

edges:
  - { from: load,  to: clean }
  - { from: clean, to: save }

agent:
  approval: human

Run it on either engine:

aqueduct run blueprints/hello.yml                                  # engine from aqueduct.yml
aqueduct run blueprints/hello.yml --set deployment.engine=duckdb   # no cluster, no JVM

Engine-wide defaults live in a separate aqueduct.yml (target engine, LLM provider, store backends, danger settings). Inline module tests live in *.aqtest.yml. Repeatable healing benchmarks live in *.aqscenario.yml. The Gallery has runnable examples of each.

Five commands to know

  1. aqueduct doctor blueprints/hello.yml is the preflight check. It validates YAML, resolves paths, verifies LLM reachability, and opens stores.
  2. aqueduct run blueprints/hello.yml executes the pipeline. On failure, the agent generates a patch under patches/pending/.
  3. aqueduct patch pr <id> --blueprint blueprints/hello.yml turns a staged patch into a pull request on a heal branch (or review locally with patch apply).
  4. aqueduct test blueprints/hello.aqtest.yml runs Channel / Junction / Funnel modules against inline data, without touching Ingress, Egress, or any external I/O.
  5. aqueduct benchmark gallery/aqscenarios/ --model <a> --model <b> compares LLM models against simulated failures, so you can pick the cheapest model that heals your pipelines. No engine required.

Full reference in CLI Reference.

What you get

Capability What it does Details
Self-healing LLM diagnoses failures, emits gated, Git-diffable patches with human, CI, or auto approval Spec §8
Heal-as-PR aqueduct patch pr applies a patch on a heal branch and opens a PR through your normal review CLI Reference
Heal memory Failure signatures cache validated fixes; repeat failures heal with zero LLM tokens Spec §8.2
Engine capability gate Unsupported features fail at compile time with a named capability, never mid-run Compatibility Matrix
Portable types One type vocabulary across engines; ambiguous spellings rejected at parse time Spec §9
Heal provenance Blueprints record which engine healed them; cross-engine deploys warn at compile Spec §8.14
Polyglot pipelines Per-module engine choice; automatic, observable handoff at engine boundaries Spec
Observability store Runs, failures, heal attempts, metrics in queryable DuckDB/Postgres Observability Guide
Column lineage Compile-time, zero engine actions; powers the patch lineage gate Spec §7
Data quality Inline Assert rules + Spillway quarantine: bad rows are routed to a typed error sink instead of being dropped Spec §4.4
Module tests aqueduct test runs transforms against inline fixtures, with no I/O and no cluster CLI Reference
LLM benchmark aqueduct benchmark scores models against simulated failures CLI Reference
Safety rails Guardrails, multi-axis budgets, hourly heal caps, sandbox replay, a written threat model Threat Model

References

Contributing

Contributions are welcome! See CONTRIBUTING.md.

Aqueduct is Apache 2.0 licensed: free and open source, with no telemetry and no lock-in.

Download files

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

Source Distribution

aqueduct_core-2.2.0.tar.gz (1.2 MB view details)

Uploaded Source

Built Distribution

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

aqueduct_core-2.2.0-py3-none-any.whl (1.1 MB view details)

Uploaded Python 3

File details

Details for the file aqueduct_core-2.2.0.tar.gz.

File metadata

  • Download URL: aqueduct_core-2.2.0.tar.gz
  • Upload date:
  • Size: 1.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aqueduct_core-2.2.0.tar.gz
Algorithm Hash digest
SHA256 fb71264e931f9ff2e0256395fe032cce16dc9da4b415275ae0eff900cbce9bce
MD5 baef5ccc010977a121dac7a44a9863c4
BLAKE2b-256 12928ca05a8ee752e6b5ab4543dda93c155238d2bf2552cc772b7154d2364380

See more details on using hashes here.

Provenance

The following attestation bundles were made for aqueduct_core-2.2.0.tar.gz:

Publisher: release.yml on sadigaxund/Aqueduct

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

File details

Details for the file aqueduct_core-2.2.0-py3-none-any.whl.

File metadata

  • Download URL: aqueduct_core-2.2.0-py3-none-any.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aqueduct_core-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5ba55f7982bd1e81635e81bdfb1464df94e4eadd50e4889b67f68ec0bf566db0
MD5 8f6e1cb9a8323c4f4d0a345318d285b5
BLAKE2b-256 200bdb62e377330ca233123baf237a9c09a1004cb2698120f1a9c39c7993dd4a

See more details on using hashes here.

Provenance

The following attestation bundles were made for aqueduct_core-2.2.0-py3-none-any.whl:

Publisher: release.yml on sadigaxund/Aqueduct

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

Release history Release notifications | RSS feed

2.3.0

2 files

2.2.1

2 files

This release

2.2.0 This release

2 files

2.1.3

2 files

2.1.2

2 files

2.1.1

2 files

2.1.0

2 files

2.0.5

2 files

2.0.4

2 files

2.0.3

2 files

2.0.2

2 files

2.0.1

2 files

2.0.0

2 files

1.3.3

2 files

1.3.2

2 files

1.3.1

2 files

1.3.0

2 files

1.2.2

2 files

1.2.1

2 files

1.2.0

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1.post0

2 files

1.0.1

2 files

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