Repository navigation
v20.0.0
A major release that trims BF's surface and settles deferred input rules. bf schedule and assistant triggers are removed, MCP becomes an optional extra (13 runtime packages instead of 36 on Linux and macOS), disabled programs stop reporting failures, and six ambiguous inputs that loaded before now fail with a message naming the fix. The brain format (version: 8), records and notes are unchanged; the assistant queue needs one manual SQL statement and the search cache rebuilds once. From this release on, breaking changes ship together in at most one major release per quarter. Follow the upgrade steps below.
Breaking changes
bf scheduleis removed. BF no longer writes systemd, launchd or cron files. To run unattended, keepbf watch --json --brain BRAINrunning as a user service you write yourself, as Watch and schedule updates shows for systemd and launchd. A timer that runsbf update --brain BRAINalso still works.- MCP is an optional extra.
uv tool install brain-frameworkno longer installs the MCP SDK, which cuts the base install from 36 to 13 runtime packages on Linux and macOS. Without the extra,bf mcpexits 1 and namesuv tool install --python 3.14 'brain-framework[mcp]'.uv tool upgradekeeps the extra once it is installed. - Assistant triggers are removed.
triggers, thesensor.changedevent and thetriggerkind of run are gone:bf validatereportsassistants.NAME.triggers: Extra inputs are not permittedand rejectstriggerin an output'swhen. Sensor changes start no assistant and record writes never touch the queue, so a collection can no longer fail after saving withSOURCE records were committed but their assistant event is not queued. - Queue format 2. The assistant queue drops its
triggerstable. Execution andbf statusrefuse a v19 queue withassistant queue predates this release; preserve it and follow the upgrade steps in the changelog. - Assistant replies and runs.
bf queueand statusmessagingdroptriggersandtrigger_counts, andbf updaterows no longer name atrigger. A record'sattributes.triggerisinput,scheduleormanual. Neither records nor{{context}}carryevent, and harnesses no longer receiveBF_ASSISTANT_RUN. - No program is named
bf. A sensor, routine, channel or assistant namedbfstopsbf.yamlfrom loading withsensors.bf: the name bf is reserved for BF addresses; rename the sensor and move memories/bf/ to its new name, because its records'bf:IDrefs read as BF addresses. Records left inmemories/bf/are skipped and reported until moved. - A BF address ending in a bare
#is invalid.bf read,bf searchand the MCP tools rejectbf://brain/projects/plan.md#as invalid input (exit 2), as they have rejected a plain ref ending in#since 18.5.0; before, it read and scoped the whole note. A note writing one in a link, frontmatterlinks,sources,entityoraliasesis invalid and unsearchable, and a record holding one in itslinksoraliasesis skipped and reported. A relative link such asplan.md#still links the whole file. - Search scopes take no section.
--scope bf://brain/projects/plan.md#decisionand--scope memories/SOURCE#xare invalid input (exit 2) in the CLI, the MCPsearchtool and eval suites, like a section's path. A suite holding one fails to load and names the field, such ascases.1.scope. bf init --name ""is invalid. An empty or blank name exits 2 and creates nothing. Omit--nameto derive the name from the folder.- libyaml is required. Without it, every command that reads YAML exits 1 and names the fix,
uv tool install --reinstall --python 3.14 brain-framework. BF used to fall back to PyYAML's slower pure-Python parser, which rejects some YAML that libyaml accepts. - OKF
sourcesresources hold at most 8,192 characters, like frontmatterlinks. A longer one fails asinvalid frontmatter: sources: reference exceeds 8192 characters.
Changed
- Disabled programs report no failure. A disabled sensor, routine or assistant shows no
failed,error,failuresorlogin status, coverage or watch, adds noattentionentry and never failsbf status --check. Its unresolved jobs stay inbf queueandmessaging, and count again once re-enabled; a removed assistant's jobs still fail the check. This reverses v19's "even once disabled". - One breaking window per quarter. Breaking changes now wait for one batched major release, at most once a quarter, and a new rule that rejects input loading today warns first where it can (Contributing). The
bf watchterminal dashboard is frozen: it gets fixes only, and new run information goes tobf watch --jsonandbf status. - The search cache rebuilds once on first search or read.
Upgrade from v19
-
With v19 still installed, stop
bf watch, its service or timers and any script callingbf run,bf channelorbf queue, including jobsbf scheduleinstalled: they runbf updateand keep working after the upgrade, so re-enable them at the end or replace them with a watch service. Already upgraded? Reinstall the previous release withuv tool install --python 3.14 'brain-framework==19.1.0', do steps 1 and 2, then upgrade again: v20 cannot drain a v19 queue or recover a v19memories/.pendingjournal. -
Drain the assistant queue: run
bf update --kind assistant, then retry or cancel each pending, failed or uncertain job, including those of disabled assistants, untilbf queuecountsshow onlycompleteandcancelled; deliveries may stay. Ifmemories/.pendingexists, run v19'sbf build. -
Back up the brain and the state directory's
runtime/folder, then upgrade withuv tool upgrade brain-framework, or update a pinned brain's pin, and checkbf --version. If an agent host startsbf mcp, installuv tool install --python 3.14 'brain-framework[mcp]'instead and check thatuv tool list --show-extrasshows[extras: mcp]. If a command reportsreading YAML needs PyYAML built with libyaml, runuv tool install --reinstall --python 3.14 brain-framework, or'brain-framework[mcp]'when you use MCP: a reinstall without the extra drops it. -
Update the queue in place; never delete it. It lives under
$XDG_STATE_HOME/bf/runtime/(normally~/.local/state/bf/runtime/), in the folderstat -L -c %d-%i ~/brainnames (stat -L -f %d-%i ~/brainon macOS). Skip this step when that folder holds noqueue.sqlite3: runningsqlite3would create an empty file BF then refuses.queue=~/.local/state/bf/runtime/"$(stat -L -c %d-%i ~/brain)"/queue.sqlite3 [ -s "$queue" ] && sqlite3 -bail "$queue" "BEGIN; CREATE TEMP TABLE drained(n INTEGER CHECK (n = 0)); INSERT INTO drained SELECT count(*) FROM jobs WHERE state NOT IN ('complete','cancelled'); DROP TABLE triggers; PRAGMA user_version=2; COMMIT;"
The statement refuses a queue that still holds unresolved jobs, and a rerun exits 1; both change nothing. Python's
sqlite3module can run the same statements. -
In
bf.yaml, deletetriggers:andtriggerfrom each output'swhen. Give a trigger-only assistant a schedule, such asschedules: {review: {every: 3600}}, with a prompt that readsbf read memories/SENSOR/24h. Rename any program namedbf, also in the inputs, outputs and--sensor,--routineor--assistantselectors that name it, and move its records withmv memories/bf memories/NEW, renaming anymemories/bfexception in.gitignore; its log and run history start again under the new name. -
Run
bf validate: it names each note holding a BF address that ends in a bare#or an OKFsourcesresource over 8,192 characters, and each record whoselinksoraliaseshold such an address. Remove each#or name the section, shorten the resource, and fix the sensor that printed the record before collecting it again. -
In scripts, host configuration and
evals/*.yaml, scope the whole note andexpectthe section, drop the#…frommemories/SOURCE#…scopes and set or omitbf init --name. Update prompts and scripts that readevent,attributes.trigger == "trigger",messaging.triggers,trigger_countsorBF_ASSISTANT_RUN. -
To run unattended, re-enable the jobs you stopped in step 1, or replace them with the user service from Run watch as a service. Never run both on one brain.
-
Run
bf skills DIRfor each skills folder, thenbf validate,bf evalandbf status --check, and restart watch.