Skip to main content

sqlite-log-query

Nested system-manual reference for the additive SQLite/`log.sqlite` sidecar over LingTai's JSONL runtime traces. Read it when you need `lingtai-agent log doctor|query|rebuild`, read-only SQL safety and WAL/rebuild caveats, the events/chat_entries/token_entries schema, quick-start snippets, SQL recipes (`tool_call_id` lifecycle, tool result stats and percentiles, spilled/large tool results), gotchas, or the redaction rules. Runtime trace forensics start here; trajectory mining is the sibling `trajectory-mining`.

Jump to install

Source facts

Repository
Lingtai-AI/lingtai-kernel
Last source activity
August 9, 2026 at 23:13
Detected SKILL.md language
English
Stars
11
Forks
14

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
sqlite-log-query
description
Nested system-manual reference for the additive SQLite/`log.sqlite` sidecar over LingTai's JSONL runtime traces. Read it when you need `lingtai-agent log doctor|query|rebuild`, read-only SQL safety and WAL/rebuild caveats, the events/chat_entries/token_entries schema, quick-start snippets, SQL recipes (`tool_call_id` lifecycle, tool result stats and percentiles, spilled/large tool results), gotchas, or the redaction rules. Runtime trace forensics start here; trajectory mining is the sibling `trajectory-mining`.
version
1.4.0
tags
["lingtai","system-manual","sqlite","log.sqlite","runtime-logs","trace","jsonl","daemon","event-log","pitfalls","observability"]
last_changed_at
2026-08-09T00:00:00Z
related_files
["src/lingtai/intrinsic_skills/system-manual/SKILL.md","src/lingtai/intrinsic_skills/system-manual/reference/trajectory-mining/SKILL.md","src/lingtai/intrinsic_skills/system-manual/reference/sqlite-log-query/scripts/event_summary.py"]
maintenance
Tracks the sqlite-log-query topic it documents; update when that integration changes.
# SQLite Log Query LingTai keeps durable runtime traces and token ledgers in JSONL files. The SQLite file at `logs/log.sqlite` is an **additive, rebuildable query index** over those JSONL sources of truth. Use it to answer questions that are painful with `grep`: which event types are hottest, what happened inside daemon runs, what chat-history turn surrounded a failure, whether notification/daemon/context events are storming, or how token usage is distributed across main/soul/daemon sources. ## Start here for log.sqlite (quick start) First-use, copy-pasteable snippets. `log.sqlite` lives at `logs/log.sqlite` under the agent directory (`AGENT_DIR=/path/to/project/.lingtai/agent-name`). Open it read-only and run the three first checks: ```bash sqlite3 -readonly logs/log.sqlite '.tables' sqlite3 -readonly logs/log.sqlite 'pragma table_info(events);' sqlite3 -readonly logs/log.sqlite \ "select type, count(*) as n from events group by type order by n desc limit 20;" ``` Plain `sqlite3 logs/log.sqlite` opens the file read-write; for inspection prefer `-readonly` (or the Python URI form below) so you never accidentally write the sidecar. Equivalent read-only Python: ```python import sqlite3 db_path = "/path/to/.lingtai/<agent>/logs/log.sqlite" conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) for row in conn.execute( "select type, count(*) from events group by type order by count(*) desc limit 20;" ): print(row) ``` The five recipes in **Query recipes** below (event type counts, per-tool result length, percentiles, `tool_call_id` lifecycle, large/spilled results) are directly copy-pasteable. The **Safety contract** and **Gotchas** sections apply to every query. ## Safety contract - **JSONL is authoritative.** `logs/log.sqlite` is derived; deleting it should not delete facts. - **Prefer the CLI.** Use `lingtai-agent log ...` instead of opening the DB for writes yourself. - **Queries are read-only with respect to SQLite database contents.** `log query` accepts read-only `SELECT`, CTE (`WITH ... SELECT`), and `EXPLAIN` statements and opens the sidecar through the kernel read-only inspection path: they never write the main database file (opened `mode=ro` with `PRAGMA query_only=ON`), though SQLite read-support `-wal`/`-shm` sidecar files may still be created or updated. - **Rebuild is offline.** `log rebuild` requires the agent working-directory lock; if the agent is running, stop/sleep/lull/suspend it first as appropriate. - **Runtime SQLite is best effort.** New top-level `logs/events.jsonl` and standard `logs/token_ledger.jsonl` rows are indexed live after the JSONL write succeeds. Chat history, archive, and daemon JSONL sources are indexed into a target agent sidecar by explicit offline rebuild so normal turns and daemon runs do not pay recursive scan or live-rewrite costs. - **Live queries are snapshots.** Runtime writes use SQLite WAL mode. For a complete historical snapshot, stop the agent and run `log rebuild` before querying. - **Never paste secrets.** Logs and chat history can contain URLs, tokens, prompts, and user data — including raw `fields_json`/`entry_json`. Apply the redaction rules below before sharing anything. ## Gotchas - The event-kind column is **`type`**, not `event_type`. - The structured event payload lives in **`fields_json`** (chat rows in `entry_json`); reach into it with `json_extract(fields_json, '$.key')`. - `log.sqlite` is **derived and rebuildable**; JSONL remains authoritative. A missing sidecar is a rebuildable index gap, never lost facts. - Open the sidecar **read-only** for inspection: `sqlite3 -readonly`, the `file:...?mode=ro` URI, or `lingtai-agent log query`. Never write to it directly. - For exact forensic payloads, follow `source_file` / `source_offset` back to the JSONL source. - `fields_json`/`entry_json` can carry URLs, tokens, prompts, and user data — redact before sharing anything. ## Commands Set a variable for the target agent directory: ```bash AGENT_DIR=/path/to/project/.lingtai/agent-name ``` Check whether the sidecar exists and is readable: ```bash lingtai-agent log doctor "$AGENT_DIR" ``` If `doctor` reports `{"status":"missing"...}` or a failing `integrity_check`, rebuild **only while the target agent is stopped/offline**. `doctor` does not detect staleness — a stale but intact sidecar still reports `status: ok`; compare `import_cursors` against source-file mtimes, or just rebuild: ```bash lingtai-agent log rebuild "$AGENT_DIR" ``` `log rebuild` scans the known JSONL trace surfaces under the target agent: - `logs/events.jsonl` → `events` (`source_kind='agent_events'`) - `logs/token_ledger.jsonl` → `token_entries` (`source_kind='agent_token_ledger'`) - `history/chat_history.jsonl` → `chat_entries` (`source_kind='agent_chat'`) - `history/chat_history_archive.jsonl` → `chat_entries` (`source_kind='agent_chat_archive'`) - `daemons/*/logs/events.jsonl` → `events` (`source_kind='daemon_events'`, `run_id=<daemon folder>`) - `daemons/*/logs/token_ledger.jsonl` → `token_entries` (`source_kind='daemon_token_ledger'`, `run_id=<daemon folder>`) - `daemons/*/history/chat_history.jsonl` → `chat_entries` (`source_kind='daemon_chat'`, `run_id=<daemon folder>`) Run a read-only query. The CLI always prints JSON; pipe to `jq .` to pretty-print when it is available: ```bash lingtai-agent log query "$AGENT_DIR" \ 'SELECT id, ts, type, agent_address, substr(fields_json, 1, 240) AS fields FROM events ORDER BY ts DESC LIMIT 20' | jq . ``` ## Schema quick reference `events` indexes top-level agent runtime events and daemon run events: | Column | Meaning | |---|---| | `id` | SQLite row id, not a stable cross-rebuild event identifier | | `ts` | event timestamp as a numeric epoch-like value; ISO strings are parsed when possible | | `type` | event `type` field, or daemon `event` field | | `agent_address` | event `address` field when present | | `agent_name_snapshot` | event `agent_name` field when present | | `fields_json` | the remaining event fields as JSON text | | `source_file` | JSONL file imported from | | `source_offset` | byte offset in the JSONL source; unique with `source_file` | | `source_line` | 1-based JSONL line number | | `source_kind` | `agent_events`, `daemon_events`, or fallback kind | | `scope` | `agent`, `daemon`, or `unknown` | | `run_id` | daemon run folder name for daemon rows | | `inserted_at` | sidecar insertion time | `chat_entries` indexes agent and daemon chat-history JSONL rows: | Column | Meaning | |---|---| | `id` | SQLite row id, not stable across rebuilds | | `ts` | parsed numeric timestamp when a row has `ts`/`timestamp`, else `0` | | `ts_text` | original timestamp text/value as stored in JSONL | | `role` | chat role (`user`, `assistant`, etc.) when present | | `kind` | LingTai daemon user-entry kind (`task`, `tool_results`, `followup`) when present | | `turn` | daemon turn number when present | | `content_text` | best-effort extracted plain text from `text` or content blocks | | `entry_json` | full source chat row as JSON text | | `source_file`, `source_offset`, `source_line` | source JSONL identity | | `source_kind` | `agent_chat`, `agent_chat_archive`, `daemon_chat`, or fallback kind | | `scope` | `agent`, `daemon`, or `unknown` | | `run_id` | daemon run folder name for daemon rows | | `inserted_at` | sidecar insertion time | `token_entries` indexes agent and daemon token-ledger JSONL rows: | Column | Meaning | |---|---| | `id` | SQLite row id, not stable across rebuilds | | `ts` | parsed numeric timestamp when possible | | `ts_text` | original `ts` value from JSONL | | `input_tokens`, `output_tokens`, `thinking_tokens`, `cached_tokens` | token counters from the JSONL ledger row | | `model`, `endpoint` | model/provider endpoint metadata when present | | `source` | ledger source tag such as `main`, `soul`, `daemon`, `tc_wake`, or legacy/null | | `em_id`, `run_id`, `api_call_id` | daemon/run/API attribution when present | | `entry_json` | full source token-ledger row as JSON text | | `source_file`, `source_offset`, `source_line` | source JSONL identity | | `source_kind` | `agent_token_ledger`, `daemon_token_ledger`, or fallback kind | | `scope` | `agent`, `daemon`, or `unknown` | | `inserted_at` | sidecar insertion time | Parent ledgers intentionally include daemon spend rows. If you query both `agent_token_ledger` and `daemon_token_ledger` rows together, avoid double-counting daemon calls that were mirrored into the parent ledger and the daemon-local ledger. Filter by `source_kind`, `source`, `em_id`, or `run_id` according to the report you need. Maintenance tables: - `schema_migrations(version, name, applied_at)` records sidecar schema version. - `import_cursors(source_file, byte_offset, line_no, updated_at)` records the last rebuild/import cursor for each JSONL source. ## Query recipes Recent events: ```sql SELECT id, ts, type, source_kind, run_id, substr(fields_json, 1, 300) AS fields FROM events ORDER BY ts DESC LIMIT 50; ``` Event type counts across agent + daemon events: ```sql SELECT source_kind, type, COUNT(*) AS n, MIN(ts) AS first_ts, MAX(ts) AS last_ts FROM events GROUP BY source_kind, type ORDER BY n DESC LIMIT 50; ``` Per-tool result length aggregation (`$.result` holds the tool output text): ```sql SELECT json_extract(fields_json, '$.tool_name') AS tool, COUNT(*) AS n, CAST(AVG(length(json_extract(fields_json, '$.result'))) AS INT) AS avg_result_len, MAX(length(json_extract(fields_json, '$.result'))) AS max_result_len, SUM(CASE WHEN length(json_extract(fields_json, '$.result')) > 5000 THEN 1 ELSE 0 END) AS over_5000 FROM events WHERE type = 'tool_result' AND json_extract(fields_json, '$.result') IS NOT NULL GROUP BY tool ORDER BY n DESC LIMIT 20; ``` Nearest-rank percentiles of tool result lengths (SQLite window functions): ```sql WITH ranked AS ( SELECT length(json_extract(fields_json, '$.result')) AS result_len, ROW_NUMBER() OVER (ORDER BY length(json_extract(fields_json, '$.result'))) AS rn, COUNT(*) OVER () AS n FROM events WHERE type = 'tool_result' AND json_extract(fields_json, '$.result') IS NOT NULL ) SELECT MAX(CASE WHEN rn <= CAST(n * 0.50 + 0.5 AS INTEGER) THEN result_len END) AS p50, MAX(CASE WHEN rn <= CAST(n * 0.90 + 0.5 AS INTEGER) THEN result_len END) AS p90, MAX(CASE WHEN rn <= CAST(n * 0.95 + 0.5 AS INTEGER) THEN result_len END) AS p95, MAX(CASE WHEN rn <= CAST(n * 0.99 + 0.5 AS INTEGER) THEN result_len END) AS p99, MAX(result_len) AS max_len FROM ranked; ``` Trace one `tool_call_id` lifecycle. A full lifecycle typically spans `tool_call_received` -> `tool_reasoning` -> `tool_call_normalized` -> `tool_call_approved` -> `tool_call` -> `tool_call_dispatch_start` -> `tool_call_dispatch_done` -> `tool_result` -> `tool_result_durable_log_visible` -> `tool_result_model_visible` (daemon runs prefix some of these with `daemon_` and carry `run_id`): ```sql SELECT ts, type, source_kind, run_id, json_extract(fields_json, '$.tool_name') AS tool, substr(fields_json, 1, 160) AS fields FROM events WHERE json_extract(fields_json, '$.tool_call_id') = 'call_00_XXXX' OR json_extract(fields_json, '$.tool_trace_id') = 'call_00_XXXX' ORDER BY ts; ``` Find large/spilled tool results. `tool_result_spilled` rows keep the real size in `$.original_char_count` and point to `$.spill_path`: ```sql SELECT id, ts, type, json_extract(fields_json, '$.tool_name') AS tool, COALESCE(json_extract(fields_json, '$.original_char_count'), length(json_extract(fields_json, '$.result'))) AS result_len, json_extract(fields_json, '$.spill_path') AS spill_path FROM events WHERE type IN ('tool_result', 'tool_result_spilled') AND json_extract(fields_json, '$.result') IS NOT NULL ORDER BY result_len DESC LIMIT 20; ``` Recent chat-history entries: ```sql SELECT id, source_kind, run_id, role, kind, turn, substr(content_text, 1, 400) AS text FROM chat_entries ORDER BY id DESC LIMIT 50; ``` Join daemon tool events with daemon chat rows by `run_id`: ```sql
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub