Skip to content

First real run ​

You have the dev stack up from Five-minute start. This page takes you one step further: a batched ingest, a KQL query that actually aggregates, and a quick look at the agent endpoint.

Ingest a small batch ​

NDJSON means one JSON object per line. The body below seeds eight rows into default.hello with mixed services and severities so the next step has something to summarize.

bash
curl -sS -X POST http://localhost:8080/v1/ingest \
  -H "X-Database: default" \
  -H "X-Table: hello" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @- <<'EOF'
{"_timestamp":"2026-05-02T10:00:00Z","service_name":"payments","severity_text":"INFO","message":"charge ok"}
{"_timestamp":"2026-05-02T10:00:05Z","service_name":"payments","severity_text":"ERROR","message":"card declined"}
{"_timestamp":"2026-05-02T10:00:09Z","service_name":"payments","severity_text":"ERROR","message":"card declined"}
{"_timestamp":"2026-05-02T10:00:11Z","service_name":"checkout","severity_text":"INFO","message":"order placed"}
{"_timestamp":"2026-05-02T10:00:14Z","service_name":"checkout","severity_text":"ERROR","message":"upstream timeout"}
{"_timestamp":"2026-05-02T10:00:17Z","service_name":"auth","severity_text":"WARN","message":"token near expiry"}
{"_timestamp":"2026-05-02T10:00:21Z","service_name":"auth","severity_text":"ERROR","message":"invalid signature"}
{"_timestamp":"2026-05-02T10:00:24Z","service_name":"shipping","severity_text":"INFO","message":"label printed"}
EOF

Response:

json
{
  "snapshot_id": "...",
  "extent_count": 1,
  "rows_ingested": 8,
  "bytes_written": 6411,
  "replayed": false
}

Schema evolution is on by default. New fields in subsequent NDJSON lines trigger an ALTER TABLE ADD COLUMN mid-ingest; history is never rewritten, older extents simply read as null for the new column.

A real KQL query ​

summarize is the workhorse. The query below counts errors per service and returns the top five:

bash
curl -sS -X POST http://localhost:8080/v1/query \
  -H "X-Database: default" \
  -H "Content-Type: application/x-kql" \
  --data-binary 'hello | where severity_text == "ERROR" | summarize n=count() by service_name | order by n desc | take 5'

Response (NDJSON, one row per line):

json
{"service_name":"payments","n":2}
{"service_name":"checkout","n":1}
{"service_name":"auth","n":1}

KQL is parsed by pensieve-kql (a chumsky-based parser), lowered to SQL, and executed by DataFusion. Pipe-stage operators like where, summarize, extend, project, order by, and take map onto the same logical plan the SQL frontend uses.

Ask the agent ​

POST /v1/agent/ask runs one agent turn against your data and streams the result as Server-Sent Events. It is the same surface your own agents will hit — the engine itself ships a small built-in one for prototyping.

bash
curl -N -sS -X POST http://localhost:8080/v1/agent/ask \
  -H "Content-Type: application/json" \
  --data '{"question":"which service had the most errors?","database":"default"}'

The response is a stream of named SSE frames. The event names you'll see:

EventWhen
run_startedOnce, with run_id, model, and the question.
thinking_deltaReasoning tokens (only if you pass "include_thinking":true).
answer_deltaStreaming chunks of the model's answer text.
tool_callThe agent invoked a tool — typically run_sql against the catalog.
tool_resultThe tool returned. For run_sql, the rows are inline.
answer_finalThe final aggregated answer, plus sql_used if any.
run_errorInit failure, runner error, tool-loop budget hit, or wall-clock timeout.
run_finishedAlways last. Carries elapsed_ms and tool_calls.

The full run is also persisted to agent_runs and can be replayed via GET /v1/agent/runs/{run_id}. For an annotated SSE transcript and the full event-payload schemas, see Query.

What's next ​

  • Concepts that explain the pruning cascade and the agent loop: Concepts
  • Other ingest frontends — OTLP, Kafka, file-drop: Ingest
  • KQL reference and the SQL/Flight surfaces: Query

An open-source project · MIT licensed.