Installing the agent
The agent is a small Node.js process that runs next to the database it observes. It reads PostgreSQL statistics read-only and pushes telemetry to the QueryPilot API. It never accepts inbound connections for data, so it works from private networks as long as it can reach the API.
1. Create an agent and get a token
Section titled “1. Create an agent and get a token”- Open the database in the web app and find the Agents panel.
- Click Create agent and give it a name, for example
orders-primary-agent. - Copy the token. It is shown once and stored only as a SHA-256 hash. If you lose it, rotate the token.
The same can be done over the API with POST /api/v1/databases/{id}/agents; the response contains
the token, the environment variables and a docker run command.
2. Configure it
Section titled “2. Configure it”The agent is configured only through environment variables:
| Variable | Required | Description |
|---|---|---|
QUERYPILOT_API_URL |
Yes | Base URL of the QueryPilot API, e.g. https://querypilot.internal:8080. Must be http or https. |
QUERYPILOT_AGENT_TOKEN |
Yes | The token from step 1. |
QUERYPILOT_TARGET_DATABASE_URL |
Yes | Connection string of the database to observe, using the monitoring role. |
AGENT_PORT |
No | Port of the health server. Default 8082. |
LOG_LEVEL |
No | debug, info, warn or error. Default info. |
Everything else (collection intervals, statement timeout, buffer size, plan-capture settings and whether query text may be sent) is decided centrally by the API and delivered at registration and on every heartbeat.
3. Run it
Section titled “3. Run it”Docker
Section titled “Docker”docker run -d --name querypilot-agent --restart unless-stopped \ -e QUERYPILOT_API_URL='https://querypilot.internal:8080' \ -e QUERYPILOT_AGENT_TOKEN='<token>' \ -e QUERYPILOT_TARGET_DATABASE_URL='postgres://querypilot:<password>@db.internal:5432/app' \ querypilot/agent:latestThe image is built from infrastructure/docker/agent.Dockerfile (Node 22 Alpine, runs as the
unprivileged node user, exposes 8082). Build it with:
docker build -f infrastructure/docker/agent.Dockerfile -t querypilot/agent:latest .Docker Compose (evaluation)
Section titled “Docker Compose (evaluation)”The repository’s docker-compose.yml has an opt-in agent service on the agent profile:
QUERYPILOT_AGENT_TOKEN='<token>' \QUERYPILOT_TARGET_DATABASE_URL='postgres://querypilot:querypilot@postgres:5432/querypilot' \docker compose --profile agent up -d agentFrom source
Section titled “From source”pnpm installpnpm --filter @querypilot/agent... buildQUERYPILOT_API_URL=http://localhost:8080 \QUERYPILOT_AGENT_TOKEN='<token>' \QUERYPILOT_TARGET_DATABASE_URL='postgres://querypilot:<password>@localhost:5432/app' \node apps/agent/dist/main.jsWhat it collects
Section titled “What it collects”| Collector | Source | Default interval | Row cap |
|---|---|---|---|
query-stats |
pg_stat_statements (top statements by total time) |
15 s | 1,000 |
activity |
pg_stat_activity, including wait events, application_name and the query id |
5 s | 500 |
connections |
pg_stat_activity |
10 s | None |
locks |
pg_locks + pg_stat_activity |
5 s | 500 |
tables |
pg_stat_user_tables, with sizes and each table’s own autovacuum settings |
60 s | 1,000 |
indexes |
pg_stat_user_indexes, with size and validity |
60 s | 2,000 |
plans |
EXPLAIN (GENERIC_PLAN) of the busiest SELECTs |
10 min | 20 queries |
| heartbeat | None | 30 s | None |
Collectors whose capability is missing are not scheduled. Query statistics are scoped to the
registered database even though pg_stat_statements is cluster-wide.
Activity samples are what Locks & waits is built from, and table and index statistics feed Tables & indexes. Sessions belonging to QueryPilot itself are excluded from activity samples, so monitoring never shows up as load.
How it protects your database
Section titled “How it protects your database”- Read-only sessions:
default_transaction_read_only=onon every connection. - Two connections at most: the agent does not eat your application’s connection budget.
- Timeouts: a server-side
statement_timeouton every session (5 s by default), plus a client-side ceiling. - No overlap: the next run of a collector is scheduled from the end of the previous one, so a slow database produces longer gaps, not a pile-up of monitoring queries.
- Plan capture never executes queries: see Execution plans.
Health and metrics
Section titled “Health and metrics”The agent’s health server starts first, even when the agent is misconfigured:
| Endpoint | Purpose |
|---|---|
GET /health/live |
Process is running. |
GET /health/ready |
Returns checks for configuration, database (ok / unreachable) and api (ok / not registered / token rejected), plus the last error. |
GET /metrics |
Prometheus metrics: querypilot_agent_collection_duration_ms, querypilot_agent_collection_errors_total, querypilot_agent_batches_sent_total, querypilot_agent_batches_failed_total, querypilot_agent_last_success_timestamp, querypilot_agent_queue_depth. |
An agent without its required variables still starts and reports exactly what is missing on
/health/ready rather than crash-looping.
Status in the UI
Section titled “Status in the UI”| Status | Meaning |
|---|---|
REGISTERED |
Created, but has never contacted the API. |
ONLINE |
Heartbeating and collecting. |
DEGRADED |
Heartbeating, but some collectors are failing or capabilities could not be read. |
OFFLINE |
No heartbeat for 3 heartbeat intervals (90 s by default). Derived when read, so a dead agent can never look online. |
REVOKED |
Token revoked; the agent can no longer send telemetry. |
Rotating and revoking
Section titled “Rotating and revoking”- Rotate token issues a new token; the previous one stops working immediately. Update the
agent’s
QUERYPILOT_AGENT_TOKENand restart it. While the token is rejected, the agent keeps its buffer and retries with a 60-second backoff instead of dropping data. - Revoke permanently disables the agent’s token.
Resilience
Section titled “Resilience”- If PostgreSQL or the API is unavailable at startup, the agent retries registration with backoff (1 s doubling up to 60 s).
- During an API outage, samples are buffered in memory up to the configured row limit, oldest dropped first. The number of dropped samples is reported in the next heartbeat.
- On
SIGTERMthe agent stops collecting, makes one bounded attempt (5 s) to deliver what is buffered, and disconnects.