Duckdb logo

Duckdb

Community
ericrisco
duckdb

Use when analytical SQL must run in-process with no server: Parquet/CSV/JSON/Arrow queried in place, OLAP embedded in an app or notebook, a slow pandas groupby on multi-GB data, or S3/lakehouse data read without downloading. NOT a multi-user analytics server (that is clickhouse-analytics), NOT an app's transactional CRUD store (that is postgresdb).

Overview

Publisherericrisco
Repositoryrsc-harness
Skill nameduckdb
Stars
239
Forks
24
Bundled files
4
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 4 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by ericrisco on GitHub. Read the source before you install it.

Installation

Install the Duckdb AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/ericrisco/rsc-harness/tree/main/skills/duckdb
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/ericrisco/rsc-harness.git /tmp/rsc-harness
mkdir -p .claude/skills
cp -r /tmp/rsc-harness/skills/duckdb .claude/skills/duckdb
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Duckdb in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Duckdb on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Duckdb is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

DuckDB — embedded columnar OLAP, no server

DuckDB is an in-process analytical (OLAP) database: it links into your process like SQLite, but stores data column-by-column and vectorizes execution for aggregates, joins, and window functions. There is no server, no port, no daemon — you pip install duckdb (or drop one CLI binary) and query. Its killer move is reading Parquet/CSV/JSON/Arrow in place, without a load step, so a folder of files becomes a table.

The fact that drives every routing decision below: DuckDB is one writer, many readers, single process. It is brilliant for analysis on one machine and wrong for multi-user serving or transactional app writes.

Latest stable is v1.5.3 (released 2026-05-20). Pin the LTS line (v1.4.x; v1.4.4 LTS shipped 2026-01-26) for anything long-lived — LTS gets ~1 year of patches and a stable storage format. Use 1.5.x for greenfield exploration.

Is DuckDB the tool? (decide first)

Your workloadReach for
Analytics over local/remote files, one process, one writerduckdb (this skill)
Many concurrent users, production query API, dashboards-as-a-service, petabyte scaleclickhouse-analytics
App transactional CRUD: users, orders, many small writes, FKs, connection poolpostgresdb
Embedded single-file transactional store / edge / syncsqlite-turso
Pure SQL syntax question, engine-agnostic (window fns, CTEs)sql
Similarity / embedding search as the core workflowvector-db

The two you will confuse most: DuckDB vs ClickHouse is embedded-single-node vs server-distributed — under ~10GB on one box DuckDB usually wins; pick ClickHouse when many people query concurrently. DuckDB vs SQLite is same niche, opposite workload — both embedded single-file, but SQLite is row-store OLTP and DuckDB is column-store OLAP. Don't run your app's writes through DuckDB.

Install & version

bash
# Python (replacement-scan + relational API)
pip install 'duckdb==1.4.4'        # LTS, for long-lived projects — stable storage format + patches
pip install duckdb                  # current stable, for greenfield exploration

# CLI (single static binary)
curl https://install.duckdb.org | sh   # or: brew install duckdb
duckdb -version

Why pin LTS for production: the on-disk .duckdb format and extension ABI are stable within an LTS line, so a routine upgrade won't strand a persisted database or break an installed extension mid-project.

Query files directly — the killer feature

Do not load a file into pandas just to query it. Point DuckDB at the path and let it scan only the columns and row groups it needs (Parquet metadata pushdown). A bare string path is a replacement scan, so FROM 'data/*.parquet' works without naming a reader.

python
import duckdb

# BAD: read the whole file into RAM, then aggregate in pandas
import pandas as pd
df = pd.read_parquet("sales/")          # pulls every column of every file into memory
out = df.groupby("region")["amount"].sum()

# GOOD: scan in place, only the two needed columns ever touch memory
out = duckdb.sql("""
    FROM 'sales/*.parquet'
    SELECT region, sum(amount) AS revenue
    GROUP BY ALL
    ORDER BY revenue DESC
""").df()

Readers and globs you will actually use:

sql
SELECT * FROM read_parquet('s3://bkt/y=*/m=*/*.parquet', filename = true);  -- glob + source col
SELECT * FROM read_csv_auto('events.csv');         -- sniff delimiter/types/header
SELECT * FROM read_csv('raw.csv', header = false, types = {'id': 'BIGINT'});  -- when sniffing is wrong
SELECT * FROM read_json_auto('logs/*.ndjson');     -- newline-delimited or array JSON

filename = true adds a filename column — essential when a glob mixes partitions and you need to know which file a row came from.

In-memory vs persistent

python
con = duckdb.connect()                    # in-memory: default, gone when the process exits
con = duckdb.connect("analytics.duckdb")  # single file, created if absent; extension is not significant

Persist when: the dataset is reused across runs, an intermediate result is larger than RAM (DuckDB spills to the file), or you are curating a dataset to share. Otherwise stay in-memory — it is the fast path and needs no cleanup. The whole database is one file; copy it to move the database.

Python / dataframe interop

In-scope pandas/Polars/Arrow frames are queryable by variable name — that is a replacement scan, no registration needed. The relational API is lazy; nothing executes until you materialize.

python
import duckdb, pandas as pd
orders = pd.read_parquet("orders.parquet")        # ordinary frame in local scope

rel = duckdb.sql("FROM orders SELECT region, sum(amount) AS rev GROUP BY ALL")  # lazy, by name
rel.df()        # -> pandas        rel.pl()       # -> Polars
rel.arrow()     # -> Arrow table   rel.fetchall() # -> list[tuple]

