Skip to content

nimblebrain.json

NimbleBrain splits configuration into two files:

  • nimblebrain.json — instance-level settings (models, HTTP, logging, limits, feature flags). One file per deployment. This page.
  • workspace.json — per-workspace settings (bundles, skill directories, named agent profiles, optional model + identity overrides). See workspace.json.

Top-level bundles, skillDirs, agents, home, and preferences entries in nimblebrain.json are silently stripped at load — they belong in workspace.json now. See workspace configuration for where they live.

NimbleBrain looks for the config file in this order, stopping at the first match:

  1. --config <path> — Explicit path passed as a CLI flag.
  2. .nimblebrain/nimblebrain.json — Project-local config directory in the current working directory.
  3. <defaultWorkDir>/nimblebrain.json — Inside the command’s default working directory (~/.nimblebrain for bun run start, the project-local workdir for worktree dev).
  4. ./nimblebrain.json — Current working directory (bare file).

If no config file exists at the resolved path, NimbleBrain auto-creates a minimal default file ({ "$schema": …, "version": "1" }) as long as the parent directory exists.

A sibling nimblebrain.overrides.json next to the resolved config file is deep-merged on top of it at load. The seed (nimblebrain.json) is operator-managed and overwritten on every deploy; the override is written by the admin set_model_config tool and preserved across deploys. The merge is one level deep — top-level objects (e.g. models) are merged key-by-key so an override of models.fast doesn’t clobber the seed’s models.default; scalars and arrays are replaced wholesale. A malformed override file is logged loudly and skipped (the seed still boots) rather than taking down startup.

{
"$schema": "https://schemas.nimblebrain.ai/v1/nimblebrain-config.schema.json",
"version": "1",
"models": {
"default": "anthropic:claude-sonnet-4-6",
"fast": "anthropic:claude-haiku-4-5-20251001",
"reasoning": "anthropic:claude-opus-4-6"
},
"providers": {
"anthropic": { "apiKey": "sk-ant-..." },
"openai": { "apiKey": "sk-..." }
},
"maxIterations": 25,
"maxInputTokens": 500000,
"maxOutputTokens": 16384,
"maxToolResultSize": 1000000,
"logging": { "dir": "~/.nimblebrain/logs", "disabled": false, "level": "normal", "retentionDays": 30 },
"http": { "port": 27247, "host": "127.0.0.1" },
"features": {
"bundleManagement": true,
"skillManagement": true,
"delegation": true,
"toolDiscovery": true,
"bundleDiscovery": true,
"fileContext": true,
"userManagement": true,
"workspaceManagement": true,
"compaction": false
},
"files": {
"maxFileSize": 26214400,
"maxTotalSize": 104857600,
"maxFilesPerMessage": 10,
"maxExtractedTextSize": 204800
},
"allowInsecureRemotes": false,
"telemetry": { "enabled": true },
"workDir": "~/.nimblebrain"
}
FieldTypeDefaultDescription
$schemastringJSON Schema URI. Set to "https://schemas.nimblebrain.ai/v1/nimblebrain-config.schema.json" for editor autocompletion.
versionstringConfig file version. Must be "1".
modelsobjectRole-based model slots. See models.
providersobjectPer-provider API keys. See providers.
modelobject{ "provider": "anthropic" }Legacy single-provider config. Use providers instead.
defaultModelstringDeprecated. Use models.default instead.
maxIterationsinteger25Max agentic iterations per request. Range: 1—50.
maxInputTokensinteger500000Max input tokens per request. Engine stops when this budget is exceeded.
maxOutputTokensinteger16384Max output tokens per LLM call.
maxToolResultSizeinteger1000000Max characters for a single tool result. 0 disables the cap.
thinking"off" | "adaptive" | "enabled"Extended-thinking mode for reasoning-capable models. off: never reason. adaptive: model decides per call. enabled: always reason (cap with thinkingBudgetTokens). Typically set via the admin set_model_config tool, which writes it to nimblebrain.overrides.json.
thinkingBudgetTokensintegerToken budget when thinking: "enabled". Counts toward maxOutputTokens. Anthropic requires a minimum of 1,024.
sessionStoreobject{ "type": "memory", "ttlSeconds": 28800 }MCP session metadata store for /mcp. See sessionStore.
loggingobjectStructured logging config. See logging.
httpobject{ "port": 27247, "host": "127.0.0.1" }HTTP server config. Omit for programmatic-only use.
featuresobjectall trueFeature flags. See Feature Flags.
filesobjectFile upload limits. See files.
allowInsecureRemotesbooleanfalseAllow HTTP (non-TLS) remote bundle connections. Dev only.
telemetryobject{ "enabled": true }Anonymous usage telemetry. See telemetry.
workDirstring"~/.nimblebrain"Working directory for runtime state (conversations, logs, workspaces).

The models object declares three role-based slots. Each value is a provider:model-id string (e.g. "anthropic:claude-sonnet-4-6").

{
"models": {
"default": "anthropic:claude-sonnet-4-6",
"fast": "anthropic:claude-haiku-4-5-20251001",
"reasoning": "anthropic:claude-opus-4-6"
}
}
SlotUsed for
defaultChat, general requests, most tool-using turns
fastTitle generation, briefing, skill matching — cheap/fast workloads
reasoningComplex analysis, planning, long-horizon tasks

A workspace can override individual slots via workspace.json → models. When a nb__delegate call or agent profile specifies a slot name (e.g. "model": "fast"), it’s resolved against these slots.

Per-provider API keys. Takes precedence over the legacy model object when both are present.

{
"providers": {
"anthropic": { "apiKey": "sk-ant-..." },
"openai": { "apiKey": "sk-..." },
"google": { "apiKey": "AIza..." }
}
}

Each provider key is optional. If a provider’s apiKey is omitted, the SDK falls back to the provider’s standard env var (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY).

Conversations are workspace-owned and persisted automatically — there is nothing to configure. Every turn is written to a per-conversation JSONL file at {workDir}/workspaces/<wsId>/conversations/<ownerId>/<convId>.jsonl in the event-sourced format (one append-only event log per conversation), which is what enables crash-resilient run replay and history compaction. Legacy message-format JSONL files are still read.

Pluggable metadata store for MCP HTTP sessions. Tracks each Mcp-Session-Id and its idle TTL across the cluster.

{
"sessionStore": {
"type": "memory",
"ttlSeconds": 28800
}
}

Process-local Map with periodic TTL sweep. No external dependencies. Default; fine for any single-replica deploy.

