Skip to content

Configuration

Three places, each for one kind of value. Nothing personal is ever compiled into the engine.

File Holds Committed?
<vault>/.env Keys and per-machine settings on your laptop No (gitignored)
/etc/secondbrain/env The same, on a server (owned root:<app user>, mode 640) No
<vault>/secondbrain.json Structured, non-secret settings for modules Yes, to your private vault

The app reads <vault>/.env at start (app/server/src/config.ts), validates every value, and refuses to start with a clear message if one is invalid. A value already in the process environment wins over the file.

Core settings

Variable Default Purpose
VAULT_ROOT The folder that holds .secondbrain/ Which vault to index
DATABASE_URL postgres://localhost:5432/secondbrain The index database
PORT 5123 HTTP port (always bound to 127.0.0.1)
LOG_LEVEL info debug, info, warn, error
DEPLOYMENT local local (no login, loopback) or server (login required). See deployment.md
SUMMARIES, SUMMARIES_M1_URL unset SUMMARIES=on makes the deploy script install the summaries timer. SUMMARIES_M1_URL is the home server's Ollama address that the default chain tries first. See summaries.md
B2_KEY_ID, B2_APP_KEY, B2_BUCKET, B2_S3_ENDPOINT unset uploads: the bucket and its S3 endpoint. Presigned URLs are signed with this key; it never reaches the browser
B2STORE_CRYPT_PASSWORD, B2STORE_CRYPT_PASSWORD2 unset uploads: the rclone-crypt passwords when the vault part of the bucket is encrypted (the same ones b2store uses)
UPLOADS_MAX_GB 50 uploads: the largest single file the page accepts
UPLOADS_PULL_TOKEN unset uploads: bearer token (16+ characters) the second machine's puller sends
MODULES empty Optional features, comma separated: ring, agents, projects, org, board, usage, map, tv, voice, livestream, media, social, fund, chat, uploads. Unknown names are ignored
OWNER_NAME owner Your name in the authenticator app and as a comment author
DEV_LOG_PATH, DOCS_PATH unset Vault-relative notes the nav bar links to; unset hides the link
SCAN_INTERVAL_MINUTES 0 Rescan and re-embed every N minutes; 0 means only when asked
INDEXER on off on a machine that uses another machine's database: it never scans or embeds
SECONDBRAIN_SETTINGS <vault>/secondbrain.json Where the structured settings live

Search and the database page

Variable Default Purpose
TOGETHER_API_KEY unset Embeddings for semantic search, and the database page's question-to-SQL box. Without it, semantic search falls back to Ollama or returns 503; keyword search always works
OLLAMA_URL http://127.0.0.1:11434 Local embeddings, when the embeddings source's provider is ollama
SQL_MODEL a Together-hosted model The model that writes SQL for the database page. Queries run read-only

Chat providers

Used by the chat module and by the embedder's optional contextual-enrichment step. A provider is offered in the app only when its key is set; which models each one lists comes from app/config/llm-presets.json plus the vault's chat.providers (below).

Variable Default Purpose
ANTHROPIC_API_KEY unset Anthropic (Claude)
OPENAI_API_KEY unset OpenAI
TOGETHER_API_KEY unset Together AI (shared with embeddings and the database page)
OPENROUTER_API_KEY unset OpenRouter
OLLAMA_URL http://127.0.0.1:11434 A local Ollama needs no key and is always offered
OLLAMA_MACHINES unset Connected computers that run Ollama: comma-separated name=url pairs, e.g. box1=http://192.0.2.10:11434,box2=http://192.0.2.11:11434. See below
RERANK_URL, RERANK_API_KEY unset A Cohere-compatible /rerank endpoint (Together, Cohere, Jina) for the chat's optional reranking step. Unset: the switch is off
RERANK_MODEL Salesforce/Llama-Rank-V1 The reranker model at that endpoint

Connected computers. Each name=url pair in OLLAMA_MACHINES adds a chat provider with id ollama-<name> and label Ollama ยท <name>. The name is [a-z0-9][a-z0-9_-]*; the url is the machine's Ollama base, with no /v1. The models are not listed in any file: the app asks the machine (GET <url>/api/tags, 2.5 s timeout) each time the settings modal opens, cached for about 20 s, and offers every model except embedding models (BERT family, or "embed" in the name). The first model is the default. A machine that does not answer stays in the list as (offline) and cannot be picked; it never slows or breaks the rest. Invalid pairs are skipped with one warning at start. The single-host ollama provider (OLLAMA_URL) is unchanged. The zen-agent harness reads the same variable the same way.

The Embeddings source's config (Sources page) takes contextualize (boolean), context_provider and context_model for the enrichment step; see rag.md.

Server and login

