Quick links

Get started

Use the product

Reference

Resources


What is Duckle?

A visual data pipeline studio that runs on your laptop. Drag sources, transforms, validators, and sinks onto a canvas. Wire them together. Press Run. Duckle compiles the graph to SQL and executes it through a real columnar engine, with live previews, generated SQL on every node, and zero hidden state.

In short: a free, open-source, local-first alternative to hosted ETL platforms like Fivetran and Airbyte, that can also run dbt on DuckDB inside the same tool.

Three things make Duckle different from the heavyweights and the toy ETL tools:

  1. An AI assistant that ships in the box. Describe the pipeline you want in English; Duckie writes the JSON and drops it onto the canvas. The model runs locally - no API key, no telemetry, no cloud round-trip.
  2. 356 components ready at install time. Files, lakehouses, SQL databases, warehouses, NoSQL, vector DBs, streaming brokers, SaaS REST/GraphQL APIs, even FTP and IMAP - working today, not coming-soon.
  3. A self-contained binary you can audit. ~65 MB download. Engines install on first launch. Workspaces are plain files in a folder you choose. Diff them, branch them, ship them.

What's new in v0.5.6

Real-time WebSocket connectors, a geospatial projection toolkit, streaming XML at multi-GB scale, and a scheduler that finally speaks your local time.

  • WebSocket source and sink (#192). src.websocket connects to a ws:// or wss:// endpoint, optionally sends a subscribe frame, and collects up to maxMessages frames into rows; snk.websocket pushes each row back out as a text frame. For live market data, sensor streams, and real-time dashboards.
  • Geospatial projection toolkit (#188, #189, #190). Define Projection stamps a CRS onto geometry that has none, Reproject Geometry transforms between CRS with the target CRS preserved on the output, and Create Geometry builds a GEOMETRY column from X/Y, WKT, or WKB.
  • Dynamic time offsets in templates (#191). ${date+1d}, ${now-2h}, ${timestamp+30m}, and chained forms like ${date+1d-2h}, all resolved against a single run instant so every reference in a run stays consistent.
  • Streaming, multi-GB XML (#186). src.xml streams local, sftp://, and https:// inputs with automatic gzip / zip handling, and honours a declared schema instead of buffering the document.
  • Salesforce Bulk sink snk.salesforce.bulk (#164). Migration-scale write-back over Bulk API 2.0: DuckDB streams the upstream to CSV on disk and each ≤90 MB part runs the async job lifecycle (create → upload → poll → fetch result sets), so a multi-million-row load never lands in memory.
  • Local-time cron and a minute-accurate countdown (#194). Cron expressions now evaluate in the machine's local time zone, so "Every day at 03:00" means 03:00 where you are. The Next countdown shows hours and minutes ("in 1 h 7 min"), and a 5-field cron is accepted instead of silently never firing.
  • OAuth client credentials for any REST source (#195). Token minting is no longer Salesforce-only: give any REST connector a token URL and it mints a fresh access token per run. Suits a Xero Custom Connection, which needs HTTP Basic client auth.
  • JSON transform xf.jq (#173) for per-row jq over a JSON column, and CRS-aware measurements (#177) that pick planar or spheroid math from the column's own CRS.

Fixes: saved S3 and other non-Salesforce connection references resolve at run time (#185), Duckie honours a configured custom AI endpoint (#183), the Linux window stays resizable after un-maximize (#182), an externally set DUCKLE_DUCKDB_BIN wins over the bundled binary (#179), and a zero-row result no longer collapses to a lone json column (#170). macOS engine signing was also hardened so llama-server launches from a downloaded build (#89) - reports from macOS users welcome, as it could not be exercised on the Windows dev box.

Full notes: see the v0.5.6 release.


Meet Duckie - the local AI pipeline assistant

Describe what you need. Duckie writes the pipeline.

The sidebar on the right is Duckie AI Assistant - powered by Qwen 2.5 Coder 1.5B running through llama.cpp, downloaded once (~1.1 GB) and then run entirely on your CPU. Ask in plain English; Duckie streams back a valid Duckle pipeline definition. One click drops it onto the canvas, ready to inspect, tweak, and run.

Truly local The Qwen model runs as a llama-server subprocess on 127.0.0.1. No API keys. No network calls. Disconnect your wifi and it keeps working.
Streamed responses Tokens arrive as they're generated, with a blinking caret in the bubble. No "wait 20 seconds for the spinner to vanish" UX.
One-click insert When Duckie produces a JSON pipeline, an Insert into canvas button appears. The graph populates with positioned nodes, wired edges, and the props the model chose.
Bring-your-own-model option The chat plumbing is the same OpenAI-compatible HTTP interface used by xf.ai.llm / xf.ai.embed connectors. Point baseUrl at Ollama, llama.cpp, Cohere, OpenAI, Voyage - anything that speaks the OpenAI shape.
Sandboxed The model has no fs / net / tool access. It can only emit text - your pipeline JSON.

Why Duckle is different

Visual, never opaque The canvas compiles to SQL you can read, and every node has a live preview tab. No black box.
Local-first AI An assistant that runs on your laptop without an API key. Your prompts, your data, your machine.
Single-file binary, no bundled DB ~65 MB app (it embeds the headless runner + MCP server). DuckDB downloads on first launch with a guided step. AI engine is opt-in.
Native speed Execution runs through DuckDB: vectorized, columnar, local. A clean-and-export job that crawls in a spreadsheet finishes in milliseconds.
Git-friendly by design Pipelines, connections, contexts, and routines persist as plain files in a folder you pick. Diff them, branch them, review them.
356 components ready today Files, databases, warehouses, lakehouses, object stores, SaaS APIs, NoSQL, streaming brokers, vector DBs, FTP, IMAP, SMTP. Each is covered by tests.
Honest about scope Single-machine and embedded by design. Built to make local and small-team data work fast, not to replace a distributed warehouse.
60 UI languages Topbar, palette, chat assistant, properties panel, and common dialogs ship localized. English, Spanish, Chinese (Simplified + Traditional), Hindi, Arabic, Portuguese (Brazil), Bengali, Russian, Japanese, Punjabi, German, Korean, French, Vietnamese, Telugu, Marathi, Turkish, Tamil, Urdu, Persian, Polish, Italian, Ukrainian, Indonesian, Thai, Dutch, Hebrew, Swedish, Greek, Czech, Hungarian, Romanian, Filipino, Malay, Norwegian, Danish, Finnish, Catalan, Bulgarian, Slovak, Croatian, Serbian, Slovenian, Lithuanian, Latvian, Estonian, Khmer, Burmese, Sinhala, Nepali, Swahili, Afrikaans, Welsh, Irish, Icelandic, Albanian, Azerbaijani, Mongolian, Kazakh. RTL (Arabic, Hebrew, Persian, Urdu) supported. Switch languages from the topbar globe.
Open source Dual-licensed MIT OR Apache-2.0. Yours to use, fork, and extend.

Benchmark

The most common job in data engineering: load a 20M-row CSV into DuckDB. One identical 2.49 GB file (20M rows of TPC-H lineitem, 16 typed columns), every tool measured at its best configuration, wall-clock time to land the data as a table.

Tool Time Peak RAM vs Duckle
Duckle 15.69s 2.88 GB 1.0x (fastest)
dlt 40.68s 2.95 GB 2.6x
Talend (bulk) 90s not captured 5.7x
Informatica (bulk) 100s not captured 6.4x
ingestr v1 (Go) 411.01s 1.01 GB 26x
Airbyte ~1150s 3.4 GB 73x

How it was measured

  • Machine: Intel Core i7-13650HX (14C / 20T), 24 GB RAM, NVMe SSD, Windows 11, DuckDB 1.5.4. Duckle, dlt and ingestr ran here.
  • Best config per tool: dlt used the Arrow plus parquet loader path; Talend and Informatica used their bulk output connectors at max config (their default row-by-row sinks were 5-7x slower). Nothing was left on a slow default to pad the gap.
  • Talend and Informatica ran on a separate 8 GB VPS, so per-tool peak RAM was not captured for those two.
  • Airbyte (source-file to destination-duckdb) is scaled from measured 2M and 5M runs at a steady ~18k rows/s, and it also needs an always-on ~8 GB platform just to start.
  • Wall-clock time, peak working-set of the whole process tree.

Why Duckle is this fast: its 15.69s sits right on top of raw DuckDB's own load floor (~16s to fully parse and write all 20M typed rows into an on-disk table). Duckle wraps the engine with pipelines, connectors, and a UI, then gets out of its way. That is the entire design goal. A read-only scan or aggregate over the same CSV is far faster still; this benchmark measures the heavier "materialize it as a table" job that every ETL tool here performs.


Status

Duckle is in public beta. The visual designer, the DuckDB execution engine, the scheduler, the cloud connectors, and the Duckie AI assistant all work today and are covered by 170+ integration tests across Linux, macOS, and Windows. The catalog is still growing and APIs may evolve before 1.0, but the day-to-day surface is stable enough for real work.

Scope, stated plainly: Duckle is a single-machine, embedded studio. If you outgrow one box, point Duckle's output at the system that scales (a warehouse, an object store, a lakehouse). It will not pretend to be a cluster.

The component palette ships 363 nodes so the roadmap is visible in the product itself:

  • 356 available runs on the DuckDB engine today
  • 3 preview is configurable in the designer (drag, wire, set properties); execution is being wired engine-by-engine
  • 15 planned is reserved in the palette but not yet executable - see docs/roadmap.md

Screenshots

Real pipelines, built and run in Duckle - not mockups.


Capabilities

Duckle is not a CSV tool with extras. It reads a broad set of formats and sources, ships a deep transform library, and writes to files, databases, object storage, vector DBs, message buses, and email.

Sources (101 available)

Group Connectors Status
Files CSV, TSV, Parquet, JSON, JSONL / NDJSON, Excel (.xlsx), YAML, TOML, Fixed-width (mainframe / banking positional dumps), XML (slash-separated rowPath), Apache Avro (.avro / .ocf, pure-Rust) Available
Geospatial files GeoJSON, Shapefile, GeoPackage, KML, GPX, GML via the spatial extension Available (lazy-loaded)
Lakehouse table formats Apache Iceberg, Delta Lake, DuckLake Available
Embedded databases SQLite (read tables), DuckDB (read tables or run a query) Available
Network relational DBs PostgreSQL, MySQL, MariaDB, CockroachDB Available (live CI for PG + MySQL)
Network relational DBs SQL Server (TDS), Oracle (Instant Client at runtime), ClickHouse (HTTP API) Available
Network relational DBs IBM DB2, generic JDBC Planned
Object storage Amazon S3, Google Cloud Storage, Azure Blob, HTTP(S), MinIO, Cloudflare R2, Backblaze B2 Available (live CI for MinIO)
Cloud warehouses MotherDuck, Snowflake (SQL API + PAT/JWT), BigQuery, Redshift (postgres ATTACH), Databricks SQL (Statement Execution + chunk follow), Azure Synapse (TDS), Teradata (ODBC, Windows / Linux), DuckDB Quack (May 2026 remote protocol - HTTP on :9494, SECRET-based token auth) Available
Streaming Apache Kafka / Redpanda (pure-Rust rskafka), NATS JetStream, GCP Pub/Sub (REST + auto-ack), RabbitMQ (lapin AMQP), AWS Kinesis (HTTP + SigV4 - no AWS SDK), WebSocket (ws:// / wss://, optional subscribe frame) Available
Streaming Pulsar, Event Hubs, multi-shard Kinesis Planned
APIs and SaaS (REST) Salesforce, HubSpot, Pipedrive, Zendesk, Intercom, Stripe, QuickBooks, Xero, Shopify, Notion, Airtable, Asana, Trello, ClickUp, Monday.com, GitHub, GitLab, Linear, Jira, Slack, Discord, Telegram, Twilio, Mailchimp, SendGrid, Segment - thin pre-configured wrappers over src.rest / src.graphql. src.rest takes a configurable API-key auth header name and offset pagination that stops on a body total_count. Salesforce Bulk (src.salesforce.bulk) - Bulk API 2.0 query source for migration-scale reads: SOQL as an async query job (query / queryAll), paged CSV result sets streamed to disk via Sforce-Locator, typed empty relations on 0 records Available
APIs (protocols) OData v4 (follows @odata.nextLink), SOAP / generic XML APIs (XML response parsing with namespace local-name match) Available
NoSQL and search MongoDB (official driver), Cassandra / ScyllaDB (CQL), Elasticsearch / OpenSearch (from+size + search_after), Redis (SCAN + GET), CouchDB (_all_docs), DynamoDB (HTTP + SigV4 - no AWS SDK; auto-unwraps typed attributes) Available
Vector / AI databases pgvector (postgres ATTACH), Qdrant (/points/scroll), Weaviate (/v1/objects), Milvus (/v1/vector/query) Available
Vector / AI databases Pinecone (no list-all-vectors API), Chroma, LanceDB Preview
File transfer FTP / FTPS (pure-Rust suppaftp) and SFTP (SSH, pure-Rust russh + russh-sftp on the ring backend; password or private-key auth, optional host-fingerprint pin) - one File Transfer component, pick the protocol. Glob filter, base64 content per file Available
Mailbox IMAP (rustls TLS, mail-parser) - basic auth today, OAuth (gmail / o365) on the roadmap Available
Webhook listener Binds 127.0.0.1:port, collects N inbound HTTP requests with a timeout, parses JSON-object / JSON-array bodies into rows Available
Desktop System clipboard (pure-Rust arboard, auto-detects JSON-array shape) Available
Repos Git (commit log or file tree from a local working copy; shells out to system git CLI) Available

For CSV / TSV sources, the Schema panel accepts an optional per-column Format (a strptime token string such as %d/%m/%Y) on Date and Timestamp columns. Several date columns can each parse a different layout in one read - the column is read as text and re-parsed with its own format, working around DuckDB's single global date format. A value that does not match its format becomes null rather than failing the run. Set a Date or Timestamp column's Format to excel to convert Excel day-serials correctly. CSV sources also surface ignoreErrors (skip unparseable rows) and nullPadding (pad short rows with nulls) toggles in the GUI.

For JSON sources, a Format selector picks how the file is read (auto / array / JSON Lines / object), and a skip malformed records toggle drops records that fail to parse instead of failing the run.

Transforms (130 available)

Group Operations
Fields Map (visual mapper: joins a main input to up to 3 lookup inputs with inner / left joins and per-output expressions + filter), Project / Select, Cast, Rename, Add / Drop / Reorder Column, Coalesce, UUID v4
Rows Filter (visual or raw SQL, with reject port), Distinct, Sample, Top N / Limit, Sort, Skip, Top N per Group, Forward Fill, Backward Fill, Constant Fill
Aggregate Group By, Rollup, Cube, Count, Window Aggregate, Cumulative, Approx Quantile (t-digest), Approx Count Distinct (HyperLogLog)
Join Inner, Left, Right, Full Outer, Cross, Lookup, Semi, Anti, Spatial Join
Set operations Union, Union All, Intersect, Except / Minus
Window Row Number, Rank, Dense Rank, Lead, Lag, First Value, Last Value, NTile
Strings Regex Replace, Regex Extract, Regex Match, Split, Concat, Trim, Case Change, Length, Substring, Format, Hash (md5 / sha1 / sha256), IP Parse, URL Parse, Text Similarity (Levenshtein / Jaro-Winkler / Jaccard), Base64, Pad, Text Match
Date / Time Parse, Format, Extract Part, Date Diff / Add, Truncate, Timezone Convert, Time Bin, Current Timestamp, Epoch Convert
Numeric Round, Modulo, Absolute, Logarithm, Power, Square Root, Bucketize, Z-Score, Clamp, Sign
JSON / nested Parse, Stringify, Flatten, JSONPath Extract, Merge Objects, Array Aggregate, jq Filter (a jq program per row over a JSON column, run in-process by the pure-Rust jaq engine - no external jq, no subprocess)
Array Explode / Unnest, Collect List, Element At, Contains, Distinct, Length, Zip Arrays to Table (headings + row-arrays -> one column per heading)
Pivot / shape Pivot, Unpivot, Denormalize, Normalize, Transpose
CDC / SCD Incremental Load (watermark column; saves the high-water mark to workspace state and advances only on a fully successful run), Diff Detect, SCD Type 1, SCD Type 2 (valid_from / valid_to / is_current), Merge / Upsert (universal across embedded, network, warehouse and Mongo sinks, with optional delete propagation driven by a CDC change-type column), DuckLake CDC change-feed reader, Row Hash (md5 / sha1 / sha256 fingerprint), Audit Stamp (_loaded_at / _loaded_date / _source / _batch_id)
AI / Search Vector Similarity Search (cosine / L2 / inner product over FLOAT[N] via vss), Full-Text Search (BM25 via fts), Embeddings (OpenAI-compatible /v1/embeddings), LLM Transform (per-row chat completion with {column} templates), Classify (LLM-backed, normalizes to UNKNOWN), Text Chunker (RAG-ready, pure local), PII Redact (regex - emails / phones / SSNs / cards), Semantic Dedupe (cosine over precomputed embeddings)
Geospatial Spatial Distance, Length, Perimeter, Area (each auto-picks the planar or spheroidal function from the geometry CRS, and rejects geometry with no CRS), Spatial Buffer (ST_Buffer), Spatial Intersects (ST_Intersects), Flip Coordinates (ST_FlipCoordinates - fix lat,lon vs lon,lat order), Define Projection (ST_SetCRS - stamp a CRS without moving coordinates), Reproject Geometry (ST_Transform between CRS, target CRS preserved on the output), Create Geometry (from X/Y, WKT, or WKB)
Debug Log Rows, Assert (hard-fail on SQL predicate violation)

All 6 AI transforms ship today. Three need a model API (LLM, Classify, Embeddings) and ride the apiKey-in-props pattern; three are pure-local (Chunk, PII Redact, Dedupe).

Data quality (27 available)

Validators split their input: passing rows continue on the main port, failures route to a reject port you can sink, count, or inspect.

Component Behavior
Not-Null Check Pass rows with no nulls in the chosen columns
Range Check Pass rows inside a numeric range (inclusive or exclusive)
Regex Match Pass rows whose column fully matches a pattern
Uniqueness Check Pass the first row per key; route duplicates to reject
Schema Validate Reject rows where any expected column is null
Column Profile Per-column stats (count, null %, distinct, min / max, quartiles) via SUMMARIZE
Describe Column names + types of the input
Histogram Value frequencies for one column, most-frequent first
Standardize Trim + case-normalize + collapse inner whitespace, in place
Fuzzy Deduplicate Keep the first row per near-duplicate cluster
Record Match Self-join: emit pairs of rows above a similarity threshold
Address Cleanse Address parsing / normalization (planned - needs external lib)

Custom code (6 available)

Capability What it does
Inline SQL Write a SELECT; the upstream node is exposed as input, result runs as a real materialized stage. A raw SQL mode runs verbatim SQL (a leading WITH / multiple CTEs / UNIONs) with no input-CTE wrapper
SQL Template Parameterized SQL with ${context.var} substitution
SQL Routines Reusable, named SQL saved in the workspace
dbt Run a dbt project (or one inline model) as a node, against the pipeline's DuckDB. Wire several upstream sources in and the project reads them all via dbt sources, so one project models across Postgres, MySQL, files, and lakes at once. Powered by the dbt Fusion engine, fetched free at first launch (Apache dbt-core fallback); no Python setup.
Shell Run any shell command; emits {stdout, stderr, exit_code, duration_ms}. Platform-aware default shell. Optional timeoutMs kills the child.
WebAssembly UDF Per-row WASM transform via pure-Rust wasmi. Sandboxed (no fs / net / env). Works with any WASM toolchain (Rust, AssemblyScript, C, TinyGo).
JavaScript UDF Per-row JS transform via pure-Rust boa interpreter. Sandboxed. Define a transform(row) function.
Python / Rust UDFs Embedded-language stages

Sinks (63 available)

Group Connectors Status
Files CSV, TSV, Parquet (ZSTD), JSON, JSONL / NDJSON, Excel (.xlsx), YAML, TOML, XML (configurable wrappers), Avro (schema inferred from first row). Parquet + CSV support Hive-partitioned writes Available
Geospatial files GeoJSON, GeoPackage, Shapefile, KML, GPX via GDAL Available (lazy-loaded)
Lakehouse Apache Iceberg (full table layout), DuckLake - modes: overwrite, append, truncate, upsert (set-based delete-by-key + re-insert), merge (partial-column MERGE INTO that preserves columns the source omits) with optional CDC delete propagation Available
Embedded databases SQLite, DuckDB - modes: overwrite, append, upsert (set-based delete-by-key + re-insert, no PK required), merge (partial-column MERGE INTO that preserves columns the source omits) with optional CDC delete propagation Available
Network relational DBs PostgreSQL, MySQL, MariaDB, CockroachDB - modes: overwrite, append, truncate, upsert (ON CONFLICT / ON DUPLICATE KEY) with optional CDC delete propagation Available (live CI for PG + MySQL)
Network relational DBs SQL Server / Azure Synapse (TDS, multi-row VALUES batched; auto-creates the table if absent; upsert via MERGE), Oracle (Instant Client; INSERT ALL, batched per statement; auto-creates the table if absent; upsert via MERGE), ClickHouse (HTTP JSONEachRow; upsert by pointing at a ReplacingMergeTree target table) - every MERGE sink supports CDC delete propagation (a delete-flag column removes matched rows) Available (SQL Server + Oracle + MySQL upsert and delete propagation verified live in Docker)
Network relational DBs IBM DB2, generic JDBC Planned
Object storage S3, GCS, Azure Blob via DuckDB httpfs (MinIO / R2 / B2 via endpoint) Available
Cloud warehouses MotherDuck, Snowflake (PAT or JWT RS256; upsert + delete propagation via MERGE), BigQuery, Redshift, Databricks SQL (upsert + delete propagation via MERGE), Azure Synapse, Teradata (ODBC), DuckDB Quack (concurrent writers to remote DuckDB via the May 2026 protocol) Available (Snowflake MERGE verified live against the SQL-API emulator)
HTTP APIs REST (POST/PUT/PATCH batched JSON-array; configurable API-key auth header name), Webhook (one POST per row), GraphQL mutations Available
SaaS / CRM Salesforce (snk.salesforce) - sObject Collections API: insert / update / upsert (by external Id) / delete, ≤200 records/request, Bearer token or OAuth 2.0 client-credentials (fresh token minted per run, same auth as src.salesforce). Salesforce Bulk (snk.salesforce.bulk) - Bulk API 2.0 for migration-scale loads: insert / update / upsert / delete / hardDelete, DuckDB streams to CSV and each ≤90 MB part runs as an async job Available
Email (SMTP) Per-row SMTP send via pure-Rust lettre + rustls. Plain text v1; HTML + attachments follow. Available
NoSQL MongoDB (insert_many batched; upsert via replace_one on a key, plus delete propagation via delete_one), Cassandra / ScyllaDB (CQL), Elasticsearch / OpenSearch (_bulk NDJSON), Redis (pipelined SET) Available
NoSQL DynamoDB Planned
Streaming Kafka / Redpanda (rskafka), NATS JetStream, GCP Pub/Sub (REST + OAuth2), RabbitMQ (lapin), WebSocket (ws:// / wss://) Available
Streaming Pulsar, Kinesis Planned
Vector / AI databases pgvector, Pinecone (/vectors/upsert), Qdrant (/points PUT), Weaviate (/v1/batch/objects), Milvus (/v1/vector/insert) Available
Vector / AI databases Chroma, LanceDB Preview (need vendor SDK)

Database sinks support an optional dead-letter (validate-before-insert) step: rows that do not match the declared column types are split off to a dead-letter file (parquet / csv / json) and only the clean rows are inserted.

Control flow (18 available)

Component What it does
Replicate / Tee Send the same data to multiple downstream outputs
Merge Streams Concatenate multiple input streams (UNION ALL)
Switch / Conditional Split Route rows to case_1..N outputs by boolean (first match wins); default for unmatched
Wait / Delay Sleep N ms / s / min / h before passing rows through
Throttle Inter-stage delay derived from a rows-per-second target
Checkpoint Pass rows through and also write a parquet snapshot to a path
Dead Letter Queue Terminal sink for rejected rows (JSON / CSV / Parquet)
Run Pipeline Inline-execute another pipeline file (ctl.runpipeline)
Run Job Call a child pipeline (picked from the workspace) passing parent context variables; chain several to build a Master Job (ctl.runjob)
Parallelize Run the downstream branches wired to its outputs concurrently; branches are unlimited (ctl.parallelize)
Iterate Run a sub-pipeline N times with ${ITER_INDEX} substitution
For Each Run a sub-pipeline once per input row with ${ITER_ITEM_<FIELD>} substitution
Try / Catch Install a fallback sub-pipeline if the wrapped stage fails
Retry Per-stage retry policy (configure on Advanced tab)
Log Message Emit an info log line ({rows} = upstream count), pass rows through (ctl.log)
Warn Emit a warning log line, pass rows through (ctl.warn)
Die / Fail Stop the run with a message: always, only when the input has rows, or only when empty (ctl.die)
Schedule Cron / interval / file-watch triggers via the orchestration crate

Advanced settings (per-node)

Every node has an Advanced tab with fields the engine honours at run time:

Field What it does
Retry attempts Total tries on failure (1 = no retry). Sleeps backoff * attempt ms between attempts.
Retry backoff (ms) Inter-attempt sleep, linearly scaled by attempt index.
Memory limit (MB) PRAGMA memory_limit applied to this stage only.
Log row count Print the post-stage rowcount to the run output.

Orchestration and workspace

Capability What it does
Run feedback Streaming run events light nodes up stage by stage, with per-node row counts, real mid-query cancel, and run history.
Error traceback A failed stage reports the exact compiled SQL plus the underlying DuckDB message, in both the Run view and the NDJSON run log, so any component's failure is debuggable.
Column lineage A top-bar Lineage button shows, per node, each output column traced back to the source column(s) it derives from.
Dives + dashboards Local-first, live-querying, shareable data views, stitched into multi-chart dashboards. Generate a chart from a plain-language question, export a dive to a self-contained HTML file, open standalone /dive/<id> and /dash/<id> share pages, and find everything in the top-bar Dives gallery.
Run logs Every run writes component-level NDJSON to <workspace>/logs/<pipeline name>/runtime.log (start/finish per stage, row counts, durations, ctl.log / ctl.warn / ctl.die messages). Tail it straight into Splunk or Dynatrace.
Schedules Cron, fixed-interval, and file-watch triggers, driven by an in-process scheduler.
Context variables Per-environment variables; bind any field to one via a Manual / Context dropdown, or reference ${var} inline. Resolved at run time.
Workspace-relative paths Built-in ${workspace} (alias ${projectroot}) resolves to the active workspace root, so source / sink paths can be written relative to it and a workspace folder stays portable when copied or moved. No context needed; works in the canvas, schema autodetect, and headless runs.
Run-time path placeholders Built-in ${date}, ${time}, ${datetime}, ${timestamp}, and ${now} (UTC) stamp the current run time into any path. They resolve fresh on every run (canvas, schedule, headless runner, built bundle), and a sink's parent folder is created automatically, so a path like ${workspace}/exports/${date}/orders.parquet lands in a new dated folder each day. No context needed.
Cloud credentials Saved S3 / GCS / Azure connections become DuckDB SECRETs; cloud reads / writes go through httpfs. S3-compatible endpoints (MinIO / R2 / B2) supported via ENDPOINT + URL_STYLE.
Workspace Pipelines, connections, contexts, documents, and routines persist as plain JSON and Markdown files in a folder you choose.

Clean data before it reaches your AI

Models inherit the quality of their inputs. RAG indexes, embedding stores, and training sets quietly accumulate duplicates, nulls, malformed rows, mixed encodings, and inconsistent schemas. Duckle is built to scrub that data before it lands in a vector store:

  • Deduplicate with exact Distinct, Uniqueness, and Fuzzy Deduplicate (Jaro-Winkler / Levenshtein); use Record Match to find near-duplicate pairs with a similarity score
  • Semantic dedupe with xf.ai.dedupe over a precomputed embedding column
  • Profile + describe every column up front (Column Profile, Describe, Histogram) so issues surface before they reach a model
  • Validate and filter malformed, empty, or out-of-range records and route failures to a reject port
  • Normalize types, encodings, casing, and null handling across messy sources (Standardize, Cast, regex / string transforms)
  • Redact PII (emails, phones, SSNs, credit cards) via xf.ai.pii before embedding
  • Chunk + embed long text via xf.ai.chunk -> xf.ai.embed for RAG indexing
  • Classify rows with an LLM (xf.ai.classify constrains the model to one of N user-supplied categories)
  • Retrieve with both halves of hybrid search, locally, no model API required: Vector Similarity Search (cosine / L2 / inner product) and Full-Text Search (BM25)
  • Land it in your store - pgvector ships, and Pinecone, Qdrant, Weaviate, Milvus all have working sinks that POST batches through each vendor's HTTP API

Engines

Duckle ships a thin shell and installs its engines on first launch.

Engine Role Status
DuckDB Default execution engine: analytics, file formats, cloud reads, SQL pushdown. Tracking v1.5.3 (latest stable). A lock-free single-SELECT read (Engine::query) powers dives. Working
Duckie AI Assistant Local chat assistant via llama.cpp + Qwen 2.5 Coder 1.5B GGUF. Downloads ~1.1 GB; runs entirely offline once installed. Managed as a llama-server subprocess exposing an OpenAI-compatible API on 127.0.0.1. Installable
SlothDB Alternate embedded analytical engine (SouravRoy-ETL/slothdb), installed the same way and selectable per pipeline. Installable
Native In-process Rust streaming / incremental engine. Planned

First-launch extension pre-fetch

When the installer downloads the DuckDB CLI it also pre-fetches the extensions Duckle uses, with per-extension progress, so the first time you touch a Postgres source or an Iceberg table there is no surprise network hop mid-pipeline:

httpfs (S3 / GCS / HTTP), `azu