FieldTypeDefaultDescription
type"memory" | "redis""memory"Provider. redis requires redis.url.
ttlSecondsinteger (s)28800 (8 h)Idle TTL. Each request resets the clock — actively-used sessions never expire.
redis.urlstringRedis connection URL (redis:// or rediss://). Required when type is "redis". Supports ${VAR} env expansion.
redis.keyPrefixstring"nb:mcp:session:"Hash key prefix for session entries.

The MCP /mcp endpoint is stateful. Each session has two parts:

  • MetadatasessionId, identityId, workspaceId, timestamps. Just data; lives in the session store.
  • Live transport — open HTTP response stream, SDK Server instance, registered request handlers, in-flight JSON-RPC state. Process-bound — holds open file descriptors and JS object references that cannot be serialized or moved.

Metadata is shareable across processes; the live transport is not. Routing requests to the process that owns a session’s transport is the load balancer’s job (cookie stickiness, header-hash on Mcp-Session-Id), not the session store’s. The session store is deliberately deployment-vocabulary-free — no notion of “which pod owns this” — so the same interface fits any deployment topology.

When a request arrives at a process that doesn’t have the live transport for the requested session, the 404 carries error.data.reason:

ReasonMeaning
not_foundSession store has no record. Idle-TTL eviction, or session never existed.
unavailableSession exists in the store, but the live transport isn’t on this process. Possible causes: process restart, sticky-routing miss, transport closed locally.

Spec-compliant clients re-initialize on either reason. Operators distinguish unavailable causes via deploy timing, process uptime, and “session-store size vs local transport count” signals.

ReplicasStoreUse case
1memoryDefault. Dev, programmatic use, simple production. Sessions die on process restart; clients re-initialize.
1redisSingle-process with cluster-shared metadata for ops visibility, audit trails, or future scale-out.
NredisHorizontal scale. Pair with sticky load-balancing (e.g. ALB lb_cookie) — without it, every cross-process request returns unavailable and clients re-initialize constantly.
NmemoryNot supported. Sessions partition across processes.

When type: "redis" is set without a redis.url, NimbleBrain refuses to fall back to in-memory and fails the boot — silently degrading would mask a misconfiguration.

platform.replicas > 1 requires all of:

  1. Storage that supports concurrent multi-process mounts. The default chart’s PVC is ReadWriteOnce and blocks RollingUpdate rollouts. Move workspace data out of the PVC (Postgres/S3) or relocate to ReadWriteMany (EFS).
  2. Routing strategy keyed on Mcp-Session-Id. ALB lb_cookie stickiness is the simplest path on AWS; alternatives are NGINX/Envoy with header-hash routing, or application-level cross-process proxying.
  3. Cluster-shared session store. sessionStore.type: "redis".
  4. Deploy strategy that doesn’t drop all replicas at once. platform.strategy.type: RollingUpdate (with the storage from (1) in place).

Drop any one of these and multi-replica is broken in a different way: PVC deadlock, re-init storms, partitioned session view, or deploy-time outages.

The 8 h default is tuned for a connector left open during a working day. Tighten it (e.g. 1800 for 30 minutes) if you want sessions to evict faster on idle; loosen it if your typical usage pattern leaves connectors open across days.

MCP_SESSION_TTL_SECONDS env var overrides this when set.

{
"logging": {
"dir": "~/.nimblebrain/logs",
"disabled": false,
"level": "normal",
"retentionDays": 30
}
}
FieldTypeDefaultDescription
dirstring{workDir}/logsLog directory.
disabledbooleanfalseDisable structured logging entirely.
level"normal" | "debug""normal""debug" persists additional verbose fields.
retentionDaysintegerAuto-delete log files older than N days on startup. Omit for no cleanup.

File upload limits applied when features.fileContext is enabled.

{
"files": {
"maxFileSize": 26214400,
"maxTotalSize": 104857600,
"maxFilesPerMessage": 10,
"maxExtractedTextSize": 204800
}
}
FieldTypeDefaultDescription
maxFileSizeinteger (bytes)26 MBMaximum size of a single uploaded file.
maxTotalSizeinteger (bytes)100 MBMaximum total size of all files per message.
maxFilesPerMessageinteger10Maximum number of files per chat message.
maxExtractedTextSizeinteger (bytes)200 KBMaximum extracted text size per file.

NimbleBrain collects anonymized, aggregate usage data with no PII — no bundle names, tool names, error messages, or file paths are sent.

{ "telemetry": { "enabled": false } }

Disable with NB_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1 env vars, or set telemetry.enabled: false as shown above.

The older single-provider model shape is still accepted for backward compatibility but deprecated. Prefer providers for mixing API keys and models for model selection.

{
"model": { "provider": "anthropic", "apiKey": "sk-ant-..." },
"defaultModel": "claude-sonnet-4-6"
}
FieldTypeDescription
provider"anthropic" | "openai" | "google"Default provider when providers isn’t set.
apiKeystringFalls back to the provider’s env var when omitted.

NimbleBrain validates nimblebrain.json at startup against the JSON Schema (draft-07) using AJV.

  • Unknown keys produce a warning on stderr and are ignored.
  • Structural errors (wrong types, missing required fields) throw an error and prevent startup.
  • Workspace-owned and legacy keys (bundles, skillDirs, agents, home, preferences, noDefaultBundles, and the legacy skills) are silently stripped — no warning.
  • The override file (nimblebrain.overrides.json) is validated structurally, but unknown-key warnings are suppressed for it: its set_model_config-written vocabulary (thinking, thinkingBudgetTokens) is not yet in the published schema.
  • Deprecated keys (identity, contextFile) produce a warning pointing at context skills.
[config] Warning: unknown key "foo" in /home/user/.nimblebrain/nimblebrain.json (ignored)
Error: Invalid config in /home/user/.nimblebrain/nimblebrain.json:
- /maxIterations: must be <= 50

CLI flags take precedence over values in the config file:

FlagOverrides
--config <path>Config file location
--workdir <dir>Default location of the config file and workDir fallback
--model <id>defaultModel field (legacy)
--port <n>http.port field
--debugEnables verbose debug event logging

The smallest valid config file:

{
"$schema": "https://schemas.nimblebrain.ai/v1/nimblebrain-config.schema.json",
"version": "1"
}

This uses all defaults: Anthropic provider (key from ANTHROPIC_API_KEY), default model, no MCP bundles installed (platform capabilities are built in), in-memory conversation storage, and structured logging enabled.