fastdata has two front ends over the same agent: a web chat and a terminal command. Both find
fastdata.yaml by searching the current folder and its parents; pass -c path/to/fastdata.yaml
to use another one.
Web chat
fastdata # same as `fastdata serve`
fastdata serve --port 9000 --no-open
| Option | Default | Meaning |
|---|---|---|
-p, --port <port> | 8000 | Port. If it is taken, the next free one (up to +10) is used. |
--host <host> | 127.0.0.1 | Interface to bind. Anything other than localhost makes your data reachable from the network; see Security. |
--no-open | Do not open the browser. |
What you see for each question:
- Steps: a collapsible list of what the agent read and did ("Reading metrics/revenue.md").
- Query cards: the purpose, the SQL (copy button), the row count and timing, and a preview of the result. Show all loads the full result; Download CSV saves it.
- The answer, in Markdown: a direct answer with the key number in bold, a table, a few insights, Assumptions, the Query and Based on (the knowledge files used).
- Turns, time and cost of the answer.
Ask follow-ups in the same chat ("And for Germany only?"); the agent keeps the context. New chat starts over. Stop cancels a running answer.
Tip: links like http://localhost:8000/?q=How+many+orders+today open the chat and ask the
question straight away, which is handy for bookmarks and team wikis.
Terminal: fastdata ask
fastdata ask "Which product categories bring in the most revenue?"
The answer (Markdown) goes to stdout; progress (steps, queries, cost) goes to stderr. So you can save or pipe just the answer:
fastdata ask "Monthly revenue for the last 12 months" > revenue.md
fastdata ask "Top 10 customers" | glow - # render in the terminal with any Markdown viewer
| Option | Meaning |
|---|---|
--loop, -l | Keep asking follow-up questions in the same conversation (see below). |
--continue | Continue the previous conversation (stored in .fastdata/last-session next to fastdata.yaml). |
--session <id> | Continue a specific conversation. |
--new | Start a new conversation without being asked about the previous one. |
--results <file> | Give the agent the results of a query you ran yourself (- reads stdin). See manual mode below. |
--json | Print every event as one JSON object per line instead of Markdown, for scripts. |
The exit code is 1 when the agent could not answer (for example it hit max_turns).
Continuing a conversation. In a terminal, when your previous conversation is less than a day
old, fastdata ask asks whether to continue it. A follow-up in the same conversation reuses
what the agent already read from the knowledge base instead of reading it again. Press Enter to
continue, n for a new conversation, or pass --new / --continue to skip the question.
Scripts (piped input, --json) always start a new conversation unless you pass --continue.
Several questions in a row. fastdata ask --loop keeps one conversation open and prompts for
the next question after each answer:
$ fastdata ask --loop "Who are our top 5 customers?"
… answer …
› And what share of revenue do they bring in?
… answer, without re-reading the knowledge base …
In the loop, /results <file> [note] sends results of a query you ran yourself (manual mode),
/new starts a new conversation, /exit or Ctrl+D quits, and Ctrl+C stops the current answer.
Running your own SQL: fastdata run
fastdata run runs a query you wrote through the connector, straight away. If the database
rejects it (a typo, a wrong table or column name, a function your dialect does not have), Claude
fixes the query, looking up the right names in the knowledge base, and fastdata runs the fixed
version. This repeats until the query works or --max-fixes is reached.
fastdata run "SELECT country, COUNT(*) FROM customers GROUP BY country"
fastdata run --file report.sql --format csv > report.csv
cat query.sql | fastdata run -
$ fastdata run "SELECT c.country, SUM(oi.quantity * oi.price) FROM customer c JOIN …"
✗ SQLite error: no such table: customer
· Asking Claude to fix the query
✎ The table is `customers`, and the price column is `order_items.unit_price`.
SELECT c.country, SUM(oi.quantity * oi.unit_price) FROM customers c JOIN …
✓ 8 row(s) in 26 ms · $0.0686
Ran the query after 1 fix(es):
…
| country | revenue |
| --- | --- |
…
The rows go to stdout and progress to stderr, so redirecting saves just the result. When a fix was needed, the query that actually ran is printed, so you can copy it back into your file.
| Option | Default | Meaning |
|---|---|---|
-f, --file <path> | Read the query from a file. - as the query (or piped input) reads stdin. | |
--max-fixes <n> | 3 | How many times Claude may fix a failing query. 0 runs it as written and never calls Claude. |
--format <format> | table | table (Markdown), csv, or json (sql, fixes, columns, rows, truncated). |
What to expect:
- A working query never reaches Claude. It costs nothing and is as fast as your database.
- Claude sees the query and the error message, not result rows. It can read the knowledge base and nothing else, like the agent.
- Fixes keep your intent: same question, tables, filters and columns. Every fixed query goes
through the read-only guard again. A statement meant to change data (
DELETE,UPDATE, …) is refused without asking Claude. - It stops early when Claude cannot fix the query (a lost connection, missing permissions,
a timeout) or suggests a query that already failed. The exit code is then
1. - Rows are capped by
limits.max_rowslike every other query; raise it for bigger exports.
Needs a connector. In manual mode, run the query in your own SQL client.
Manual mode (no connector)
Without a connector in fastdata.yaml, fastdata cannot touch your database. It still does the
hard part, finding the right tables and writing the query, and then hands the query to you.
In the web chat: the query card says Run this yourself. Copy the SQL, run it in your SQL client, then paste the output into the box under the query and click Analyze results. Any of these formats works: CSV, TSV (what you get when you copy cells from most SQL clients or a spreadsheet), JSON, or a Markdown table.
In the terminal:
fastdata ask "What is the cancellation rate by channel?"
# … run the printed query, save the output as results.csv …
fastdata ask --continue --results results.csv
# or pipe it in:
psql "$DB" --csv -c "SELECT …" | fastdata ask --continue --results -
You can add a note with the results: fastdata ask --continue --results results.csv "Focus on mobile".
Writing good questions
- Name the time range ("last quarter", "since January") or accept the agent's stated assumption; it always lists the assumptions it made.
- Use your business words. If "active customer" has a specific meaning, put it in
glossary.mdonce and every answer will use it. - Ask follow-ups instead of restarting: "split that by country", "only enterprise".
- If an answer is wrong, check Assumptions and Query first; then fix the knowledge base (usually a glossary entry, a metric definition or a table note) so it stays fixed.
HTTP API
The web chat uses a small local API you can also call from scripts:
| Endpoint | Purpose |
|---|---|
GET /health | Liveness check |
GET /api/info | Mode (execute or manual), model, dialect, connector, example questions |
POST /api/chat | Body { "message": "...", "sessionId"?: "...", "results"?: "..." }. Response is a Server-Sent Events stream of events. Returns 409 if that conversation is still answering. |
GET /api/results/:id | Full result as JSON; add ?format=csv for CSV |
curl -N -X POST localhost:8000/api/chat \
-H 'content-type: application/json' \
-d '{"message":"How many customers do we have?"}'
Events
| Event | Fields |
|---|---|
session | sessionId (send it back to continue), mode |
step | label, tool |
text | text: the agent thinking out loud between steps |
query | queryId, sql, purpose, status (running or proposed) |
result | queryId, resultId, columns, rows (preview), rowCount, truncated, durationMs |
query_error | queryId, message |
answer | markdown |
error | message |
done | turns, costUsd, durationMs (always the last event) |
Results are kept in memory (the last 200) for as long as the server runs.