Skip to content

Configuration

Suavecito edited this page Jul 3, 2026 · 2 revisions

Configuration

obsidian-tc is configured by a single JSON file, loaded from an explicit path via the OBSIDIAN_TC_CONFIG environment variable or the CLI config-path argument. There is no XDG / %APPDATA% auto-discovery in v1.0. Secrets stay off disk via environment variables.

The schema is ServerConfigSchema, exported from @the-40-thieves/obsidian-tc-shared (packages/shared/src/config.schema.ts).

Minimal config

{
  "vaults": [
    { "id": "primary", "path": "/Users/me/vault", "restApiKey": "<local-rest-api-key>" }
  ]
}

One vault, default none auth (loopback only), Ollama embeddings. That is enough to serve.

Top-level fields

Field Type / default Purpose
cacheDir string, .obsidian-tc Where per-vault SQLite caches, traces, and spools live
vaults array, min 1 Vault registry (see below)
auth object none or jwt (HS256 or asymmetric RS256/ES256/EdDSA via JWKS)
acl object Root access-control block; each vault may override via vaults[].acl
embeddings object Provider, model, dimensions
transports object stdio (default on) and http
governor { maxResponseBytes: 1_000_000 } Response size ceiling
throttle object Per-class rate tiers + max concurrent writes/vault
observability object otel / prometheus / morgiana / retention
plur object (optional) Global plur read-proxy endpoint
idempotencyTtlSeconds int, 86400 Idempotency key lifetime
idempotencyReclaimSeconds int, 60 Reclaim window for a crashed in-flight idempotency row
elicitTtlSeconds int, 300 HITL elicit-token lifetime
maintenance { enabled: true, intervalMinutes: 60 } Periodic cache.db sweep (row expiry + event_log retention)
plane { enabled: true, intervalMinutes: 240 } Sleep-time consolidation scheduler; only works with an inference gateway configured
toolFacade { mode: "triad" } Advertised tool surface — triad (default) / domain / flat
toolVisibility object (optional) Hide/disable tools from the advertised surface

Vault entry (vaults[])

{
  "id": "primary",
  "name": "My Vault",
  "path": "/Users/me/vault",
  "mode": "auto",
  "restApiUrl": "http://127.0.0.1:27124",
  "restApiKey": "<key from Local REST API plugin>",
  "acl": { "readOnly": true },
  "bridges": { "timeoutMs": 5000 },
  "plugins": { "forceEnabled": ["dataview"], "probeSkip": false },
  "workspace": { "traceFolder": ".obsidian-tc/traces" }
}

mode is live | headless | auto (auto probes the Local REST API once at startup). The optional per-vault acl overrides the root ACL for this vault (here: read-only); omit it to inherit the root default.

Auth

HS256 (shared secret):

"auth": {
  "mode": "jwt",
  "jwtSecret": "<>= 32 chars, prefer OBSIDIAN_TC_JWT_SECRET env>",
  "tokenTtlSeconds": 86400
}

Asymmetric (RS256 / ES256 / EdDSA) via a JWKS — inline jwks or a jwksFile loaded once at boot; algorithms is an allowlist (default ["RS256","ES256","EdDSA"]), key rotation is kid-based:

"auth": {
  "mode": "jwt",
  "jwksFile": "/etc/obsidian-tc/jwks.json",
  "algorithms": ["RS256", "EdDSA"]
}

mode is none | jwt; in jwt mode supply either jwtSecret or a JWKS (jwks / jwksFile). A fail-closed interlock refuses to start when transports.http.enabled && auth.mode === "none" and the host is non-loopback — the server never serves an unauthenticated routable host. Full model in Security and ACL.

ACL (root + per-vault)

The root acl is the inherited default; each vaults[] entry may carry its own acl block (same shape) to override it — so "write vault A, read-only vault B" works in one process.

"acl": {
  "readOnly": false,
  "defaultScopes": ["read:vault"],
  "rules": [{ "glob": "02-projects/**", "scopes": ["read:vault", "write:vault"] }],
  "readPaths": ["**"],
  "writePaths": ["02-projects/**", "01-daily/**"],
  "deletePaths": [],
  "strictReadDefault": false
}

readOnly: true is the kill switch — it short-circuits every write/delete to a read_only_mode error. rules are last-match-wins. Omitting readPaths / writePaths / deletePaths leaves that op kind unrestricted; set strictReadDefault: true to make an undefined readPaths fail closed on reads.

Embeddings

"embeddings": {
  "provider": "ollama",
  "model": "nomic-embed-text",
  "dimensions": 768,
  "baseUrl": "http://127.0.0.1:11434"
}

Providers: ollama | openai | voyage | cohere. Each vault can use a different provider; a local-Ollama vault and a cloud-Voyage vault coexist cleanly.

Transports

"transports": {
  "stdio": true,
  "http": { "enabled": true, "host": "127.0.0.1", "port": 8765 }
}

Environment variables

Variable Purpose
OBSIDIAN_TC_CONFIG Path to the JSON config
OBSIDIAN_TC_DEFAULT_VAULT Default vault id when several are configured
OBSIDIAN_TC_JWT_SECRET JWT signing secret (keeps it off disk)
OBSIDIAN_TC_PLUR_ENDPOINT / OBSIDIAN_TC_PLUR_TOKEN plur read-proxy endpoint + token
OBSIDIAN_TC_FORCE_JS_FALLBACK=1 Force the pure-JS native fallback

Multi-vault

Adding or removing a vault requires a server restart (config edit + restart). reload_vault re-reads an existing vault's config and applies non-destructive changes live; destructive changes (path, embeddings provider/model, cacheDir) emit a vault_config_dirty event and need an explicit reset_vault_cache. Per-vault isolation (separate SQLite DB, traces, embeddings, ACL) is detailed in Architecture.

Clone this wiki locally