Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

Quipu is configured via .bobbin/config.toml in your project directory or ~/.config/bobbin/config.toml for global defaults.

Config File

[quipu]
# Path to the SQLite triple store
store_path = ".bobbin/quipu/quipu.db"

# Base namespace new IRIs are minted under (optional; default is the aegis
# ontology namespace). A non-aegis deployment MUST set this before its first
# write — an IRI namespace is data identity and cannot be changed afterwards
# without re-ingesting every episode.
base_ns = "http://example.org/kb/"

[quipu.server]
# Enable the REST API server
enabled = false
# Bind address
bind = "127.0.0.1:3030"
# Require this bearer on write endpoints.
# auth_token = "current-token"
# During a credential rotation only, accept the old bearer for at most 24h.
# Use an absolute deadline so restarting cannot renew the old bearer.
# previous_auth_token = "old-token"
# previous_auth_token_expires_at_epoch_secs = 2000000000

[quipu.events]
# Event-log retention. Unset (the default) keeps every event forever.
# When set, the server hourly deletes events older than this many days —
# but never an event a registered consumer has not yet committed past, so
# a lagging consumer's replay is never broken (its backlog just stays on
# disk). A consumer registering AFTER a prune replays from the retained
# prefix, not from genesis.
# retention_days = 90

# Read-only databases mounted alongside the store. Each block is one layer:
# a knowledge pack, a shared reference database, a frozen archive someone
# handed you. Their named graphs become readable by `GRAPH <iri>` / `FROM <iri>`
# without changing what any existing query returns.
# [[quipu.attachments]]
# alias = "reference"
# path = "/srv/quipu/reference.qpack.db"

Config Fields

Every key below is wired — src/config.rs carries a test (config_knobs_are_wired_or_listed_unwired) that fails if a documented knob stops being read.

FieldDefaultDescription
store_path.bobbin/quipu/quipu.dbSQLite database path
base_nsaegis ontology NSBase namespace for minted IRIs (set before first write; --base-ns overrides per CLI call)
server.enabledfalseEnable REST API server
server.bind127.0.0.1:3030Server bind address
server.auth_tokenunsetBearer token required on write endpoints when set
server.previous_auth_tokenunsetPrevious write bearer accepted temporarily during rotation; requires server.auth_token and a positive grace duration
server.previous_auth_token_expires_at_epoch_secsunsetAbsolute UTC Unix epoch expiry for the previous bearer; at most 24 hours away when starting; expired previous bearers are ignored
server.read_onlyfalseRefuse all write endpoints
server.cors_allowed_origins[]CORS allowlist for the UI/API
server.read_pool_size4Read-only connection pool size (0 = all reads take the writer lock)
events.retention_daysunset (keep forever)Prune events older than N days, never past any registered consumer’s committed offset
labels.min_freshnessunsetGraph-label floor: refuse results staler than this
labels.min_trust_rank / labels.min_trust_chainunsetTrust floors on the query path
labels.deny_policy_tokens[]Policy-class tokens that exclude a graph from results
labels.deny_data_kinds[]Refuse queries composing graphs of these dataKind tokens (a blocklist — undeclared kinds pass)
search.default_limit10Result limit when the caller passes none
search.max_limit1000Hard cap on requested result limits
search.max_sparql_rows10000Cap on SPARQL result rows
search.query_timeout_ms30000SPARQL evaluation deadline
search.max_join_rows1000000Abort a join once an intermediate exceeds this
search.oversample_factor10Vector-search oversampling before filtering
shacl.validate_on_writefalseValidate episode ingest against the stored shapes
owl.validate_on_writetrueEnforce owl:disjointWith / owl:FunctionalProperty at write time (with functional-property supersede); set false for an explicitly informal deployment
governance.enforce_on_writefalseEvaluate action-boundary policies against every write (the write-time gate)
governance.validate_placementfalseCheck SARC class↔placement rules when a write defines/amends a policy
governance.verify_transitionsfalseRefuse a write landing an aegis:TransitionEvent whose signature is missing or does not verify under a registered key
governance.enforce_authorityfalseMake a supplied principal chain binding for graph writes
resolution.enabledfalseEntity resolution (dedup) on the episode write path
resolution.threshold / top_k / strict_mode0.85 / 3 / falseMatch threshold, candidate count, refuse-on-ambiguity
embedding.auto_embedfalseAuto-embed entities on write (needs model/tokenizer paths)
embedding.model_path / tokenizer_pathunsetONNX model + tokenizer for embeddings
embedding.dimension / max_sequence_length / embed_batch_size384 / 256 / 32Embedding runtime parameters
vector.backendsqlitesqlite or lancedb; selects the store’s vector backend at open (see below)
federation.remotes[]Remote quipu endpoints ({name, url, auth_token?, timeout_ms?}); health-checked at startup, queried via federated: true
attachments[]Read-only databases mounted alongside the store ([[quipu.attachments]] with alias and path); see below

