Configure
Use the side menu to jump: a short worked example, then every common YAML field for preset/catalog, runtime config, and env.
Overview & example
Required: qllm.preset.yaml + qllm.catalog.yaml. Optional:
qllm.config.yaml, qllm.env.yaml,
qllm.access.yaml. Secrets are names of env vars (*Env), never
the secret text in YAML.
my-project/
qllm.preset.yaml
qllm.catalog.yaml
qllm.config.yaml # optional
qllm.env.yaml # optional (local)
qllm.access.yaml # optional (per-app keys)
./qllm validate --config-dir ./my-project
./qllm serve --http --config-dir ./my-project
Worked example: warehouse DB + stock API
Postgres holds product master data; a REST inventory service exposes
GET /v1/stock. You only publish the fields the agent may see.
# qllm.preset.yaml (excerpt)
protocolVersion: "0.2.0"
project: warehouse
limits:
maxSyncMs: 15000 # ms (~15 s total)
maxSourceMs: 12000 # ms (~12 s per source)
defaultLimit: 100 # rows
maxLimit: 1000 # rows
readOnly: true
sources:
- id: warehouse_pg
type: postgres
connection:
hostEnv: QLLM_PG_HOST
port: 5432
database: warehouse
userEnv: QLLM_PG_USER
passwordEnv: QLLM_PG_PASSWORD
sslMode: require
- id: stock_api
type: rest
connection:
baseUrlEnv: QLLM_STOCK_API_BASE
auth:
type: bearer
tokenEnv: QLLM_STOCK_API_TOKEN
options:
timeoutMs: 10000 # ms
resources:
stock:
list:
method: GET
path: /v1/stock
queryParams: [sku, warehouse_id, limit, offset]
itemsKey: data
getById:
method: GET
path: /v1/stock/{sku}
itemsKey: data
# qllm.catalog.yaml (excerpt)
protocolVersion: "0.2.0"
project: warehouse
entities:
- name: products
source: warehouse_pg
binding: { kind: table, schema: warehouse, table: products }
fields:
- { name: sku, physical: sku, type: string }
- { name: title, physical: title, type: string }
- { name: weight_kg, physical: weight_kg, type: number }
- name: stock_levels
source: stock_api
binding: { kind: rest_resource, resource: stock }
fields:
- { name: sku, physical: sku, type: string }
- { name: qty, physical: qty, type: number }
- name: location
physical: location
type: json
shape: "{aisle, bin}"
- name: tags
physical: tags
type: json
shape: "string[]"
- name: warehouse_id
physical: warehouse_id
type: string
fromFilter: true
-
json
Nested objects/arrays: one top-level field with
type: json+ optionalshape(decision D22). REST does not resolve dotted paths likelocation.aisle. -
fromFilter
API takes a filter key but omits it in the body → set
fromFilter: true(decision D20). Needs a top-leveleqor the query isINVALID_IR. -
itemsKey
When the body is
{ "data": [ … ] }, setitemsKey: dataon the REST operation.
Draft helpers:
catalog introspect (SQL) and catalog from-openapi (REST).
Always review before serve.
Preset & catalog fields
Logical ids (sources[].id, entity/field names):
^[a-z][a-z0-9_]*$. Extra keys on closed objects → reject.
qllm.preset.yaml — root
Required: protocolVersion, project, limits, sources (≥1).
| Field | Notes |
|---|---|
protocolVersion |
0.1.0 / 0.2.0 |
project |
Must match the catalog project |
limits |
All five fields required |
sources[] |
Each needs id, type, connection |
limits (all required)
| Field | Range / rule |
|---|---|
maxSyncMs |
100–60000 ms · total query budget (typical 15000 ms ≈ 15 s) |
maxSourceMs |
100–60000 ms · per-source round trip (typical 12000 ms ≈ 12 s) |
defaultLimit |
≥ 1 row · when IR/SQL omits LIMIT (typical 100 rows) |
maxLimit |
≥ 1 row · hard ceiling (typical 1000 rows) |
readOnly |
must be true |
sources[]
| Field | Notes |
|---|---|
id |
Referenced by catalog entities[].source |
type |
Stable: postgres mysql mongodb
rest. Also experimental types + wire aliases — see
Connectors.
|
connection |
Shape depends on type; extra keys error |
options |
Runtime reads known keys only (timeoutMs in
ms, statementTimeoutMs in ms,
REST resources, …). Typos fail silently.
|
connection by type (cheat sheet)
| type | Required / notable |
|---|---|
postgres / mysql (+ aliases) |
hostEnv port database
userEnv passwordEnv; optional
sslMode (postgres)
|
mssql |
Same required; optional encrypt; no sslMode |
sqlite |
pathEnv only |
mongodb |
uriEnv, database |
rest / ksql / graphql |
baseUrlEnv; optional auth
(none|bearer|header|basic
|
dynamodb |
region; optional endpointEnv |
redis |
addrEnv or hostEnv+port |
kafka |
brokersEnv; optional TLS/SASL |
REST/ksql auth: bearer → tokenEnv;
header → name + valueEnv;
basic → userEnv + passwordEnv.
REST options.resources
Required to query. Catalog binding.resource must be a key of this map.
Each resource may define list and optional getById.
| Operation field | Meaning |
|---|---|
method |
Default GET; with readOnly only GET/HEAD |
path |
Starts with /; {name} from eq on getById |
queryParams |
Documents filters; runtime does not enforce the list |
itemsKey |
JSON key holding the array/object (data, items, …) |
maxPages / pageSize |
Offset pagination; maxPages capped at 20 |
limitParam / offsetParam |
Defaults limit / offset |
Only eq filters push down to REST. Nested JSON → top-level
type: json. Missing echoed filter key → fromFilter: true.
qllm.catalog.yaml — entities[]
Required root: protocolVersion, project, entities (≥1).
| Field | Notes |
|---|---|
name |
FROM / IR from |
aliases |
Optional alternate IR names |
description |
Text for the agent |
source |
Must match a preset sources[].id |
binding |
Physical mapping (below) |
primaryKey |
Logical field names |
fields |
≥ 1 field |
relations |
Hints only; do not create FKs |
scope |
Force eq from the credential (row scope —
decision D21)
|
binding.kind
| kind | Also required |
|---|---|
table |
schema, table (sqlite: schema: main) |
collection |
collection (mongodb) |
rest_resource |
resource → key of options.resources |
graphql_operation |
resource → key of options.operations |
key |
redis: keyPattern + partition access path |
topic |
kafka: topic + access path |
fields[]
Required: name, type, physical.
| Field | Notes |
|---|---|
type |
string number boolean
timestamp json. Big ids → string
(number is DOUBLE in DuckDB).
|
physical |
Column/key; dotted paths OK except REST (top-level only) |
fromFilter |
REST: fill from eq when body omits the key
(decision D20
|
shape |
Free text for type: json; shown in catalog, not validated
(decision D22
|
description |
Optional agent hint |
relations[]
Required: name, to, type
(many_to_one|one_to_many|one_to_one, on (pairs of
[local, remote]). Hints for agents — queries must still express the join.
qllm.config.yaml
Optional. Only the serve key. Precedence: secure defaults → this file →
CLI flags. If you set authTokenEnv, that env var must be non-empty or
serve refuses to start.
| Field | Default | Meaning |
|---|---|---|
serve.addr |
127.0.0.1:8088 |
HTTP /v1 bind |
serve.mcpAddr |
127.0.0.1:8089 |
MCP HTTP bind |
serve.authTokenEnv |
(none) | Name of env var holding the shared Bearer token |
serve.insecureBind |
false |
Required for non-loopback bind without auth |
serve.maxBodyBytes |
1048576 bytes (1 MiB) | POST body cap (≥ 1024 bytes) |
serve.maxRestResponseBytes |
10485760 bytes (10 MiB) | REST response cap (≥ 1024 bytes) |
serve.cors.origins |
[] |
Empty = CORS off; * rejected |
serve.cors.allowHeaders / allowMethods |
— | Optional CORS lists |
serve:
addr: "127.0.0.1:8088"
mcpAddr: "127.0.0.1:8089"
authTokenEnv: "QLLM_AUTH_TOKEN"
insecureBind: false
cors:
origins: []
CLI overrides: --addr, --mcp-addr,
--auth-token-env, --insecure-bind,
--cors-origin (repeatable).
Optional: qllm.access.yaml
Replaces the single Bearer token with per-app keys and tables. Not part of
qllm.config.yaml, but often configured next to it. Row scope comes from the
credential (decision D21). Details:
multi-user-safety.md.
apps:
- name: inventory-agent
key: ${QLLM_INVENTORY_AGENT_KEY}
tables: [products, stock_levels]
qllm.env.yaml
Optional seed file. Required shape: root key env with at least one entry.
Used when the process does not already have the variable set — a non-empty process /
Secret value wins and is not overwritten.
| Rule | Detail |
|---|---|
| Key names | ^[A-Za-z_][A-Za-z0-9_]*$ |
| Values | Non-empty string; literal or exactly ${OTHER_ENV} |
| Empty placeholders | Do not create them — apply skips empty named refs |
| Git / K8s | Do not commit real secrets; on Kubernetes use a Secret |
# qllm.env.yaml — local / compose style
env:
QLLM_AUTH_TOKEN: ${QLLM_AUTH_TOKEN}
QLLM_PG_HOST: postgres
QLLM_PG_USER: qllm
QLLM_PG_PASSWORD: ${QLLM_PG_PASSWORD}
QLLM_STOCK_API_BASE: http://inventory:8080
QLLM_STOCK_API_TOKEN: ${QLLM_STOCK_API_TOKEN}
Every *Env name in the preset (and authTokenEnv in config)
must resolve at serve time. Hostnames like postgres are fine for Compose
DNS; passwords should stay as ${…} injected by the environment.
Prefer exporting vars in the shell or orchestration for production. This file is a convenience for local layouts — see environments.md.