DB Query Guidance
Safety notes and operator config knobs for the swarm's `db-query` tool/route (SQLite). Use when a query keeps timing out, gets rejected with a concurrency error, or targets session_logs, agent_log, events, or task_context_snapshots — those four tables are too large to read whole.
Template Content
DB Query Guidance
The swarm database is SQLite, reachable read-only through the db-query MCP
tool and the POST /api/db-query HTTP route (same executor underneath).
Execution model
By default a query runs in a short-lived child process with a wall-clock
budget (10s over HTTP, 5s over MCP) — the API keeps answering other requests
while it runs, and a query past its budget is killed. That protection can be
off: an operator can disable it, or a host with no separate bun executable
on $PATH falls back to running the query in-process with no budget. On the
fallback path, a query that reads millions of rows can stop the API long
enough for Kubernetes to restart it, failing every task in flight, even
though it doesn't crash anything outright.
Failures are documented and machine-readable, not a generic 400:
| Condition | HTTP status | Error code |
|---|---|---|
| Query ran past its budget | 408 | db_query_timeout |
| Too many bounded queries already in flight | 429 (with Retry-After) | db_query_concurrency_cap — retry shortly |
| Write statement, bad SQL, multiple statements | 400 | (message describes the problem) |
Four tables are too large to read whole
session_logs, agent_log, events, task_context_snapshots.
For these four tables:
- Filter on an indexed column.
session_logs:taskId,sessionId.agent_log:agentId,taskId,eventType,createdAt. - Add
LIMIT 1000or less. - Do not use
COUNT(*),SUM(...), ortypeof()across the table. - Do not split a large read into
rowidchunks — each chunk still reads every row in its range. Chunking does not make a large read safe.
If you need a row total or a column type census, ask the lead. Do not derive it yourself by scanning the table.
Columns audits guess wrong
Run SELECT name FROM pragma_table_info('<table>') before you guess a column.
agent_tasks: no cost column and nometadatacolumn. Cost issession_costs.totalCostUsd, joined ontaskId. AssigneeagentId, creatorcreatorAgentId, modelmodel,resolvedModel,modelSource.parentTaskIdis the only SQL child finder, becausekeyis unique per task.scripts: the owner iscreatedByAgentId; there is noagentId.isScratch = 1marks inlinescript-runbodies (scratch-*names), and they outnumber named scripts many times over. AddisScratch = 0to catalog audits. To find a script's callers, group byisScratchinstead of dropping scratch rows: inline runs are often the negative tests.script_runs:scriptName,agentId,status,startedAt,finishedAt,error. There is no duration column: use(julianday(finishedAt) - julianday(startedAt)) * 86400.session_costs:taskId,agentId,totalCostUsd,model, the token columns,createdAt.
Operator config knobs
All are read dynamically (a swarm_config edit or env var change takes
effect on the next request, no restart) and surfaced on the dashboard's
Settings → Configuration → Database queries group.
| Env var | Default | Controls |
|---|---|---|
DB_QUERY_BOUNDED_ENABLED | true | Master kill switch for the bounded child-process path. false restores the pre-fix synchronous, unbounded path and logs a one-time startup-class warning. |
DB_QUERY_HTTP_BUDGET_MS | 10000 | Wall-clock budget for /api/db-query before its child process is killed. |
DB_QUERY_HTTP_MAX_ROWS | 1000 | Row cap for /api/db-query, regardless of how many rows the query matched. |
DB_QUERY_MCP_BUDGET_MS | 5000 | Wall-clock budget for the MCP db-query tool. |
DB_QUERY_MCP_MAX_ROWS | 100 | Row cap for the MCP db-query tool, regardless of how many rows the query matched. |
DB_QUERY_CONCURRENCY_CAP | 3 | Max bounded queries in flight at once, across HTTP and MCP callers. Each in-flight query can peak around 200MB; the default is sized against a 1 GiB API pod memory limit — raise it only if the pod has more headroom. |
total in a result always reports every row SQLite matched, even when the
returned rows are capped — compare the two to detect truncation.