lacon
token-lean agent↔data query interface — query files, don't read them
Before/After • Install • What You Get • Primitives • HITL • Python API • CLI Reference
When an agent needs to understand a CSV or Parquet file, the naive approach is to dump the whole file into context. A 5 MB CSV is ~350k tokens. lacon is the opposite: agents query the file and get back only the answer — shaped, capped, and token-costed. DuckDB does the querying under the hood; lacon's value is the interface and the result shaping.
Sibling to datoon (cheaper representation once data is in the prompt) — lacon's lever is to not put the data there at all.
Before / After
Same answer. Tiny fraction of the token cost.
┌─────────────────────────────────────────────────────┐
│ DESCRIBE A 5MB FILE ████░░░░░░░░ 89 tokens │
│ SAME FILE DUMPED RAW ████████████ 350k tokens │
│ WRITES ALLOWED ░░░░░░░░░░░░ 0 (read-only)│
└─────────────────────────────────────────────────────┘
Install
# core (CLI + DuckDB + SQL validation)
pip install lacon
uv add lacon
# with token estimates (~tokens in every response)
pip install "lacon[tokens]"
# with MCP server (coming in v0.2)
pip install "lacon[mcp]"
Requires Python 3.12+.
Claude Code skill:
claude skill install https://github.com/andrii-su/lacon/releases/latest/download/lacon.skill
Once installed, Claude automatically uses lacon when you mention a .csv, .parquet, or .json file — no manual invocation needed.
What You Get
| What | |
|---|---|
lacon CLI |
9 curated primitives for querying data files from terminal or scripts |
| Python API | describe(), aggregate(), filter(), query() — same primitives programmatically |
| Claude Code Skill | /lacon in-session trigger, progressive disclosure workflow, HITL for query |
| ~tokens | Every response includes its estimated token cost (requires lacon[tokens]) |
| MCP Server | Coming in v0.2 — expose primitives as MCP tools for Claude Desktop, Cursor, Windsurf |
Primitives
9 curated operations. Each builds safe SQL internally, enforces read-only, and applies auto-LIMIT.
| Primitive | Use when the agent asks... | Returns |
|---|---|---|
describe |
"what's in this file?", "what columns?", "how many rows?" | schema + metadata, no data rows |
sample |
"show me some rows", "what does the data look like?" | first/random N rows |
count |
"how many rows?", "how many where X?" | integer |
profile |
"tell me about column X", "any nulls?", "distribution?" | per-column stats |
distinct |
"what are the unique values of X?" | values list + truncation info |
aggregate |
"total/avg X by Y", "group by Z" | grouped rows |
filter |
"show rows where X", "find records matching Y" | matching rows, projected columns |
find-duplicates |
"are there duplicates?", "find duplicate X" | duplicate groups + counts |
query |
anything the primitives above can't express | rows — HITL required |
Supported formats: CSV, Parquet, JSON, JSONL — auto-detected from extension.
Quick Start
# always start here
lacon describe data.csv --pretty
# understand a column
lacon profile data.csv --column revenue --pretty
# answer a count question
lacon count data.csv --where "country = 'UA'"
# group by
lacon aggregate data.csv --group-by country --metrics revenue:sum --pretty
# filter with column projection
lacon filter data.csv --where "year = 2024" --columns name revenue --pretty
# unique values
lacon distinct data.csv --column country --pretty
# find duplicates
lacon find-duplicates data.csv --columns email --pretty
# escape hatch — use HITL (see below)
lacon query data.csv "SELECT country, AVG(revenue) FROM {file} GROUP BY country" --show-sql --pretty
lacon query data.csv "SELECT country, AVG(revenue) FROM {file} GROUP BY country" --pretty
Use {file} as the placeholder for the data source in query. Auto-LIMIT is always enforced (default 50, max 1000).
HITL for query
The query primitive runs arbitrary SQL — an escape hatch when curated primitives aren't enough. Because the SQL is not predictable from fixed parameters, always preview before executing.
# Step 1 — preview resolved SQL (no execution)
lacon query sales.csv \
"SELECT country, name, revenue FROM {file} t \
WHERE revenue = (SELECT MAX(revenue) FROM {file} t2 WHERE t2.country = t.country) \
ORDER BY revenue DESC" \
--show-sql --pretty
{
"op": "query",
"sql": "SELECT country, name, revenue FROM read_csv('sales.csv') t WHERE ...",
"will_execute": false
}
# Step 2 — confirm the SQL, then execute
lacon query sales.csv "..." --pretty
Result always includes the sql field so you can verify what ran:
{
"op": "query",
"schema": ["country", "name", "revenue"],
"rows": [["UA", "Bob", 3400.0], ...],
"shown": 3,
"~tokens": 74,
"sql": "SELECT country, name, revenue FROM read_csv('sales.csv') t WHERE ..."
}
Why: text-to-SQL agents achieve ~80% accuracy on real schemas. A subtly wrong WHERE clause executes silently and looks correct. Showing the SQL before execution catches errors before they produce wrong answers. Curated primitives need no confirmation — their SQL is fully determined by parameters.
Output Envelope
Every response is a shaped JSON object:
{
"op": "aggregate",
"schema": ["country", "sum_revenue"],
"rows": [["UA", 4500.25], ["DE", 2200.0], ["US", 2001.25]],
"shown": 3,
"~tokens": 62
}
| Field | Description |
|---|---|
op |
Which primitive ran |
schema |
Column names — agent never guesses shape |
rows |
Data as arrays — compact, no key repetition |
shown |
How many rows returned (honest truncation) |
~tokens |
Estimated token cost of this response (lacon[tokens]) |
sql |
Resolved SQL that ran (query only) |
describe returns metadata only (no rows). count returns a single integer.
Python API
from lacon import DuckDBEngine, describe, sample, count, aggregate, filter, query
with DuckDBEngine() as engine:
# schema + metadata
r = describe("data.csv", engine)
print(r["row_count"], r["schema"])
# column stats
r = profile("data.csv", column="revenue", engine=engine)
print(r["min"], r["max"], r["mean"])
# grouped aggregation
r = aggregate(
"data.csv",
group_by=["country"],
metrics=[{"col": "revenue", "fn": "sum"}],
engine=engine,
)
# filtered rows with projection
r = filter("data.csv", where="revenue > 2000", columns=["name", "revenue"], engine=engine)
# escape hatch — read-only, auto-LIMIT
r = query("data.csv", "SELECT country, AVG(revenue) FROM {file} GROUP BY country", engine=engine)
Safety
- Read-only — no writes, no DDL, no COPY, no INSTALL
- SQL validation — sqlglot parses every
querycall; rejects INSERT, DROP, CREATE, multi-statement - Auto-LIMIT — injected if missing, capped at 1000
- Path escaping — single quotes in paths escaped before DuckDB
CLI Reference
Global options
| Flag | Description |
|---|---|
--pretty |
Pretty-print JSON output |
--version |
Print version and exit |
describe <path>
Schema, row count, file size. Always start here.
sample <path>
| Flag | Default | Description |
|---|---|---|
--n N |
5 | Number of rows |
--random |
false | Random sample instead of first N |
count <path>
| Flag | Description |
|---|---|
--where EXPR |
Optional WHERE filter |
profile <path>
| Flag | Description |
|---|---|
--column COL |
Column to profile (required) |
--top-k N |
Top-k values for categorical columns (default 10) |
aggregate <path>
| Flag | Description |
|---|---|
--group-by COL [COL ...] |
Group-by columns |
--metrics COL:FN [...] |
Metrics — fn ∈ sum avg min max count |
--where EXPR |
Optional WHERE filter |
--limit N |
Row cap (default 50) |
filter <path>
| Flag | Description |
|---|---|
--where EXPR |
WHERE filter (required) |
--columns COL [...] |
Column projection |
--limit N |
Row cap (default 50) |
distinct <path>
| Flag | Description |
|---|---|
--column COL |
Column to enumerate (required) |
--limit N |
Value cap (default 50) |
find-duplicates <path>
| Flag | Description |
|---|---|
--columns COL [...] |
Key columns (required) |
--limit N |
Group cap (default 50) |
query <path> SQL
| Flag | Description |
|---|---|
--limit N |
Row cap (default 50, max 1000) |
--show-sql |
Preview resolved SQL without executing (HITL) |
Use {file} as the data-source placeholder in SQL.
Development
# setup
uv sync --extra dev
uvx pre-commit install
# tests (46 — primitives, safety, shaping)
uv run --with ".[dev,tokens]" pytest
# build lacon.skill for distribution
python scripts/build_skill.py
Links
- CONTRIBUTING.md — contributor workflow
- CLAUDE.md — agent/maintainer context
- CHANGELOG.md — release history
- SECURITY.md — vulnerability reporting
- Live docs —
docs/ - Issues — bugs, features, questions
License
MIT
Metadata
Release files for lacon 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| lacon-1.1.1.tar.gz | 103.1 kB | Without provenance | Details for lacon-1.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| lacon-1.1.1-py3-none-any.whl | Python 3 | none | any | Details for lacon-1.1.1-py3-none-any.whl |
Total release size: 119.0 kB
Release files / lacon-1.1.1.tar.gz
| Download URL | lacon-1.1.1.tar.gz |
|---|---|
| Size | 103.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e3cbad79d1a41fcf99a7070134a517056f1c03cc652031dcab93b43102e164cc
|
|
BLAKE2b-256 checksum How to use checksums |
4d1efb5087e5408210ca5087cf90de8134731799127e732ea06133101586fac6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / lacon-1.1.1-py3-none-any.whl
| Download URL | lacon-1.1.1-py3-none-any.whl |
|---|---|
| Size | 15.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
56dc39ab05ab6f2beeb9cee0b14dcffcc9a76a81e4494e835920794e2bf09d00
|
|
BLAKE2b-256 checksum How to use checksums |
005320770e5ccc4747f528efbf6894a8925c70b354e3844e5edb16f383d65536
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Security for lacon 1.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.