Complete reference for all jeeves-watcher configuration options.
The CLI always takes the config path explicitly: jeeves-watcher start -c <path> (and service install -c <path>, config validate -c <path>). start loads the file with cosmiconfig, so it may be JSON, YAML or JavaScript/TypeScript; jeeves-watcher init -o <dir> writes <dir>/config.json.
Only programmatic use without a path (startFromConfig() or loadConfig() with no argument) searches for a config: a legacy jeeves-watcher.config.json in the working directory is first moved to jeeves-watcher/config.json and that file is loaded; otherwise cosmiconfig searches the working directory (not its parents) for:
jeeves-watcher property in package.json.jeeves-watcherrc (JSON or YAML).jeeves-watcherrc.json, .yaml, .yml, .js, .ts, .cjs, .mjsjeeves-watcherrc files under .config/ (for example .config/jeeves-watcherrc.json)jeeves-watcher.config.js, .ts, .cjs, .mjsWhen the config is loaded, string inferenceRules entries and string maps values (JSON file paths) are read relative to the config file's directory, defaults are applied and ${ENV_VAR} references are substituted (see Environment Variable Substitution).
The init command generates a config with a $schema pointer for IDE autocomplete and validation:
{
"$schema": "node_modules/@karmaniverous/jeeves-watcher/config.schema.json",
"watch": { ... }
}
This enables IntelliSense in VSCode and other editors that support JSON Schema.
jeeves-watcher config validate -c <path> checks a JSON config file against the same schema (without substituting ${ENV_VAR} references or loading rule files). A running service validates a candidate config more deeply with POST /config/validate.
Top-level keys the schema does not define are dropped without an error or warning. In particular metadataDir, extractors and slots, documented by older versions, are not config keys: enrichment lives in the SQLite enrichment store under stateDir, and extractors are built in (see Text Extraction).
Check the version before deploying new keys. Versions before 0.20.0 silently ignore config keys they do not know. A config that uses
sources,api.apiKeyorvectorStore.payloadIndexesloads without error on an older watcher, which then runs without those features (for example, with an open API). Confirmjeeves-watcher --versionreports 0.20.0 or later before deploying such a config.
interface JeevesWatcherConfig {
description?: string; // Organizational strategy description (v0.5.0+)
schemas?: Record<string, SchemaEntry>; // Global named schemas (v0.5.0+)
watch: WatchConfig;
configWatch?: ConfigWatchConfig;
embedding: EmbeddingConfig;
vectorStore: VectorStoreConfig;
stateDir?: string; // Persistent state directory (default ".jeeves-metadata")
api?: ApiConfig;
inferenceRules?: (InferenceRule | string)[]; // Inline rules or JSON rule file paths
maps?: Record<string, unknown>; // Named JsonMap definitions
templates?: Record<string, unknown>; // Named template definitions
mapHelpers?: Record<string, HelperRef>; // Named map helper modules
templateHelpers?: Record<string, HelperRef>; // Named template helper modules
search?: SearchConfig; // Score thresholds and hybrid search
reindex?: ReindexConfig; // Completion callback and concurrency
vcs?: VcsConfig; // Version control settings
sources?: Record<string, SourceConfig>; // Named callback sources for source documents (v0.20.0+)
logging?: LoggingConfig;
shutdownTimeoutMs?: number;
maxRetries?: number;
maxBackoffMs?: number;
}
description - Deployment Descriptionv0.5.0+
{
"description": "This archive indexes documents across organizational domains (email, slack, jira, etc.). The domain property is the primary partition: every record belongs to exactly one domain."
}
| Field | Type | Default | Description |
|---|---|---|---|
description |
string |
undefined |
Human-readable description of this deployment's organizational strategy and content domains. Consumed by LLM agents for orientation. |
This field provides organizational context for LLM consumers. Delivered alongside the JSON Schema from GET /config/schema.
schemas - Global Schemas Collectionv0.5.0+
Define reusable named schemas referenced by inference rules:
{
"schemas": {
"base": {
"type": "object",
"properties": {
"domain": {
"type": "string",
"description": "Content domain",
"uiHint": "select"
},
"created": {
"type": "integer",
"description": "Record creation date as unix timestamp (seconds)",
"uiHint": "date"
}
}
},
"jira-common": "schemas/jira-common.json"
}
}
| Entry Type | Description |
|---|---|
| Inline object | JSON Schema object defined directly in config |
| File path (string) | Relative path to a JSON schema file (resolved from config directory) |
Schema entries define property shapes (type, description, uiHint, enum) without set wiring. Inference rules reference these by name and layer on set templates in their inline tail objects.
See Inference Rules Guide for merge semantics and usage patterns.
watch - Filesystem Watching{
"watch": {
"paths": ["./docs/**/*.md", "./notes/**/*.txt"],
"ignored": ["**/node_modules/**", "**/.git/**"],
"debounceMs": 2000,
"stabilityThresholdMs": 500,
"pollIntervalMs": 1000,
"usePolling": false,
"respectGitignore": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
paths |
(string | { path, vcs? })[] |
Required | Glob patterns for files to watch (at least one). Supports picomatch syntax. An entry may be an object with a path glob and per-root vcs overrides (see Version Control). |
ignored |
string[] |
["**/node_modules/**"] |
Glob patterns to exclude from watching. Setting the key replaces the default, so list **/node_modules/** again if you need it. |
debounceMs |
number |
300 |
Events for the same file are collapsed until this long has passed since the last one (prevents re-embedding during rapid edits). |
stabilityThresholdMs |
number |
500 |
File must be stable (no size changes) for this long before processing. |
pollIntervalMs |
number |
1000 |
Polling interval if usePolling is enabled. |
usePolling |
boolean |
false |
Use polling instead of native filesystem events. Enable for network drives or Docker volumes. |
respectGitignore |
boolean |
true |
Skip files ignored by .gitignore in git repositories. Nested .gitignore files are respected within their subtree. Only applies to repos with a .git directory. |
moveDetection.enabled |
boolean |
true |
Treat an unlink followed by an add of the same content as a move: the points are re-keyed to the new path without re-embedding. |
moveDetection.bufferMs |
integer |
2000 |
How long (ms, min 100) an unlink is held while waiting for a matching add before it is treated as a delete. |
A config reload that changes paths, ignored, moveDetection or respectGitignore rebuilds the filesystem watcher with the new watch settings; a change to the other keys alone does not. debounceMs belongs to the file event queue and is read at startup only.
Glob examples:
{
"paths": [
"d:/email/archive/**/*.json", // All .json files under archive (Windows)
"./meetings/**/*.{txt,md}", // .txt or .md files under meetings
"**/*.pdf", // All PDFs recursively
"/absolute/path/to/docs/**", // Absolute path (Linux/macOS)
],
}
configWatch - Config File Watching{
"configWatch": {
"enabled": true,
"debounceMs": 10000
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Watch config file for changes and trigger scoped reindex. |
debounceMs |
number |
1000 |
Debounce window for config changes. |
reindex |
string |
"issues" |
Reindex scope on config change: "issues" (re-process files with open issues), "rules" (re-apply inference rules without re-embedding), or "full" (clear the values index and re-process every watched file; files whose embedded text is unchanged are still skipped by content hash). "path" and "prune" fail config validation here. |
When the config file changes:
watch settings changedA reload that fails (invalid config, unreadable rule file) is logged as Failed to reload config and the previous config stays in effect. POST /config/apply triggers the same configWatch.reindex scope after it writes the file.
The config-triggered reindex covers files only; source documents are re-processed by an explicit POST /reindex. Settings read only at startup (api.apiKey, sources, vectorStore.payloadIndexes) do not change on reload: restart the watcher.
embedding - Embedding Provider{
"embedding": {
"provider": "gemini",
"model": "gemini-embedding-001",
"apiKey": "${GOOGLE_API_KEY}",
"chunkSize": 1000,
"chunkOverlap": 200,
"dimensions": 3072,
"rateLimitPerMinute": 300,
"concurrency": 5
}
}
| Field | Type | Default | Description |
|---|---|---|---|
provider |
string |
"gemini" |
Embedding provider: "gemini", "mock". Any other name stops the service at startup (Unsupported embedding provider). |
model |
string |
"gemini-embedding-001" |
Model name (e.g., "gemini-embedding-001" for Gemini). |
apiKey |
string |
undefined |
API key. Supports ${ENV_VAR} template syntax. Required by the Gemini provider (startup fails without it); ignored by mock. |
chunkSize |
number |
1000 |
Maximum characters per chunk for text splitting. |
chunkOverlap |
number |
200 |
Overlap between consecutive chunks (helps preserve context at boundaries). |
dimensions |
number |
3072 |
Vector dimensions: the size of a new collection, and the vector size requested from Gemini (outputDimensionality). Must match an existing collection. |
rateLimitPerMinute |
number |
300 |
Max embedding API requests per minute, shared by every caller. 0 disables the request-rate limit. See Rate Limiting. |
concurrency |
number |
5 |
Max documents processed concurrently by the file event queue, and by the source-document notify queue. |
Behaviour change in 0.20.0.
embedding.rateLimitPerMinutenow limits embedding API requests, not file-watch events. Review the value when upgrading.
Before 0.20.0, rateLimitPerMinute drove a token bucket inside the file event queue that counted processed watch events, while reindex and search called the provider without any limit.
Since 0.20.0, one token bucket wraps the embedding provider. It is created once at startup and shared by file watching, source-document notifications, reindex and search:
rateLimitPerMinute, which is also the burst size) and refills continuously.0 disables the request-rate limit. When the key is omitted, the default of 300 applies.embedding.concurrency is unchanged in meaning: it caps how many queued documents are processed at once.On HTTP 429 the limiter pauses every caller for the provider's Retry-After (the header, or Google's retryDelay in the response body; without either, 1 second doubling per attempt, capped at the root maxBackoffMs, default 60 seconds; see Error Resilience below), then retries:
maxBackoffMs is waited in full (never shortened, so the retry is not throttled again).maxBackoffMs fails the request at once, and every other caller fails fast until the pause has fully passed (even once less than maxBackoffMs of it remains).embedding_error issue.maxBackoffMs), then POST /search answers 503 with a Retry-After header instead of 500:{
"code": "embedding_unavailable",
"error": "EmbeddingUnavailableError",
"message": "Embedding provider still rate-limited after 3 attempts: ..."
}
rateLimitPerMinute.{
"embedding": {
"provider": "gemini",
"model": "gemini-embedding-001",
"apiKey": "${GOOGLE_API_KEY}",
"dimensions": 3072
}
}
Models:
gemini-embedding-001 - 3072 dimensions (recommended)The provider calls the Gemini batchEmbedContents REST endpoint and requests vectors of the configured dimensions (outputDimensionality), so any size the model supports works: 3072 (native, the default and the init template value) or a smaller one such as 768 or 1536. A response of any other length fails with Gemini embedding returned invalid dimensions. Reduced-size Gemini vectors are not normalised; the collection's Cosine distance makes that irrelevant for search. dimensions must still match an existing collection.
{
"embedding": {
"provider": "mock",
"dimensions": 3072
}
}
Generates deterministic embeddings from content hashes. No API calls, no cost. Ideal for CI/CD tests.
vectorStore - Qdrant Configuration{
"vectorStore": {
"url": "http://localhost:6333",
"collectionName": "jeeves-watcher",
"apiKey": "${QDRANT_API_KEY}"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
url |
string |
Required | Qdrant server URL. |
collectionName |
string |
Required | Qdrant collection name. Created automatically if it doesn't exist. |
apiKey |
string |
undefined |
Qdrant API key (for Qdrant Cloud). |
payloadIndexes |
PayloadIndex[] |
[] |
Qdrant payload indexes to create at startup (v0.20.0+). See Payload Indexes. |
On startup, the watcher:
source and source_rule indexes)Warning: If the collection exists with different dimensions (e.g., manually created with 768 instead of 3072), the service will start but embedding upserts will fail with a dimension mismatch error.
To change embedding settings that affect vector dimensions:
collectionName in config)v0.20.0+
Qdrant filters on large collections are fast only on indexed fields. Declare the fields you filter on:
{
"vectorStore": {
"url": "http://localhost:6333",
"collectionName": "app_documents",
"payloadIndexes": [
{ "field": "user_id", "type": "keyword", "isTenant": true },
{ "field": "type", "type": "keyword" },
{ "field": "updated_at", "type": "integer", "isPrincipal": true }
]
}
}
| Field | Type | Description |
|---|---|---|
field |
string |
Payload field to index. |
type |
string |
Qdrant index type: keyword, integer, float, bool, datetime, uuid or text. |
isTenant |
boolean? |
Qdrant is_tenant: true: optimises storage for per-tenant filtering. Valid only for keyword and uuid. |
isPrincipal |
boolean? |
Qdrant is_principal: true: optimises storage for range filtering on the main ordering field. Valid only for integer, float and datetime. |
Behaviour:
isTenant / isPrincipal flags, the watcher logs a warning and leaves it alone (drop the index in Qdrant and restart to apply the configured one).source and source_rule always get keyword indexes and may not be listed here: an entry for either fails config validation.field fails config validation.chunk_text full-text index is separate: it is managed by search.hybrid.enabled, not by payloadIndexes.isTenant / isPrincipal on an unsupported type fails config validation.api - HTTP API Server{
"api": {
"host": "127.0.0.1",
"port": 1936
}
}
| Field | Type | Default | Description |
|---|---|---|---|
host |
string |
"127.0.0.1" |
Host to bind to. Use "0.0.0.0" to accept external connections. |
port |
number |
1936 |
Port to listen on. |
cacheTtlMs |
number |
30000 |
TTL in milliseconds for caching read-heavy endpoints. |
apiKey |
string |
undefined |
Bearer API key required on every route except GET /status (v0.20.0+). Supports ${ENV_VAR}. Read at startup only. |
The API provides endpoints for search, metadata enrichment, reindexing, and status. See API Reference.
When apiKey is set, every request except GET /status must send Authorization: Bearer <apiKey>, or it gets 401. It must be non-empty: "", or a ${...} that substitutes to an empty string, fails config validation, and an unresolved ${...} stops the service at startup. Omit the key to run without authentication. The whole api block is left out of GET /config. Changing the key requires a restart. The CLI API commands read the key from --api-key or JEEVES_WATCHER_API_KEY, and the OpenClaw plugin from its apiKey setting or the same variable; setting apiKey here does not configure them. See Authentication.
inferenceRules - Metadata Enrichment Rulesv0.5.0: Inference rules now use schema arrays instead of set objects. Rules require name and description fields.
{
"schemas": {
"base": {
"type": "object",
"properties": {
"domain": { "type": "string", "description": "Content domain" }
}
}
},
"inferenceRules": [
{
"name": "meeting-classifier",
"description": "Classify files under meetings directory",
"match": {
"properties": {
"file": {
"properties": {
"path": { "type": "string", "glob": "**/meetings/**" }
}
}
}
},
"schema": ["base", { "properties": { "domain": { "set": "meetings" } } }]
},
{
"name": "frontmatter-title",
"description": "Extract title from frontmatter",
"match": {
"properties": {
"frontmatter": {
"properties": {
"title": { "type": "string" }
},
"required": ["title"]
}
}
},
"schema": [
{
"properties": {
"title": {
"type": "string",
"description": "Document title",
"uiHint": "text",
"set": "{{frontmatter.title}}"
}
}
}
]
}
]
}
Each rule requires:
name (non-empty string, unique): rule identifierdescription (non-empty string): human-readable purposematch (JSON Schema object): attributes matcherOptional fields:
schema (array of named schema references and/or inline objects): metadata schema with set templatesmap (inline JsonMap, named map reference, or .json file path): transformation to derive metadatatemplate (inline Handlebars string, named template, or .hbs/.handlebars file path): content transformation for embeddingrender (declarative frontmatter + body sections): structured Markdown output; mutually exclusive with templaterenderAs (1–10 lowercase alphanumeric characters, e.g. "md"): output file extension override; requires template or renderAn inferenceRules entry may also be a string: the path of a JSON file holding one rule, resolved relative to the config file's directory.
See the Inference Rules Guide for full details on schema merge semantics, type coercion, uiHint and reserved field names.
maps - Named JsonMap Definitionsmaps is an optional dictionary of reusable JsonMap definitions.
Rules can reference these by name via inferenceRules[*].map: "mapName".
Each value is an inline JsonMap, a .json file path (resolved relative to the config directory when the config loads), or a wrapper object { "map": <JsonMap or file path>, "description": "..." }:
{
"maps": {
"extractProject": {
"description": "Extract project name from first path segment",
"map": {
"project": {
"$": [
{ "method": "$.lib.split", "params": ["$.input.file.path", "/"] },
{ "method": "$.lib.slice", "params": ["$[0]", 0, 1] },
{ "method": "$.lib.join", "params": ["$[0]", ""] }
]
}
}
},
"fromFile": "maps/from-file.json"
}
}
mapHelpers - Map Helper Modules{
"mapHelpers": {
"dateUtils": {
"path": "./helpers/date-utils.js",
"description": "Date parsing utilities"
},
"pathUtils": { "path": "./helpers/path-utils.js" }
}
}
Named object format (Record<string, { path, description? }>), paths relative to the config directory. Every exported function of the module (or of its default-export object) is added to the JsonMap $.lib as <namespace>_<export> (e.g., $.lib.dateUtils_parseDate).
templateHelpers - Template Helper Modules{
"templateHelpers": {
"jira": {
"path": "./helpers/jira-helpers.js",
"description": "Jira-specific Handlebars helpers"
},
"formatting": { "path": "./helpers/formatting.js" }
}
}
Named object format (Record<string, { path, description? }>), paths relative to the config directory. The module's default export is called as register(handlebars, namespace); every helper it registers is renamed to <namespace>_<name> (e.g., {{jira_ticketUrl key}}).
templates - Named Template Definitions{
"templates": {
"jira-issue": "templates/jira-issue.hbs",
"simple-doc": {
"description": "Simple document template",
"template": "# {{heading}}\n\n{{body}}"
}
}
}
Each value is a template string or a wrapper object { "template": "...", "description": "..." }. A string ending in .hbs or .handlebars is a file path (relative to the config directory); any other string is the template source.
search - Search Behavior{
"search": {
"scoreThresholds": {
"strong": 0.85,
"relevant": 0.7,
"noise": 0.5
},
"hybrid": {
"enabled": true,
"textWeight": 0.3
}
}
}
| Field | Type | Default | Description |
|---|---|---|---|
scoreThresholds.strong |
number |
none | Minimum score for a strong match (-1 to 1). |
scoreThresholds.relevant |
number |
none | Minimum score for a relevant match (-1 to 1). |
scoreThresholds.noise |
number |
none | Score below which results are noise (-1 to 1). |
hybrid.enabled |
boolean |
false |
Combine vector search with full-text (BM25) search on chunk_text, fused with RRF. |
hybrid.textWeight |
number |
0.3 |
Weight of the text results in the fusion (0 to 1). |
scoreThresholds has no defaults: the block is optional, but when present all three values are required. The service does not use them itself: they are published in GET /config for clients that grade results (the OpenClaw plugin, for example, tells agents to read them there).
With hybrid.enabled, the watcher creates a full-text index on chunk_text at startup if it is missing. Searches read the hybrid settings on every request, so a reload switches modes; after enabling hybrid search, restart once so the index exists.
stateDir - Persistent State{
"stateDir": "/var/lib/jeeves-watcher"
}
| Field | Type | Default | Description |
|---|---|---|---|
stateDir |
string |
".jeeves-metadata" |
Directory for the watcher's persistent state. Created if missing; a relative path resolves against the working directory. |
Contents:
enrichments.sqlite: the enrichment store (metadata written by POST /metadata and POST /rebuild-metadata), a SQLite database opened with Node's built-in node:sqlite (WAL mode, so -wal and -shm files sit next to it)issues.json: open processing issues, as returned by GET /issuesvalues.v8: the values index behind GET /search/facets and the rule values in GET /configThe enrichment store is the only file that cannot be regenerated: back it up (see Deployment). With VCS enabled, stateDir must not be equal to, inside, or a parent of any watch root; startup fails otherwise. Keep it out of the watched paths in any case (or add it to watch.ignored) so its files are not indexed.
reindex - Reindex Behavior{
"reindex": {
"callbackUrl": "http://localhost:8080/webhook/reindex-complete",
"concurrency": 50
}
}
| Field | Type | Default | Description |
|---|---|---|---|
callbackUrl |
string (URL) |
undefined |
URL to POST a JSON summary to when a reindex run completes. |
concurrency |
integer (≥ 1) |
50 |
Items a reindex works on at once: files, source documents submitted to the notify queue, and stored-rule lookups (issue-source discovery, POST /reindex with source). |
The callback body is { scope, filesProcessed, durationMs, errors, sources? }: errors lists the failed file paths, then the failed source references (or the run's error message when the run failed as a whole), and sources counts the source outcomes (indexed, unchanged, deleted, failed) when the run covered source documents. Delivery is attempted up to 3 times (exponential backoff from 1 s, capped at 4 s); a non-2xx answer counts as a failure. If every attempt fails, Reindex callback delivery failed; giving up is logged: the run's result is unaffected and the callback is not re-sent. Dry runs and path reindexes of source references send no callback. A failing file never stops a run: it is logged and listed in errors, and the remaining files are still processed.
logging - Logging Configuration{
"logging": {
"level": "info",
"file": "./logs/watcher.log"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
level |
string |
"info" |
pino log level: "trace", "debug", "info", "warn", "error", "fatal", or "silent" to turn logging off. Any other value stops the service at startup. |
file |
string |
undefined |
Log file path. If omitted, logs to stdout. |
Uses structured JSON logging via pino.
shutdownTimeoutMs - Graceful Shutdown{
"shutdownTimeoutMs": 10000
}
| Field | Type | Default | Description |
|---|---|---|---|
shutdownTimeoutMs |
number |
10000 |
Max time (ms) to wait for in-flight operations on shutdown (SIGTERM/SIGINT). |
On shutdown, the watcher (v0.20.0+ order):
shutdownTimeoutMs); stopping drops work the drain did not reach and waits for in-flight runsmaxRetries / maxBackoffMs - Error Resilience{
"maxRetries": 10,
"maxBackoffMs": 60000
}
| Field | Type | Default | Description |
|---|---|---|---|
maxRetries |
number |
Infinity |
Maximum consecutive system-level failures before triggering fatal error. |
maxBackoffMs |
number |
60000 |
Maximum backoff delay in milliseconds for system errors. |
vcs — Version Control{
"vcs": {
"enabled": true,
"commitThrottleMs": 30000,
"maxBatchSize": 1000,
"branch": "master",
"commitMessage": {
"enabled": true,
"provider": "anthropic",
"model": "claude-haiku-4-0",
"apiKey": "${ANTHROPIC_API_KEY}"
},
"retention": {
"maxAgeDays": 30,
"maxVersions": 100,
"squashCron": "0 0 * * *"
},
"defaultAccessToken": "${GIT_ACCESS_TOKEN}",
"staleLockThresholdMs": 60000,
"maxConsecutiveFailures": 5,
"circuitBreakerCooldownMs": 300000
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
false |
Enable git-backed content versioning. |
commitThrottleMs |
number |
30000 |
Throttle interval (ms) for batching commits (min: 1000). |
maxBatchSize |
number |
1000 |
Max files per commit batch (min: 1). |
branch |
string |
"master" |
Git branch name for VCS operations. Squash retention and startup recovery target this branch. |
author.name |
string |
"jeeves-watcher" |
Git commit author name, written to each repository's local git config at startup. |
author.email |
string |
"watcher@localhost" |
Git commit author email. |
commitMessage.enabled |
boolean |
true |
Enable AI-generated commit messages. |
commitMessage.provider |
string |
"anthropic" |
LLM provider. |
commitMessage.model |
string |
"claude-haiku-4-0" |
Model name. |
commitMessage.apiKey |
string |
undefined |
API key (${ENV_VAR} supported). Falls back to the provider key in the local OpenClaw config; without either, commits use a template message. |
retention.maxAgeDays |
number |
30 |
Squash commits older than this (min: 1). |
retention.maxVersions |
number |
100 |
Keep at most this many commits (min: 1). |
retention.squashCron |
string |
"0 0 * * *" |
Cron schedule for squash (5-field format). |
defaultAccessToken |
string |
undefined |
Shared access token for remote push (${ENV_VAR} supported). |
staleLockThresholdMs |
number |
60000 |
Age in ms after which an index.lock is considered stale and force-removed (min: 5000). |
maxConsecutiveFailures |
number |
5 |
Circuit breaker: trip after this many consecutive commit failures (min: 1). Pending files are retained. |
circuitBreakerCooldownMs |
number |
300000 |
Cooldown in ms before a tripped breaker makes a single recovery attempt (min: 1000). |
Retention defaults (30 days, 100 versions, daily midnight cron) apply automatically when vcs.enabled is true, even if the retention block is omitted.
Per-root overrides (including remote and accessToken) are configured via watch.paths object entries. A per-root vcs block takes the schema defaults for omitted keys (notably enabled: false), not the root-level values; see Per-Root Overrides. The root-level vcs block is never returned by GET /config. See Version Control (VCS) Guide for full details on configuration, API endpoints, squash retention, and remote push.
sources - Source Documentsv0.20.0+
Named HTTP callbacks for source documents: records that live in another system and are retrieved by reference (<name>://<id>).
{
"sources": {
"app": {
"url": "https://app.internal.example/watcher/documents/{id}",
"headers": { "Authorization": "Bearer ${APP_CALLBACK_TOKEN}" },
"timeoutMs": 10000,
"concurrency": 8
}
}
}
| Field | Type | Default | Description |
|---|---|---|---|
| key | string |
— | Source name used in references: a lowercase letter, then 1–31 of [a-z0-9-]. |
url |
string |
Required | Callback URL template containing {id}. Each /-segment of the id is URL-encoded; slashes are kept. Absolute http/https only, with {id} outside the scheme, host and port. |
headers |
Record<string, string> |
undefined |
Headers sent with every callback. |
timeoutMs |
number |
10000 |
Callback timeout (ms). |
concurrency |
number |
8 |
Max concurrent callbacks to this source. |
url and headers support ${ENV_VAR}; an unresolved reference stops the service at startup.GET /config.timeout) are ignored with a warning: logged at startup and on config reload, and returned by POST /config/validate as warnings.source_unavailable (response too large).See the Source Documents guide for the callback contract.
All string fields support ${ENV_VAR} template syntax:
{
"embedding": {
"apiKey": "${GOOGLE_API_KEY}"
},
"vectorStore": {
"url": "https://qdrant.example.com:6333",
"collectionName": "jeeves_archive",
"apiKey": "${QDRANT_API_KEY}"
}
}
When the config is loaded (at startup and on every reload), these are replaced with the environment variable values. A reference to a variable that is not set is left as the literal ${...} text, except for the secret-bearing values api.apiKey, sources.*.url and sources.*.headers.*: there a reference that cannot be resolved stops the service at startup (the error names the config path, never the value). Set templates in inference rules use Handlebars {{...}} syntax (e.g. {{frontmatter.title}}), which is distinct from the ${...} environment variable syntax used in config values like embedding.apiKey.
{
"description": "Production watcher indexing organizational documents across email, meetings, and projects",
"schemas": {
"base": {
"type": "object",
"properties": {
"domain": {
"type": "string",
"description": "Content domain",
"uiHint": "select"
}
}
}
},
"watch": {
"paths": [
"d:/email/archive/**/*.json",
"d:/meetings/**/*.{txt,md}",
"d:/projects/**/*.{md,pdf,docx}"
],
"ignored": ["**/node_modules/**", "**/.git/**"],
"debounceMs": 2000,
"stabilityThresholdMs": 500
},
"configWatch": {
"enabled": true,
"debounceMs": 1000,
"reindex": "issues"
},
"embedding": {
"provider": "gemini",
"model": "gemini-embedding-001",
"apiKey": "${GOOGLE_API_KEY}",
"chunkSize": 1000,
"chunkOverlap": 200,
"dimensions": 3072,
"rateLimitPerMinute": 300,
"concurrency": 5
},
"vectorStore": {
"url": "http://localhost:6333",
"collectionName": "jeeves_archive"
},
"stateDir": "d:/jeeves-watcher/state",
"api": {
"host": "127.0.0.1",
"port": 1936
},
"inferenceRules": [
{
"name": "meetings-classifier",
"description": "Classify meeting transcripts and notes",
"match": {
"properties": {
"file": {
"properties": {
"path": { "type": "string", "glob": "d:/meetings/**" }
}
}
}
},
"schema": ["base", { "properties": { "domain": { "set": "meetings" } } }]
}
],
"search": {
"scoreThresholds": {
"strong": 0.85,
"relevant": 0.7,
"noise": 0.5
}
},
"logging": {
"level": "info",
"file": "./logs/watcher.log"
}
}