This is the shared compatibility policy for inputs a user deliberately writes or supplies: brain configuration, documented environment settings, per-module JSON settings and canonical module content. Field references remain with their owners. Their documented meaning, validation, precedence and migration rules are part of the integration contract, even when a field is described in a linked module reference rather than repeated there.
The decision records the maintainer's boundary. This reference follows repository source; consult the installed package's changelog for release availability.
A supported input includes its accepted representation, units, interpretation, validation and failure behavior, precedence over other sources, omission/default behavior and promised preservation during edits or migration. A loader accepting arbitrary data does not make every such value a supported format. Undocumented implementation details are internal; an existing documented promise cannot be withdrawn merely by relabeling it internal.
Apply the existing contract versioning rules: additions ship in a minor;
removing, renaming, retyping or changing the meaning of a supported input is a
break. Before 1.0 a break requires a prior maintainer ruling on its issue, the
breaking label and a minor changeset naming the break; from 1.0 it requires a
major. Contract changes use CONTRACT: and update the integration contract in
the same commit. An explicit migration explains what changes, preserves
unrelated source and data, and documents any refusal or manual recovery path.
Opening an editor or loading a configuration is not permission to migrate it.
Some omitted values deliberately select a curated policy, such as the jobs default-enabled boards or images' documented model selection. Such policies may evolve under their explicit owner policy; they do not promise one permanent catalog. This is not a blanket exemption for default changes. Assess each change against its documented promise, including validation of explicit selections, opt-in behavior and any affected migration. An explicit user value must retain its documented precedence.
Module authors own their domain schemas and canonical field references and link
them to this policy. They classify a proposed change before implementation.
The separately governed experimental module-authoring API does not make a user's
supported files experimental merely because a module reads them. Likewise the
published ENV_VARS descriptor API and the meanings of environment inputs are
separate compatibility questions; export curation does not waive input promises.
context/policies/governance.md is versioned host input, excluded from the
content index and read-only to workers. Its UTF-8 YAML frontmatter must have
schema_version: 1; its Markdown body has no policy meaning. The data-only
reader accepts at most 1 MiB, refuses linked ancestors, symlinks, hardlinks and
nonregular files, and does not evaluate brain configuration. The entire file's
exact bytes, including prose and line endings, determine its lowercase SHA-256
content hash. A prose edit changes identity without changing policy semantics.
All objects reject unknown keys. Omitted fields resolve to the defaults below; explicit fields replace their corresponding default, while nested budget/profile objects merge per field. No value grants authority to autonomous turns.
| Family | Accepted fields | Meaning and defaults |
|---|---|---|
profiles |
interactive, voice: governed or strict |
Both default to governed. A host's explicit strict override remains stronger. True voice uses voice; dictation uses interactive. |
protect |
Array of canonical brain-relative paths | Adds protection. No absolute, home, drive, backslash, empty/dot/parent segment or control-character forms. Default []. |
release |
Array of exact built-in tier B paths | Default []; tier A and .env* cannot be released. See the protected tiers. |
budgets |
turn, session, day, each with optional warn_usd, block_usd, warn_tokens, block_tokens |
USD amounts are finite/nonnegative; tokens are nonnegative safe integers. Turn warn/block $100/$200 with 512,000,000 fallback tokens; session/day $2,000/$4,000 with 8,000,000,000 fallback tokens. Day is UTC calendar day; session is retained lifetime. |
scope |
max_files_changed, max_minutes, max_tool_calls, max_bytes_written |
Nonnegative safe integers, except finite minutes. Defaults: 1,000 paths, 120 minutes, 500 calls, 4 GiB. Path/byte defaults are provisional approved values, not measured distributions. |
commands |
deny, confirm: arrays of { pattern: string, reason?: string } |
Additions to existing permissions, default []. Regex sources preserve their literal anchors and compile case-insensitively under confirmation rules. Mixed lists retain valid entries in order and report skipped sources; nonempty entirely invalid lists refuse. |
egress |
mode: record/allowlist; allow, deny: string arrays; max_bytes_per_turn: nonnegative safe integer |
record, empty lists and no byte cap by default. This reader does not establish network enforcement. |
outward |
allow: array of { command: string, remotes: string[] }; hosts: string array |
Default empty additions to the existing outward-action rules and built-in destination list. Parsing grants no action. |
taint |
egress: { allow: string[] } |
Default empty additions; session-derived hosts/inference relay and taint enforcement remain their owning guardrail's responsibility. Validated provider-relay requests are outside taint narrowing and create no taint (#3246); this list never needs a provider host for them. |
providers |
Array of { paths?: string[], types?: string[], allow: string[] } |
Default []. At least one selector is present. Paths use canonical brain-relative Bun glob patterns; types are exact runtime taxonomy names. Either selector matches; every matching rule must allow the actual recipient. Unknown recipients cannot authorize matching content. |
apify |
enabled: boolean; discovery, actors: deny, ask or allow; actor_grants: array of { account_id, actor_id, build_number, profile_id, schema_sha256, pricing_sha256, origins, expires_at, max_run_usd } (at most 64); limits: { run_usd, turn_usd, session_usd, day_usd, max_runs_per_turn, max_requests_per_turn, max_concurrent_runs, max_runtime_seconds, max_result_items, max_result_bytes, max_retention_seconds } |
Default { enabled: false, discovery: ask, actors: ask, actor_grants: [], limits: null } (additive, #3159, #3160 and #3161). Only enabled: true lets the server use its host-only APIFY_TOKEN; until then it contacts no Apify endpoint, whatever credential exists. An invalid policy file disables Apify rather than keeping an earlier enablement. discovery governs catalog searches and Actor inspection, which send the query or the chosen Actor's identifier to Apify; it never permits a run. actors: ask reviews every run, once; deny refuses every run; allow skips the review only for a run an unexpired grant matches exactly: the bound account, the immutable Actor id (not its name), an exact build_number such as 1.4.2 (not a tag), the qualified profile_id, the lowercase hex SHA-256 of the build's canonical input schema and of the Actor's price list, every https target origin the input names, and a run cap no larger than max_run_usd (positive, at most 1,000,000). Any other run is reviewed. expires_at is an ISO 8601 instant. An approved run never adds a grant; grants change only through a policy edit. limits is the only spending entitlement: without it no run is admitted. With it, every field is required. Amounts are USD from 0 to 1,000,000. Counts are whole numbers up to 1,000,000. max_runtime_seconds is at most 7 days, max_result_items at most 1,000,000, max_result_bytes at most 1 GiB and max_retention_seconds at most 30 days. Zero refuses what it bounds. run_usd bounds a run's whole reservation: its cap plus its reserved lifecycle requests and remote storage. turn_usd, session_usd and day_usd count pending reservations as spent and apply alongside, never above, budgets. day_usd and max_concurrent_runs are account-wide across brains. max_requests_per_turn counts every Apify request in a turn. Results stay readable for max_retention_seconds after a run ends, which never shortens Apify's own storage retention. See the Apify contract. |
The budget and scope rulings supply these defaults. The reader selects profiles and protected paths; budget, scope, command, egress, outward, taint and provider enforcement belongs to the corresponding guardrails and is not implied by a valid check.
The same captured protection governs authoritative agent Markdown applications,
planner-derived capture/archive destinations and submitted PNG masks. Protected
targets, directory descendants and ancestor replacement refuse as
policy_denied without partial effects; permitted reads and existing approvals
remain intact. A mask's scratch cleanup also retains protected files, directories
and ancestors. Neither a later session nor a new policy load can replace an
already admitted application's protection (#1743).
Conversation inbox staging (inbox_add) and its compensation, and an
autonomous turn's exactly approved application, refuse protected targets the
same way, with the protection of the policy that turn was admitted under; an
approved autonomous write to a protected path no longer applies (#2876). A
protected path that is itself a link, or a .env* name that is one, also
protects where it leads, and a mask's scratch prune retains those places. A
target reached through a link keeps the application route's alias_denied
refusal; inbox staging, which follows links, refuses it as protected.
At every interactive/voice admission, the host reads the policy as data and captures an immutable effective policy/hash for that turn. Invalid edits record their validation notice and retain the process's last valid policy/hash; before any valid file, they use built-in defaults with a null effective hash. A missing optional file selects defaults without an error. A running turn retains its captured identity; a later load cannot mutate it. Policy edits remain outside turns or use the separately approved proposal route, never a worker write.
Literal recursive folder selectors such as me/** also retain folder topology
for newly admitted hosted writers. The prefix must contain no glob operators;
this step does not interpret exact document types or arbitrary glob selectors.
A shared writer inherits the retained folder roots of active peers, even if a
later policy removes them. A governed worker read-write binds each existing,
unaliased root and every ancestor before its ordinary protected mounts. Files
inside and permitted siblings stay writable; root and ancestor renames fail.
Initial inspection of an absent, linked, special or unverifiable folder selects
strict fallback without creating a placeholder. A changed inspected identity
refuses launch. Folder mount qualification stays private to the resolver and binds
the frozen layout to its exact brain identity; copied or caller-constructed
layouts cannot authorize retained-folder mounts. The live governed launcher also
checks each retained root/ancestor mount point and its exact inode in the
authenticated worker namespace before opening the payload gate. A changed or
unobservable view refuses launch. This requires host access to the held worker
namespace; the current separate-UID broker only attests runtime-state/temp roots,
so it cannot qualify retained provider folders. Such launches refuse until that
concrete boundary is qualified; ordinary separate-UID launches without retained
folders retain their existing behavior. Native output publication refuses staged removal/replacement of
admitted roots or ancestors through strict-area, maintenance and local sync.
Before admitting a governed live writer with retained folders, the gate refuses an active peer with an unaccounted plan or a plan missing a required root pin. A peer's descendant root also retains its ancestors; retaining an ancestor alone does not retain each child inside it. An unknown peer-plan checker refuses too. A peer missing a root waits rather than refusing, for up to 30 seconds: every writer admitted after the turn retained its folders inherits them, so only an older one can lack them, such as a writer admitted before the first turn under the current policy translated a type selector. The server records each translation in its UI database, so writers in the cron runner and after a restart plan those folders too. A peer still active at that bound refuses. This check does not stop an older writer and does not block nested shared maintenance, explicitly strict turns or strict inspection fallback.
The gate stores planned root sets, not proof of mounted namespace identities; legacy rows remain unaccounted. These primitives do not activate inference-worker routing, masks, filtered indexes or server-tool refusals. They do not establish that a pre-existing writer or a concurrent policy change is safe for a protected reader. A future privacy admission must qualify all those boundaries and refuse unsupported unsafe selectors. Arbitrary type/glob requirements remain open; folder retention cannot authorize protected data for an unknown recipient.
Hosted turns (additive in 0.44.0, #1668). A provider rule denies every
interactive and voice turn whose recipient it does not allow, every turn
without a proven recipient, and every autonomous turn. Each literal
folder/** root of a denying rule present in the turn's view reads as an
empty read-only directory, a selected folder inside another selected folder
is hidden with it (#2718), .git is hidden, and .brain-index/ is a
turn-private, writable index. It is built before the turn by the brain's own
CLI (brain index, keyword-only) in a worker that sees the same masks, so it
never held a masked document. A hosted turn reuses an earlier turn's build
when it provably equals a fresh one (#2833): the server keeps one copy per
policy, mask set and recipient, compares the turn's checkpoint with the one
that copy was built from, and adds new Markdown and asset files outside the
masks with the CLI's incremental index. Any other change, including a
document edited, removed or moved into a masked folder, builds the index from
nothing again, as does the first such turn after the server starts. After
each turn's index refresh the server brings that copy up to the live brain
off the turn path, adding new notes or rebuilding it after a document
changed, so the next turn usually reuses it as it is. Changes
inside .brain/ and .brain-ui/ do not, because the build sees those
folders as empty; a brain.config.ts that imports from them does not load in
that build. The real index is refreshed from Markdown after
the turn as usual. Host bridge tools that run on the server, outside the
worker's mounts, refuse every path at or below a masked root with the named
refusal provider_masked and one provider_mask_refusal governance event per
call: brain_read_base, the application tools, share completion, image masks,
inbox_add and policy proposals (#2690). query_activity, inbox_list and
inbox_get read the server's own records, which keep text from earlier runs;
such a turn is refused them whole with the same provider_masked refusal and
no event (#2698). An autonomous (restricted) turn applies the same
masks and private index to its envelope and reads the same last-valid policy.
It translates selectors as below and refuses the rest the same way: an
unsupported path selector before any worker starts, an untranslatable type
once the brain's taxonomy has been read, a hardlink out of a masked folder as
for hosted turns. Its bridge refuses masked paths the same way (#2691).
It also starts only when host provenance shows its origin inbox thread was
written without the masked folders, and its inbox_list and inbox_get
revalidate that; a legacy thread, an item a conversation added without a
born-masked lineage that hid those folders (#2816), or a thread changed
under a new policy is refused with provider_masked and the queued work
stays stored (#2741). In
every denied turn the committed generated-text caches read as empty:
.brain-cache/ is an empty read-only folder (#2725), and any root-level
.context-cache.jsonl or .asset-cache.jsonl an earlier release left reads
as empty (#2692). The same holds in every turn allowed only some provider
folders; a turn allowed every one sees .brain-cache/ (below).
Allowed recipients (additive in 0.44.0, #2695). A turn's recipient is the provider that receives its inference, named only from the exact origin its host inference relay forwards to. That relay runs for governed and strict Claude and pi turns on managed native storage; any other turn has no proven recipient. These origins name a recipient:
| Origin | Recipient |
|---|---|
https://api.anthropic.com |
anthropic |
https://api.openai.com, https://chatgpt.com (pi's openai-codex) |
openai |
https://generativelanguage.googleapis.com |
google |
https://api.x.ai |
xai |
https://api.mistral.ai |
mistral |
https://api.deepseek.com |
deepseek |
https://api.groq.com |
groq |
https://api.cerebras.ai |
cerebras |
https://api.fireworks.ai |
fireworks |
https://api.together.ai |
together |
https://api.moonshot.ai, https://api.moonshot.cn, https://api.kimi.com |
moonshotai |
https://api.minimax.io, https://api.minimaxi.com |
minimax |
https://api.z.ai, https://open.bigmodel.cn |
zai |
An origin the server's BRAIN_UI_PROVIDER_RECIPIENT_ORIGINS names |
That name |
Any other origin is an unknown recipient, which no rule allows. That includes
a custom baseUrl or ANTHROPIC_BASE_URL, an origin listed only so a
credential may reach it (BRAIN_UI_CLAUDE_OAUTH_ORIGINS,
BRAIN_UI_PI_CREDENTIAL_ORIGINS), routers such as OpenRouter, and clouds that
host several vendors' models, such as Bedrock or Vertex. A model name, vendor
label or cost route never names a recipient. An operator who runs a trusted
gateway names it with BRAIN_UI_PROVIDER_RECIPIENT_ORIGINS
(name=origin[,origin];name=origin, for example
anthropic=https://gateway.ithaca.invalid). A built-in origin cannot be given
another name, and a malformed entry refuses the server's boot.
Matching rules intersect their allowlists. A turn allowed every provider
folder sees them all, .git, the real .brain-index/ and .brain-cache/;
root-level caches from an earlier release still read as empty. A turn allowed some
of them sees those, and the rest are masked as above; its private index is
built from that partial view, so it lacks only the masked folders' documents.
A folder that a denying rule selects inside a folder the recipient may see
masks that outer folder whole: only outermost provider folders are kept in
place against other writers (#2718), so a mask nested inside a visible folder
would not survive a peer replacing it. The turn's relay then refuses to reach any origin but the one its
recipient was named from.
A conversation keeps what its turns read: provider history is never redacted.
So the server records which recipient each turn showed which provider
folders. A later turn of the same conversation whose recipient the current
policy does not allow those folders is refused before it starts, with
ProviderRecipientRefusedError naming the folders and the earlier recipient.
That covers a profile pin that fell back to another profile, a changed route
and a pi model change. A handoff carries its source's record into the new
conversation, and a handoff summary is not drafted for a conversation that
showed provider folders; write it by hand instead. A new conversation starts
without a record. The record covers folders under a provider rule when the
turn ran: content a turn read before a rule selected its folder is not
tracked.
The rule covers the inference recipient only. What a turn's tools send over
the network, including the brain CLI's own provider calls such as search
embeddings, is egress and follows the egress policy (egress, outward),
not providers. A turn allowed every provider folder still carries the
provider masks, empty. So whatever is hidden from every turn under a provider
rule, such as the generated-text caches (#2692), stays hidden from it too,
until those caches move into a masked directory (#2725).
Which selectors hosted turns enforce (additive in 0.44.0, #2694). A
selector is enforced only when it maps exactly onto whole folders; the turn
then masks those folders as above. Every other selector refuses turn admission
with ProviderPolicyRefusedError, which names it and says why, so nothing it
selects is left silently readable.
| Selector | Hosted turns |
|---|---|
paths: [folder/**] or [folder/**/*] with a literal folder |
Masks the folder. The two spellings match the same files. |
Any other path pattern: me/*.md, **/oath/**, {a,b}/**, a single path such as me/identity.md, or a .git or .brain-index folder |
Refused. |
types: [type] whose folders map exactly |
Masks the type's folders. |
Any other types entry |
Refused, with the reason. |
A type's folders are its effective match prefixes, or [dir] without
match, as brain config check --json reports them. They map exactly when
all of these hold at admission:
- The type is defined, it is not the inbox type (which also takes every path outside the other types' prefixes), and it has at least one prefix.
- Each prefix is a literal folder a turn can hide: no glob characters, and
not
.gitor.brain-indexor inside them. - No other type's prefix equals or lies inside one of them, so path inference gives no other type within them.
- No Markdown file outside the turn's masked folders declares the type in
frontmatter. The check reads every
.mdfile the turn could see, hidden and excluded folders andnode_modulesincluded; only.gitand.brain-index/are skipped. A.mdsymbolic link counts as the file it resolves to, unless that file is in a masked folder. A link to a folder outside the brain is not followed and refuses the type as unverifiable. A file whose frontmatter does not parse declares no type, as for the indexer.
For example, the core identity type (dir: me) masks me/ as long as every
type: identity document lives there. A note rule refuses, because note
is the inbox type; so does an index rule, because index has no folder.
Files of other types that sit inside a masked folder are hidden with it.
The taxonomy comes from brain.config.ts, which the host never runs: the
brain's own CLI reports it from a credential-free worker that sees the
literal-folder masks, and anything that worker writes is discarded. Assets
(images, PDFs) take their type from their path under that configuration, so
the reported folders decide which assets are hidden. Markdown documents stay
covered by the frontmatter check whatever the taxonomy reports. Admission
therefore runs that worker and reads every Markdown file outside the masked
folders, on top of the private index build. When admission takes a newer
checkpoint of the brain before the turn starts, it judges the types again on
that checkpoint.
Protected taxonomy code (breaking in 0.44.0, #2699). Because the
reported folders decide which assets are hidden, a type selector translates
only when no governed turn could have changed the code the taxonomy runs.
The taxonomy worker sees only the policy's protected paths: the built-in
tiers (brain.config.ts, brain.config.json, package.json, bun.lock,
node_modules, bunfig.toml, context/policies and the rest), minus what
the policy releases, plus its protect list. The rest of the brain, .git
included, is absent from that worker, and so are .brain/ and .brain-ui/,
which the server writes for turns, even when protected. Every type selector
of the policy
refuses with ProviderPolicyRefusedError, naming the reason, when:
- the policy releases
brain.config.ts,brain.config.json,package.json,bun.lockornode_modules; - the CLI that reports the taxonomy is a brain file outside the protected
paths, such as the brain's own CLI fallback script, which a brain without
a packaged CLI (
node_modules/.bin/brain) runs; - the configuration does not load from the protected paths alone, for
example because it imports a helper file, loads a local module
(
./<folder>inmodules), reads atsconfig.jsonpath alias or reads any other file outside them. The refusal quotes the configuration's own error, which names the file; - the taxonomy it reports differs from the one read with the rest of the brain visible too, because the configuration reads or probes a file outside the protected paths. Both reads run on the same checkpoint, and only the protected one is used, so the masks never depend on a file a turn can change even when code such a file holds makes the two reads agree;
- a protected name, such as
brain.config.tsbeside abrain.config.json, was created by a governed turn that is still running, or has ended since admission began, and has not been reconciled. A protected name that does not exist is not mounted, so a turn can create it; its end moves the new file out of the brain; - the configured brain path is not its canonical path (it is reached through a symbolic link);
- a protected path, or a folder on the way to it, is a symbolic link, is a hardlinked file, or is neither a file nor a folder in the turn's view.
To keep translating, add each such file or folder to the policy's protect
list, for example protect: [modules, lib/odysseus-types.ts]. A governed turn
then cannot change it either. A configuration that imports only installed
packages, as the template's does, needs nothing. A brain that has only the
fallback script still refuses after protecting it, because that script
cannot yet start in the taxonomy worker; install the packaged CLI instead.
Protection is judged against
the policy in force at admission: a file that an earlier policy left
unprotected keeps whatever a turn wrote to it then, so review it before
protecting it. Code outside the brain, which no turn can write, still loads.
Before 0.44.0 such configurations translated, trusting code a governed turn
could rewrite; the maintainer approved this restriction on #2699.
Hardlinks out of masked folders (additive in 0.44.0, #2700). A folder
mask hides names, not the file itself. Admission therefore refuses with
ProviderPolicyRefusedError when a regular file outside the masked folders
is a hardlink to a file inside one (same device and inode), whatever it is
called, including names that are not valid UTF-8. The refusal names up to
three such files, each with the masked file it shares, and counts the rest.
Remove those hardlinks and retry. This applies to literal folders and to
translated type folders alike. Symbolic links are not hardlinks and are not
followed; the turn resolves them where the target is masked. A copy is a
different file and is not detected.
The check reads metadata only, never contents. It looks at every file in the
masked folders, and walks the rest of the brain only if a file there has more
names than the masked folders hold. Hidden folders and node_modules count;
only the root .git and .brain-index/ are skipped. It checks the
checkpoint the turn's private index is built from and, for a governed turn,
the live brain as well.
These checks apply to the brain as admission sees it. A document that the turn or a peer creates or retypes to a selected type outside its folders during the turn stays readable to that turn; the next admission refuses until it is moved into the type's folders or retyped. The same holds for a hardlink a peer creates in the live brain after the check, during admission or the turn.
Core indexing applies these selectors to embedding eligibility without removing
keyword documents, chunks or links. A changed restriction reconciles existing
vectors even without --embeddings; force rebuilds, cache reuse and asynchronous
vector writes retain the same check. With provider rules present, hardlinked,
symlinked, escaped or unreadable source paths cannot borrow an unrestricted
spelling to authorize provider work. Keyword indexing remains available for
these sources; coverage uses the eligible denominator.
The concrete built-in Gemini embedding factory recognizes recipient google
only when its actual installed SDK client resolves to the HTTPS Google
Generative Language endpoint. Provider/model labels, custom implementations,
Vertex/platform overrides and other endpoints do not establish that identity.
A constructed client retains its resolved route when the ambient environment
later changes; a new client is checked independently.
An embedding recipient does not authorize a separate completion or vision provider. Matching documents use local summary/context fallback, and matching assets retain keyword placeholders or cached descriptions rather than sending bytes to an unproven enrichment route. An invalid policy leaves keyword indexing available, disables embedding/enrichment requests and reconciles all vectors as ineligible, with a named diagnostic. This does not redact text already copied into unrelated documents or establish worker, history or server-tool isolation. Policy check warns that affected turns lose Git history; the warning is not proof that worker masks or a relay are active.
brain policy check --json is read-only, does not execute brain.config.ts,
and reports the documented envelope.
It checks the current file independently of any server's last-valid cache.
brain.config.ts default-exports its config object; defineConfig supplies
typing, while the loader validates at runtime. brain.config.json uses the same
schema and is read only when the TypeScript file is absent. The files are not
merged. TypeScript can carry provider/runner implementations; JSON cannot carry
functions. A missing file returns no user config, and {} is valid. An invalid
selected file fails rather than falling back to another file or to defaults.
Imports are process-cached: start a fresh process to reload TypeScript edits.
All top-level families are listed below. Their nested field types, constraints, effective defaults and examples live in configuration.md. Omission preserves source omission; core effective defaults are applied by the consumer rather than written into the user's file.
| Family | Supported fields and meaning | Omission and validation |
|---|---|---|
profile |
name, cliTitle: cosmetic CLI identity. |
Optional strings; strict object. |
taxonomy |
types, dirAnchors, canonical, canonicalPolicy, propagation, facts, assetTitleRules, classifierHints, defaultStaleness, tags. |
Core defaults, then module contributions, then user overrides; field-specific merge rules in concepts. Type names are validated strings, not a fixed union. |
exclude |
dirs, files, segments: indexing exclusions. |
Adds to core/module exclusions; exact file paths cannot end in /. See exclusions. |
geo |
Canonical service, eligibility, endpoint, cache and resource configuration. | Absent leaves new services disabled; strict shared schema and contained relative cacheDir. See geo configuration. |
embeddings |
provider, model, apiKeyEnv, dimensions. |
Omitted block resolves the Gemini built-in; missing key leaves vector search unavailable. A present block requires provider; dimensions are positive integers. |
completions |
provider, fallback, apiKeyEnv, fallbackApiKeyEnv. |
Omitted block resolves the Gemini Flash built-in; a present block requires provider. Fallback is used when primary throws. Explicit key-variable names are nonempty strings. |
agentRunner |
Built-in name or passed-in runner. | Claude by default; registry resolution rejects unknown names. |
reranker |
enabled, provider, model, apiKeyEnv, exclude, timeoutMs, depth, skipMargin. |
Model judgment is off unless enabled: true; credentials and mode overrides cannot enable it. Positive integer timeout, depth at least two, nonnegative margin. See activation. |
graph |
root: document from which graph distances are measured. |
Root defaults to AGENTS.md, then CLAUDE.md; contained relative path. This does not enable a search-ranking boost. |
skills |
emitters: extra skill emitters. |
Claude always runs; extras are optional string names. |
stats |
coverageFloor, brokenLinkCeiling: health warning ratios. |
Defaults 0.9 and 0.05; values in 0–1. |
media |
maxTrackedBytes, track, ignore: sync/doctor media policy. |
Defaults 5 MiB and empty lists; positive integer byte limit and nonempty glob strings. |
hooks |
embedOnCommit: background paid enrichment opt-in. |
Off by default; boolean only. |
search |
language: full-text tokenizer and stopwords. |
english by default, or none; changing it rebuilds FTS without re-embedding. |
instructions |
maxTokens: doctor instruction-weight warning budget. |
8000 estimated tokens; positive integer. |
sync |
pull, judge: pull strategy and bounded judgments. |
See sync defaults; enums rebase/merge and jev/off. Missing judgment credentials use conservative decisions. |
modules |
Package name or ./ local module path → domain config, with core-owned enabled. |
Config order is load order; keys must be package specifiers or contained relative paths. Domain validation belongs to each manifest. enabled defaults true and must be boolean. |
Core objects reject unknown fields. Path constraints apply where the schema uses
the repository-relative path validator: no absolute/home/backslash forms, ..
segments or control characters. Do not generalize this to every string field:
for example travelParty[].requirementsDoc is presently an optional string.
Taxonomy constraints and downstream checks remain as documented in their owners.
Root selection is explicit argument → BRAIN_ROOT → nearest ancestor containing
a config file → nearest ancestor containing .git → current directory. Environment
does not generically overwrite brain-config keys: only the documented overrides
do, such as CLI --rerank → BRAIN_RERANK_MODE → configured reranker mode,
all subject to the persistent activation gate.
BRAIN_UI_WORKER_IDENTITY_HELPER is an optional trusted host input owned by
the UI SDK. It names one absolute, root-owned executable implementing the
separate worker identity protocol.
It is not a repository setting, command prefix or per-turn environment grant.
Omission retains shared UID with masks and an explicit exposure disclosure;
configured helpers must pass the real identity/sentinel/positive-control probe.
The package-owned references below contain the literal-variable inventory,
meaning, omission behavior and conditional requirements. The inventory includes
platform inputs and test controls so they can be classified rather than silently
promoted into new product promises. Packages with no environment reader add no
separate environment format. These references are generated from each package's
src/config/env.ts; the resolver and actual consumer determine parsing and
effective validation. Documented product controls below are supported inputs.
Platform location inputs, build metadata and the internal NODE_ENV test control
are classified separately in the precedence boundaries below; the table does not
promote ambient SDK variables to supported inputs.
| Owner reference | Declared literal names |
|---|---|
| core | BRAIN_ROOT, BRAIN_STORAGE_GIT_ROOTS, BRAIN_RERANK_MODE, XDG_BIN_HOME, NO_COLOR, BRAIN_CHROME_NO_SANDBOX, BRAIN_UI_CHROME_NO_SANDBOX, CLAUDE_CODE_PATH, GEMINI_API_KEY, TYPESAFE_API_KEY, ANTHROPIC_API_KEY, APIFY_TOKEN, BRAIN_APIFY_LEDGER |
| module-images | OPENAI_API_KEY, GEMINI_API_KEY, OPENAI_BASE_URL, GEMINI_BASE_URL |
| module-jobs | CHROME_CDP_URL |
| scrape | SCRAPE_USER_AGENT, SCRAPE_CHROME_URL, SCRAPE_CHROME_PATH, SCRAPE_CHROME_NO_SANDBOX, SCRAPE_RESPECT_ROBOTS |
| ui-backend-claude | BRAIN_UI_EXEC_WRAPPER, BRAIN_UI_EXEC_KILLER, BRAIN_UI_SUBPROCESS_ENV_EXTRA, ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_BASE_URL, BRAIN_UI_REVERSE_GEOCODE, NOMINATIM_PUBLIC_SERVICE_ELIGIBLE, NOMINATIM_URL, NOMINATIM_USER_AGENT |
| ui-backend-pi | BRAIN_UI_EXEC_WRAPPER, BRAIN_UI_EXEC_KILLER, BRAIN_UI_SUBPROCESS_ENV_EXTRA, GEMINI_API_KEY, BRAIN_UI_REVERSE_GEOCODE, NOMINATIM_PUBLIC_SERVICE_ELIGIBLE, NOMINATIM_URL, NOMINATIM_USER_AGENT |
| ui-render-puppeteer | PUPPETEER_EXECUTABLE_PATH, BRAIN_UI_CHROME_PATH |
| ui-server | BRAIN_UI_AUTONOMOUS_SPEND_USD_PER_DAY, BRAIN_UI_AUTONOMOUS_TURNS_PER_DAY, BRAIN_UI_AUTONOMOUS_EMERGENCY_SPEND_USD, BRAIN_UI_AUTONOMOUS_EMERGENCY_TURNS, BRAIN_UI_AUTONOMOUS_TIMEZONE, BRAIN_UI_AUTONOMOUS_UNPRICED_USD_PER_TOKEN, BRAIN_UI_ASK_USER_FORM_MAX_DEPTH, BRAIN_UI_ASK_USER_FORM_MAX_NODES, BRAIN_UI_ASK_USER_FORM_MAX_OPTIONS, BRAIN_UI_EXEC_KILLER, BRAIN_UI_EXEC_WRAPPER, BRAIN_PATH, HOME, BRAIN_UI_CRON_HYGIENE, BRAIN_UI_SUBPROCESS_ENV_EXTRA, PI_CODING_AGENT_DIR, XDG_CONFIG_HOME, BRAIN_UI_SKILLS_GITHUB_TOKEN, GITHUB_TOKEN, DB_PATH, HOST, BRAIN_UI_INBOX_POKE_TOKEN_FILE, MAX_AUTONOMOUS_RUNS, BRAIN_UI_AUTONOMOUS_YIELD_AFTER_MS, BRAIN_UI_CONFIRM_BASH, BRAIN_UI_WS_MAX_CONNECTIONS, BRAIN_UI_WS_RATE, BRAIN_UI_WS_BURST, BRAIN_UI_LOG_LEVEL, SOURCE_COMMIT, ALLOWED_ORIGINS, MAX_CONCURRENT_SESSIONS, BRAIN_UI_WORKER_STATE_BYTES, BRAIN_UI_WORKER_STATE_BUDGET_BYTES, BRAIN_UI_TURN_TEMP_BYTES, BRAIN_UI_TURN_TEMP_BUDGET_BYTES, BRAIN_UI_MAINTENANCE_TEMP_BYTES, BRAIN_UI_MAINTENANCE_TEMP_BUDGET_BYTES, BRAIN_UI_TURN_TIMEOUT_MS, AUTH_MODE, BRAIN_UI_PASSWORD_HASH, COOKIE_SECRET, TRUST_PROXY, TRUST_PROXY_HOPS, PROXY_AUTH_HEADER, BRAIN_UI_DANGEROUSLY_DISABLE_AUTH, BRAIN_UI_ALLOW_PASSWORD, WEBAUTHN_RP_NAME, WEBAUTHN_USER_NAME, WEBAUTHN_USER_ID, WEBAUTHN_RP_ID, WEBAUTHN_ORIGINS, BRAIN_UI_ALLOW_LOOPBACK_ORIGIN, AGENT_BACKEND, TYPESAFE_API_KEY, CLAUDE_CODE_PATH, BRAIN_UI_CLAUDE_DEFAULT_MODEL, BRAIN_UI_CLAUDE_DEFAULT_THINKING_LEVEL, BRAIN_UI_CLAUDE_PROFILES, BRAIN_UI_PI_PROFILES, CLAUDE_CODE_OAUTH_TOKEN, BRAIN_UI_CLAUDE_TOKEN_MINTED_AT, ANTHROPIC_API_KEY, BRAIN_UI_MODEL_DISCOVERY, BRAIN_UI_MODEL_TTL_HOURS, BRAIN_UI_PRICING_DISCOVERY, BRAIN_UI_PRICING_TTL_HOURS, NODE_ENV, APIFY_TOKEN, BRAIN_APIFY_LEDGER, DEEPGRAM_API_KEY, VOICE_PROVIDER, VOICE_KEYTERM_LIMIT, VOICE_CACHE_DIR, BRAIN_UI_COASTLINE, OVERPASS_URL, OVERPASS_USER_AGENT, COASTLINE_CACHE_DIR, BRAIN_GEO_CONFIG_JSON |
Static secrets and names selected by config are read when needed. Server factory
configuration is resolved when the app is created; it is not a promise of live
reload. Presence checks do not prove provider access, quota or request success.
Token-valued flags use trimmed, case-insensitive 1/true/on/yes and
0/false/off/no; unknown or empty tokens use the declared fallback. NO_COLOR
uses nonempty presence instead: NO_COLOR=0 still suppresses color. The installed
shell hooks' BRAIN_SKIP_HOOKS bypass is specifically =1.
Validation is input-specific, not a universal reject-on-invalid rule. The server's autonomous budgets, form limits and canonical geo JSON reject invalid inputs; some numeric settings and log severity use documented safe fallbacks. Profile JSON and confirmation patterns have their own validation at backend initialization. Read the owning server reference, hosting configuration and budget reference before relying on a fallback. Authentication defaults and conditional secret requirements remain governed by the hosting/auth validation; optional descriptor entries do not authorize an invalid authentication setup.
Important precedence boundaries:
SCRAPE_CHROME_URLwins over jobs' retainedCHROME_CDP_URLalias. With no endpoint the shared scraper can launch its own browser; the alias is not a promise that every scrape connects to a fixed local endpoint.- Renderer executable selection checks
PUPPETEER_EXECUTABLE_PATH, thenBRAIN_UI_CHROME_PATH, then known system paths. Core accepts either no-sandbox spelling through its token parser; the Puppeteer adapter receives that option. - Inference profiles' declared credentials/base URLs override inherited defaults; profiles without their own credential retain the documented Claude subscription routing and API-key clearing. Model-discovery authentication is a separate use.
- Pi settings location is
PI_CODING_AGENT_DIR→XDG_CONFIG_HOME/pi→HOME/.pi. Specialized skill-install token precedesGITHUB_TOKEN. APIFY_TOKENis read only by the server and only used while the governance policy setsapify.enabled: true(#3159). No subprocess, worker orBRAIN_UI_SUBPROCESS_ENV_EXTRAentry can receive it; the server binds the account the fixed Apify API reports, and a token for a different account needs an owner's confirmation before use. There is no fallback to another credential. Since #3162 a standalonebrain apifyorbrain mcpprocess reads its ownAPIFY_TOKENthe same way, andBRAIN_APIFY_LEDGERnames the shared Apify ledger: the server opens it (defaultDB_PATH.apify-ledger.sqlite), and a standalone process without it refuses every Apify operation. Neither variable reaches any worker, so a shellbrain apifyinside a chat turn refuses.BRAIN_GEO_CONFIG_JSONsupplies canonical geo settings over legacy Overpass configuration;BRAIN_UI_COASTLINE=falsestill prevents those requests.HOMEand XDG variables are platform location inputs.SOURCE_COMMITis build-provided status metadata.NODE_ENV=testcontrols discovery defaults, not security. InheritedPATH, SDK/platform variables and arbitrary extra variable contents are not all brain-kit configuration APIs.
Literal tables cannot enumerate these supported name sources and transport boundaries. This inventory follows the current readers and the package-owned dynamic-family metadata. Supported name sources remain distinct from internal transport snapshots.
| Owner and name source | Meaning and boundary |
|---|---|
Core embeddings.apiKeyEnv, completions.apiKeyEnv, completions.fallbackApiKeyEnv, reranker.apiKeyEnv |
Select built-in provider credential names at call time; omission uses that provider's default name. Primary and fallback are independent. |
Images provider catalog apiKeyEnv |
Provider-declared credential lookup; current OpenAI/Gemini names are in the literal table. This does not introduce a provider-registration seam. |
Claude inference-profile authTokenEnv / apiKeyEnv |
Query-time credential and availability lookup; values are not config-file secrets. The profile authentication rules still apply. |
UI server and pi WEB_SEARCH_PROVIDERS[].envVar |
Catalog-selected key-presence checks for environment-configured web-search providers. Presence is returned, not secret values or copied settings. |
Pi brain-config embeddings.apiKeyEnv |
Call-time key-presence check for the selected embedding provider. |
Core inheritedEnv(overrides) |
Call-time ambient snapshot with explicit overrides last; an internal subprocess transport, not a promise for every ambient variable. |
UI server subprocessEnv(audience, extra, extraNames) |
Audience allowlist plus admitted operator/declared names; explicit overrides last. BRAIN_UI_SUBPROCESS_ENV_EXTRA is a supported comma-separated admission control, trimmed/validated and itself withheld. |
| Claude/pi filtered subprocess environments | Existing agent allowlist and operator extras; Claude also admits selected-profile credential names and applies its profile overrides. Forwarding does not make unknown variable meanings part of this contract. |
The Google SDK's ambient GOOGLE_API_KEY is not a brain-kit credential-selection
input. The Gemini providers pass their selected key explicitly. No supported
promise requires mutating GOOGLE_API_KEY around asynchronous requests.
For lowercase module manifest names, settings/<name>.json is a canonical
JSON object. Its own keys merge recursively over the brain-config domain block;
arrays, scalars and null replace. The same manifest schema then validates and
applies defaults. Absent JSON contributes {}. Core-owned enabled is forbidden
there; enable/disable is a separate writer. Dormant modules retain domain loading
and validation. Invalid hand-edited JSON fails; it does not erase an override or
silently fall back. Settings paths cannot alias another path through symlinks.
The module settings contract defines the shipped snapshot, provenance, opaque source revision, CLI and HTTP writer. Load, validation, CLI saves and UI saves use the same domain schema. Untouched optional fields stay absent; read-only/unsupported JSON values remain preserved. TypeScript keeps logic, and ordinary settings saves do not rewrite it. Inherited values require an explicit override; resetting removes the saved key.
Writes validate before replacing source, serialize revision checks and commit, and reject stale revisions. A changed save commits only its owned JSON once; structurally unchanged saves retain bytes and make no commit. Source subtrees that did not change retain their representation. Failures restore prior target files/index entries and preserve unrelated staged work. These are preservation promises, not a requirement to maintain a particular internal lock or hash layout.
Migration is an explicit reviewed transaction. Jobs previews the complete legacy scoring source, proves equivalent parsed scoring and atomically moves known keys to JSON, retaining casing, accepted source representations, unknown scoring keys, unrelated frontmatter and prose. Legacy frontmatter remains a fallback until migrated. JSON scoring wins when supplied; defaults are not materialized just because an editor opens. Travel's source-only ownership migration remains a different command: it refuses existing speaking/travel settings files, and its manifest does not declare a shared settings migration planner. Such files require reviewed manual migration, not an automatic invocation of a nonexistent planner.
All five domain schemas are strict. Each reference below owns its field meanings, validation, defaults and canonical formats under this shared policy. The shared writer applies even when a module has no custom editor metadata.
| Module | Supported domain configuration | Canonical user content and owner reference |
|---|---|---|
| Speaking | travelParty array of {name, role?, requirementsDoc?}, default []; deprecated but retained during the explicit travel transition. |
talk and conference Markdown, existing hubs and links; speaking reference, common frontmatter and directory-anchor rules. User prose is canonical. |
| Travel | travelParty with the same member representation and default []. |
travel, trip, place Markdown; journey/trip-owned visit IDs, place references, route/track and photo assets. Canonical formats specify validation, relative references and unknown values. Dates/coordinates may remain unknown; titles/dates are not visit identity. |
| Finance | clientsDir default clients, numeric feeTolerance default 30, currency default USD, positive integer termsDays default 30. |
ledger.md frontmatter: client identity, invoices, payments and allocations are authoritative. Statuses, balances and owned generated tables are derived; surrounding prose is preserved. |
| Jobs | Required criteria; optional source-form scoring, dbPath, positive currency rates; opportunitiesDir, boards, queries, enrichment.concurrency and enrichment.maxDetailPages with schema defaults. |
Scoring/settings, criteria format, opportunity status.md pipeline fields and registry spec. JSON scoring or legacy criteria frontmatter is canonical input. jobs.db also holds manual review decisions and notes. |
| Images | imagesDir default assets/images; optional disabledModels and ordered preferredModels string arrays. |
Images reference: generated image files kept at user-selected paths are content; catalog/routing details follow explicit selection policy. No additional canonical Markdown or JSON content schema is declared. |
Settings live at the manifest-name path, independent of a package key alias. Core frontmatter, wiki links, registry declarations and generated-region markers retain their file-layer contracts. Module-specific Markdown fields augment those rules; they do not replace them.
brain.db is a disposable content index; rebuilding from canonical files may
replace it. Its existing separately documented query/direct-SQL compatibility
promises still bind their consumers. Committed context/asset sidecars and stats
history retain their existing file-layer contracts even though they are
machine-managed. Derived does not automatically mean uncontracted.
Internal cache layouts, transient scratch output and internal storage schemas
are not supported hand-authored input formats. Do not infer that an unclassified
module file can be deleted or regenerated. Jobs' database contains review_status,
reviewed_at and review_notes written by manual review: scraping/indexing cannot
reconstruct that state. UI operational storage likewise has its own backup and
recovery requirements. Those schemas can remain internal while their state must
be preserved. Canonical module Markdown, JSON settings and user-kept assets are
never disposable merely because they are outside the core content index.
The enabled diagrams module accepts a versioned YAML diagram fence or
standalone .diagram.yaml document. The v1 spec and check contract
owns its strict fields, family grammar, budgets, geometry validation and parse bounds.