Required when DEPLOYMENT=server; the app refuses to start without them.

Variable Purpose
PUBLIC_ORIGIN The browser-visible origin, for example https://brain.example.com. Every mutating request must carry it as Origin
AUTH_PASSWORD_HASH, AUTH_TOTP_SECRET Printed by secondbrain auth-setup, which you run on your own machine
B2_SERVE_URL Optional: a local b2store serve for big files kept only in object storage

Deploy settings (DEPLOY_*) are read by deploy.sh only: see deployment.md.

Using another machine's database

Variable Where Purpose
SECONDBRAIN_DB_URL Vault .env The launcher connects to this database instead of a local one, and starts the app with INDEXER=off
SECONDBRAIN_DB=local Environment Force the local database for one run

Module settings

Variable Module Default Purpose
AGENT_HUB_URL, AGENT_HUB_TOKEN agents unset The agent-hub service that serves Claude chats and the Telegram toggle. Unset: those tabs say so
ZEN_AGENT_URL, ZEN_AGENT_TOKEN agents unset The zen-agent serve process behind /api/zen (the zen-agent chat page). The token is the serve process's ZEN_SERVE_TOKEN. Unset: the page says so
HERMES_HOME agents, archive ~/.hermes Hermes's home on this machine
ARCHIVE_SSH_HOST, REMOTE_HERMES_HOME, REMOTE_HERMES_USER archive unset Pull a server's Hermes history over ssh; unset skips the pull
SB_LEDGER_DIR, SB_LEDGER_TOKEN agent ledger (on each machine, not the server) the OS data folder, the token in ledger.json Where a machine's agent ledger keeps ledger.db and ledger.json, and a token that overrides the stored one. See the app README, "Agent ledger"
PROJECTS_FEED_TOKEN projects unset Read-only bearer token for GET /api/feed/projects (a kiosk panel). Unset: the route doesn't exist. deploy.sh generates one
KNOWLEDGE_TOKEN any unset Read-only bearer token for an agent on the same machine (zen-agent's search_notes, search_everything, read_note). Opens GET /api/search, /api/search/global and /api/file/source and nothing else, only from loopback and never through a proxy (a request with a forwarding header, or a Host that is not 127.0.0.1, localhost or [::1], is refused). At least 32 characters; openssl rand -hex 24. Unset: the token does nothing
ORG_OWNER org owner Your handle on the agent board (lowercase, a-z0-9-)
ORG_VAULT_ASSIGNEE org vault-session The kanban assignee for vault work: a Claude Code session on your machine that no dispatcher spawns (lowercase, a-z0-9-). Cards on boards that still use the old slug m4 keep working
ORG_SESSION_MAX_RUNS org 3 Runs one remembered claude-code agent session carries before the next wake starts fresh. 1 never resumes. Not used by the zen-agent lane, whose wakes are always fresh
BOARD_LEASE_MINUTES board 60 How long a claimed card stays a lane's without a heartbeat before the board-reclaim sweep puts it back to ready
BOARD_URL, BOARD_TOKEN board unset On a machine that is not the server: cli-board and agents reach the board over HTTPS as the roster member the token names (cli-board token create <agent>). Unset: the database connection is used
ORG_TASK_WAKE_RETRIES, ORG_TASK_WAKE_BACKOFF_MINUTES org 2, 15 A card that keeps waking its bot waits 15, then 30 minutes between wakes, and after 2 re-wakes in 24 h is held until a person wakes it (wake-add --reason owner)
ORG_TZ org UTC The local day for "one routine wakeup per day" and the dispatcher's quiet hours (an IANA zone name)
ORG_DISPATCH org off on: the engine's dispatcher runs bots whose roster runtime is zen-agent (secondbrain org-dispatch; a timer on the server). off: wakeups are left to your own dispatcher
ORG_DISPATCH_TICK_SECONDS, ORG_DISPATCH_PARALLEL org 120, 2 How often the dispatcher looks, and how many bots run at once
ORG_DISPATCH_QUIET org unset Local hours with no wakes, HH-HH (e.g. 01-07)
ORG_DISPATCH_DAILY_TOKENS org 0 (off) Tokens all bots together may spend in one local day before the rest of the day's wakes are held
ORG_ZEN_COMMAND org zen-agent The zen-agent command the dispatcher runs (on the PATH, or absolute)
ZEN_SESSIONS_DIR usage <vault>/.zen/sessions Where zen-agent writes its traces; usage-sync reads them into the ledger
CLASSIFY_PROVIDER org none The classify step's provider: openrouter (Jev through OpenRouter with OPENROUTER_API_KEY) or typesafe (with TYPESAFE_API_KEY). none: every decision falls through to the old path
CLASSIFY_DAILY_TOKENS org 2000000 Classifier input tokens per day; above it, decisions fall through
TYPESAFE_API_KEY org unset A direct TypeSafe key, when CLASSIFY_PROVIDER=typesafe
RING_PULL_SSH_HOST, RING_PULL_DB ring unset, secondbrain Mirror a server's ring tables over ssh
TTS_URL, TTS_MODEL voice http://127.0.0.1:8880, a Kokoro model A local OpenAI-compatible speech server. Never used on a server
ROBOT_LAUNCH_SCRIPT none (local only) unset A script the "start robot" button runs. Unset: the route isn't mounted

Vault sync

Per-machine settings for secondbrain-vault sync (sync.md); the shared policy lives in secondbrain.json, below.

Variable Default Purpose
SYNC_ROLE owner owner (you edit here; a conflict stops and alerts) or agent (a server; a conflict keeps both versions)
SYNC_MACHINE the hostname Name in sync commit messages and conflict copies
SYNC_INTERVAL_MIN 60 Minutes between runs, used by sync install
SYNC_CONFLICTS by role stop or both
SYNC_ALERT desktop desktop, log, or an https:// webhook URL
SYNC_STATE_DIR ~/.local/state/secondbrain Where sync.json and sync.log are written and where the app reads the sync chip from

Agent hooks

Variable Purpose
DEV_LOG_INDEX The README the dev-log gate tells agents to link new entries from
SB_GUARD_LOCAL An explicit path to guard.local.json

secondbrain.json

Settings that don't fit on one env line. Every section is optional; a file that doesn't parse is reported and ignored, and the app uses empty defaults. vault-template/secondbrain.example.json has one of each.

Key Module Holds
goalsFile projects, map Your goals note: markdown tables of \| W1 \| Goal \| Measure \| Due \| Status \|
missionFile map Your mission note
map.divisionFolders map { "division": ["folder", ...] }: which folders belong to which division when no project claims them
map.missionFolders map Notes that sit at the centre of the map
metrics[] map Numbers the map tracks. Each has key, label, unit (followers, subscribers, hours, usd, count), optional goal, target, division, headline, and either collect (type youtube, x, instagram or base-balance) or sum (a list of other metric keys)
tv.episodesDir tv Where episodes live in the vault (default tv/episodes)
tv.shows tv { "slug": "Show name" }
livestream.youtube, .twitch, .kick livestream Your channel handle on each platform
fund.dir, fund.txService, fund.explorer fund Vault folder of the fund's state (default fund), the Safe Transaction Service URL and the block explorer for its chain (defaults: Base)
projects.reportFile projects Where cli-projects render writes the markdown report (default projects.md)
summaries.chain, summaries.minSteps, summaries.quietMinutes, summaries.maxInputChars usage Which providers write session summaries and in what order, the shortest session worth summarizing, how long a session must be quiet, and how much transcript is sent. Defaults in app/config/summaries.json; see summaries.md
chat.defaultProvider chat The preset provider the chat starts on, when its key is set
chat.systemPrompt chat Replaces the engine's base system prompt (still editable in the app's settings modal)
chat.providers.<id> chat Per preset provider: models to add ([{ "id", "label" }]), another default model, another baseUrl
sync.remote, sync.branch vault sync What every machine syncs with (default origin, main)
sync.agentOwned vault sync Files only agent-role machines commit, e.g. a rendered report
sync.embeddedRepos, sync.maxFileMB, sync.settleSeconds, sync.heartbeat vault sync ignore or record nested repos' pointers; largest file to commit (20); recent-write wait (90); write .sync/<machine>.json with each commit (off). See sync.md
classify.allow org Channel slugs and vault folder prefixes whose text may be sent to the classifier. Empty (the default): every decision falls through and nothing leaves the machine
classify.model org provider/model for decisions whose schema pins none (default openrouter/typesafe/jev-1.14)
classify.schemasDir org Vault folder of decision schemas, <decision>.json (default .agents/classify); the engine's defaults fill in what is missing
dispatch.tiers org { "flash-low" \| "flash-high" \| "mid" \| "strong": { "model", "thinking" } }: what the card-tier decision routes to. Unset tiers use zen-agent's own model
dispatch.budgets org Per wake reason (dm, mention, handoff, task, routine, owner): { "turns", "tokens", "minutes" }
dispatch.tools org Per wake reason: zen-agent tool sets (fs, shell, skills, knowledge, kanban, org, subagent)

Example:

{
  "goalsFile": "Areas/goals.md",
  "map": { "divisionFolders": { "writing": ["Areas/blog"] } },
  "tv": { "shows": { "weekly": "The weekly report" } },
  "livestream": { "youtube": "example" }
}