Zero-downtime bearer rotation

Quipu captures bearer configuration at startup. To rotate without a write freeze, configure the new value as server.auth_token, the old value as server.previous_auth_token, and a short server.previous_auth_token_expires_at_epoch_secs, then restart quipu-server. Both bearers authenticate writes until the grace deadline; request telemetry records authenticated_current or authenticated_previous without recording either secret.

After consumers have switched, remove both previous_auth_token fields and restart again. That explicitly invalidates the old bearer immediately. If the cleanup restart is delayed, the old bearer still expires at the configured absolute deadline, and a restart never extends it. On restart, an expired previous bearer is dropped with a warning; the current bearer remains required and valid. Forgotten cleanup cannot cause a delayed startup outage. Invalid pairs, identical bearers, and deadlines over 24 hours away still make startup fail closed.

Attachments

[[quipu.attachments]] mounts other SQLite databases alongside your store, so their named graphs are readable in the same query as your own. Both binaries honour it at open — every quipu subcommand and quipu-server alike.

[[quipu.attachments]]
alias = "reference"
path = "/srv/quipu/reference.qpack.db"

[[quipu.attachments]]
alias = "tenant_a"
path = "packs/tenant-a.db"
KeyRequiredDescription
aliasYesSQLite schema name the file mounts under, and the source its contributed graphs carry in the graph registry. Must match ^[a-z][a-z0-9_]*$
pathYesPath to the database file; relative paths resolve against the working directory

What to expect:

  • Nothing silently changes. The default dataset stays your own ROOT alone, so an attachment is visible only to a query that names one of its graphs — GRAPH <iri>, FROM <iri>, or a dataset that includes it.
  • Mounts are read-only, always. There is no read_only = false: cross-database writes are permanently out of scope, so the key would be an affordance for something the store refuses anyway.
  • A bad declaration refuses the open — it never degrades into fewer rows. A missing file, an invalid or duplicate alias, a file with no g column (it predates named graphs), or one whose term space collides with yours all fail at startup, naming the alias and the remedy. A term-space collision is fixed with quipu db respace.
  • The packs you already build are attachable. quipu pack --space N ships a pack in its own term space, which is exactly what a collision-free mount needs.

See what is actually mounted — including deep freeze’s archives, which no config declares:

quipu db attach --list --db my.db

quipu-server prints the same list to stderr at startup.

Choosing a vector backend

[quipu.vector] backend selects the store’s vector backend at open, for both binaries: search, entity resolution, auto-embedding and the MCP/REST search tools all go through the selected one.

[quipu.vector]
backend = "lancedb"
lancedb_path = ".bobbin/quipu/quipu-vectors"

lancedb requires a binary built with the lancedb feature, and the release binaries are not. The feature drags in protoc and the whole datafusion tree, which most deployments do not need. A binary that lacks it refuses backend = "lancedb" at startup and names the rebuild — it does not fall back to the SQLite table, because a deployment that has run quipu migrate-vectors would then have every search answered out of the store it migrated away from.

Move existing embeddings across with quipu migrate-vectors --from sqlite --to lancedb. See LanceDB Vector Backend.

Not wired into the quipu CLI / quipu-server

Nothing, currently — every documented key above is read by the shipped binaries. The mechanism is kept rather than deleted: unwired_warnings() still exists, and any future key that parses but is not acted on must be listed there so setting it prints a warning: instead of being silently inert.

(Two keys used to sit here. federation.remotes was wired in quipu #47 — health-checked at startup and queried per-request via federated: true on POST /query, see Federation. vector.backend was wired in quipu-lv7, described just above.)

Priority Order

Configuration is resolved in this order (highest priority first):

  1. CLI flags (--db, --bind)
  2. Project config (.bobbin/config.toml in working directory)
  3. Global config (~/.config/bobbin/config.toml)
  4. Built-in defaults

CLI Overrides

CLI flags always take precedence:

quipu read "SELECT ..." --db /tmp/test.db    # Overrides store_path
quipu-server --bind 0.0.0.0:8080             # Overrides server.bind