Quick links
Get started
- What is Duckle?
- What's new in v0.5.6
- Quickstart (60 s)
- Download / Install
- Build from source
- Run your first pipeline
Use the product
- Meet Duckie (AI)
- How to use Duckle
- Recipes / examples
- In-app Git (GitHub/GitLab)
- Workspace + Git flow
- Schedules
- Server deployment
- MCP server: connect Claude, Cursor or any agent
- Connection management
- Context variables
Reference
- Capabilities matrix
- Sources
- Transforms
- Sinks
- Data quality
- Custom code
- Control flow
- Advanced settings
- Engines
- Configuration
Resources
- Architecture
- Clean data for AI
- Performance tips
- FAQ
- Troubleshooting
- CI / CD
- Status
- Roadmap
- Contributing
- Sponsor Duckle
- License
- Releases
- Roadmap doc
- Contributing doc
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:
- 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.
- 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.
- 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.websocketconnects to aws://orwss://endpoint, optionally sends a subscribe frame, and collects up tomaxMessagesframes into rows;snk.websocketpushes 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.xmlstreams local,sftp://, andhttps://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.dedupeover 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.piibefore embedding - Chunk + embed long text via
xf.ai.chunk->xf.ai.embedfor RAG indexing - Classify rows with an LLM (
xf.ai.classifyconstrains 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
No comments yet
Be the first to share your take.