Use a single connection per thread, never share one cursor across threads. Full client surface — parameterized queries, relational operators, NumPy/torch round-trips, threading rules — is in references/python-and-interop.md.

Friendly SQL — use the dialect

DuckDB's dialect removes the boilerplate that makes analytics SQL tedious. Prefer it in DuckDB-only code.

sql
FROM events SELECT count(*);                 -- FROM-first: pipe-friendly, valid on its own
SELECT * EXCLUDE (raw_payload) FROM events;  -- everything but the noisy column
SELECT * REPLACE (lower(email) AS email) FROM users;  -- transform one column, keep the rest
SELECT region, sum(amount) FROM sales GROUP BY ALL;   -- no restating non-aggregates
SELECT * FROM sales ORDER BY ALL;            -- deterministic order without listing columns
SELECT COLUMNS('amount_.*') FROM sales;      -- regex over column names
SELECT 1, 2, 3,                              -- trailing commas are legal

GROUP BY ALL / ORDER BY ALL are the biggest wins: add a column to the SELECT and the grouping follows automatically, so the two clauses can't drift out of sync.

Remote + lakehouse data

Read from S3/GCS/HTTP without downloading first: load httpfs and store credentials in a secret.

sql
INSTALL httpfs; LOAD httpfs;
CREATE SECRET s3 (TYPE s3, PROVIDER credential_chain);  -- picks up env/role creds
SELECT region, sum(amount) FROM read_parquet('s3://bkt/sales/*.parquet') GROUP BY ALL;

Iceberg, Delta, and DuckLake (DuckDB's own SQL-catalog lakehouse format) are read via extensions. Secret config, hive-partition globs, and the lakehouse one-liners live in references/remote-and-lakehouse.md.

Export & handoff

sql
COPY (SELECT region, sum(amount) AS rev FROM 'sales/*.parquet' GROUP BY ALL)
  TO 'summary.parquet' (FORMAT parquet);

COPY sales TO 'out/' (FORMAT parquet, PARTITION_BY (year, month));  -- hive-partitioned dataset

Parquet is the default handoff: it keeps types and reads straight back into DuckDB, Spark, pandas, or ClickHouse. Use PARTITION_BY so downstream readers can prune partitions.

Outgrowing DuckDB

DuckDB scales up (more RAM/threads, out-of-core spill), not out. Tune within one box:

sql
PRAGMA threads = 8;            -- match cores
PRAGMA memory_limit = '12GB';  -- cap RAM; the rest spills to the temp dir / database file

Hand off when you hit the single-process wall: concurrent writers or a multi-user query API or an always-on service → clickhouse-analytics. Want managed/shared/hybrid DuckDB without running infra → MotherDuck (managed DuckDB-as-a-service; ATTACH 'md:') — the scale-out escape hatch, not the default.

Anti-patterns

Anti-patternDo instead
Loading a file into pandas, then querying the framePages the whole file through RAM. FROM 'file.parquet' SELECT ... scans only needed columns.
Making DuckDB the app's databaseOne writer, single process — concurrent CRUD writers corrupt the workflow. App OLTP is postgresdb.
Serving a dashboard API for 200 users from DuckDBIt's embedded, not a server; concurrent users serialize on the writer. That's clickhouse-analytics.
Running the latest version in prodPin the LTS (1.4.x; v1.4.4 shipped 2026-01-26) so a future upgrade doesn't break the on-disk format / extension ABI.
Downloading the S3 files, then reading themINSTALL httpfs + CREATE SECRET reads s3://... in place with row-group pushdown. No download.
SELECT * over a 200-column ParquetColumnar engine — list the columns you need so it skips the rest. SELECT * reads everything.
GROUP BY a, b, c — restating every columnDrift bait. GROUP BY ALL tracks the SELECT automatically.
Treating "embedded" as "no need to think about RAM"Set memory_limit / threads; without a cap a runaway aggregate can thrash before it spills.
Using DuckDB as the embeddings/vector storeVSS exists but vector search is vector-db's workflow, not DuckDB's home turf.

Verify

Run scripts/verify.sh from anywhere. It runs a tiny self-contained smoke test — prefers the duckdb CLI, falls back to python3 -c "import duckdb" — that runs an aggregate and a read_csv_auto over a generated file and asserts a known scalar, proving the documented commands execute on your installed version. If neither the CLI nor the Python module is present it prints SKIP and exits 0. No network.

Project grounding (02-DOCS + CLAUDE.md)

In a project with a 02-DOCS/ layer (the harness wiki), record this project's DuckDB decisions — version pin, file layout, persistent vs in-memory, remote/secret setup — in 02-DOCS/wiki/stack/duckdb.md and index it in 02-DOCS/wiki/index.md (the Knowledge map; root CLAUDE.md keeps only a short pointer to it). Read it first on every use and keep choices consistent. No 02-DOCS/? Skip silently. Conventions are recorded, never gated.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Duckdb AI skill do?

Use when analytical SQL must run in-process with no server: Parquet/CSV/JSON/Arrow queried in place, OLAP embedded in an app or notebook, a slow pandas groupby on multi-GB data, or S3/lakehouse data read without downloading. NOT a multi-user analytics server (that is clickhouse-analytics), NOT an app's transactional CRUD store (that is postgresdb).

Why use Duckdb on TypingMind?

Because you install it once and use it with any model. Duckdb is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Duckdb in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/ericrisco/rsc-harness/tree/main/skills/duckdb. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Duckdb?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Duckdb?

As many as you like. As long as a model supports skills, you can use Duckdb with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Duckdb AI skill free?

Yes. It is published on GitHub by ericrisco under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