-
Notifications
You must be signed in to change notification settings - Fork 0
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).
{
"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.
| 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 |
{
"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.
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.
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": {
"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": {
"stdio": true,
"http": { "enabled": true, "host": "127.0.0.1", "port": 8765 }
}| 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 |
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.