Changelog
This page is rb’s own CHANGELOG.md, every release in full, newest first.
0.8.0 (2026-11-07)
Section titled “0.8.0 (2026-11-07)”Four new kinds of rule (a count, a best of N, a relative change and a cited difference of means) are kept in record format 2, each is read its own way wherever rb reads it, and rb writes every other file in format 1, as rb 0.7 writes it.
-
Record format 2. rb writes a criterion’s file in format 2 (
rb: 2) only while it holds one of four new kinds of rule (below), and writes every other file in format 1 (rb: 1), as rb 0.7 writes it. rb reads both formats.- A file that says
rb: 1and holds one of the four is refused (E_STATE_CORRUPT: “is format 1 and holds over: count, which only format 2 has”), andrb showof such a file says it is not valid (“format 1 holds no over: count: only format 2 does, and rb writes such a file as rb: 2”). A format above 2 asks for an upgrade (“this rb reads formats 1 to 2”). - A retracted rule keeps the rule it was, so its file stays in format 2. rb says nothing about a file raised to
rb: 2by hand that holds none of the four, and its next write of that file puts backrb: 1. rb export --with-recordwritesrecord_format: 2in the bundle’s manifest when a criterion’s file is in format 2, else 1 (every earlier rb wrote 1); the bundle staysrb-bundleversion 2.rb clonekeeps each of the four whole, in format 2, and refuses a bundle whoserecord_formatis above 2.
- A file that says
-
Four new kinds of rule.
rb criterion addtakes four new kinds of rule that format 1 cannot hold, each kept in its criterion’s own file in format 2. Each reads a threshold its own way (--at-mostor--at-least, or, for all but a best of N,--equalswith--tolerance), and each is said in the same words byrb criterion add,rb show,rb status,rb context, the SDK and MCP.- A count.
--count Kreads whether at least K units meet the threshold, judging each unit as--eachdoes, and--of Nsays how many units the rule is of:--metric int8.val_loss --at-most 0.890 --count 4 --of 5declares “4 of 5 units’ int8.val_loss ≤ 0.89”. Its reading names both numbers: “recorded values meet criterion: 4 of 5 seeds at most 0.89; the rule asks for 4”. Where the runs hold another number of units than the rule is of, or fewer than K, it reads undetermined and says so: “criterion undetermined: the rule is of 5 seeds; the runs hold 4”. - A best of N.
--best, with--at-mostor--at-least, reads the unit best in the threshold’s direction against it, and--of Nsays how many units the rule is of, as for a count: “the best of 5 units’ val_loss ≤ 0.889” reads “recorded values meet criterion: best of 5 seeds, seed 3: 0.8887 ≤ 0.889”. Its reading is that unit’s own value, never a mean: “a best of N is one unit’s own value, with no interval”. - A relative change.
--relative, on a comparison of two arms, reads the relative change of the arms’ means, (subject - reference) / reference, in percent, and its threshold is a percent:--metric change.val_bpb --at-most -22.5 --relativedeclares “change.val_bpb as a relative change ≤ -22.5%”. The arms are paired where every unit holds both, and read apart otherwise. From 3 units its interval is a delta-method t interval (“relative change of the means, paired, delta-method t interval” onrb show’s reads line,comparison.methodin--json); with fewer it has none, and a met rule reads “estimate meets criterion”. - A cited difference of means.
--means, beside--sourceand--quote, reads the cited number as a difference of the arms’ means over all their units, with a Welch interval from 3 units in each arm. For “change.val_loss as a difference of means = 0.0317 ± 0.00005”,rb show’s reads line says “difference of means, as cited” (“difference of means, as cited, Welch interval” once each arm holds 3 units). rb sets the cited number against that difference of means, never against its paired reading, so a unit that only one arm ran does not make the rule read “not comparable”, as it can a cited rule without--means. - A relative change and a difference of means read a threshold on their estimate, or with
--boundon their interval’s bound (“change.val_loss as a relative change, upper bound ≤ 20%”). With--each,--count,--bestor an interval rule they are refused (“a ‘relative’ reading takes a threshold on its estimate or its bound”), and so is an--estimand(“a relative or means reading is rb’s own reading of the runs; it takes no estimand”). - A met count or best of N reads “recorded values meet criterion” (a cited one, “reproduced on the recorded values”), since the recorded units themselves meet it, whatever the interval of the units’ mean, and the tally counts it among the rules met on the estimate alone: “Rules: 1 met, on the estimate alone”. Timing, calls and
--fail-ongates read the four as any rule. - Their refusals include these, each naming its flags: “--of says how many units a count or a best of N is of”, “a best run is best in a threshold’s direction: --at-most or --at-least”, “a ‘relative’ reading compares two arms: give a reference (or read change.<metric>)”, “a difference of means is what a cited number measures: give its --source and --quote” and “--relative and --means each say what the reading measures.” A count above its
--ofis refused (“a count rule’s count is at most the units it is of”), and an interval rule takes none of--each,--bound,--countand--best(“a ‘better’ rule already reads the interval; --each, --bound, --count and --best are for thresholds”).
- A count.
-
The four, wherever rb reads them. Each of the four is read its own way wherever rb reads it.
- At one fixed design cell that
--whereselects, a relative change and a difference of means are read on that cell’s runs, with its interval: “change.val_loss [where batch=32]: +0.5625 [+0.545, +0.58] % · relative change of the means, paired, delta-method t interval, 3 units, 95% (rb)”. - Across fixed design cells, a relative change and a difference of means read each cell as their rule reads it and say the cells’ range and how many meet in the rule’s own unit (“descriptive range +0.1% to +0.5625%; no sampling interval across fixed cells · 1 of 2 cells meet the threshold”), and the rule reads undetermined: “a relative change is not read across fixed design cells, which are not independent samples; select one cell (--where) to read its replications”. A count and a best of N judge every unit within the cells, as
--eachdoes. - Where a reading that pools a per-run setting meets its rule, a best of N, a relative change and a difference of means name each value whose runs alone would not meet it, read there as the rule reads them. A count names no value, since its K is of every unit it reads.
- A count and a best of N read the runs a rule’s
--whereselects, and an analysis of the comparison is said beside any of the four, never read in its place. - A relative change and a difference of means read each arm’s own numbers, so where a run gives only the difference (
change.<metric>), they have no reading. - A cited count or best of N is never set against the arms’ means, so it never reads “not comparable” for that reason.
- At one fixed design cell that
-
One way to read a threshold.
rb criterion addand MCP’scriterion_addrefuse two of--each,--bound,--countand--besttogether (E_OBJECT_INVALID: “A threshold is read one way: --each and --bound each say a different one.”, with “Give one of --each, --bound, --count K or --best.”), where rb 0.7.1 read--each --boundas--eachand dropped--boundwithout a word. -
The page.
- A count and a best of N say what they pass on (“At least 19 of the 20 units’ values are at most 0.89 nats”, “The best of the 20 units’ values is at most 0.885 nats”) and are said by their units (“INT8: 10 of 20 seeds at most 0.89; the rule asks for 19”, “INT8: best of 5 seeds, seed 3: 0.8893 > 0.889”). The figure marks no design cell “outside the rule” for a count or a best of N, which judge units, not cells.
- A relative change is said in percent, never in points, on its card, its verdict and a call’s figure (“INT8 is 0.3914% lower than the FP32 baseline (-0.4438 to -0.339)”), and the runs under it keep their metric’s own unit. A cited difference of means is said with ”, a difference of means, as cited”.
- A sentence that starts with a metric’s name keeps it as declared (“top1 is 0.74”, never “Top1”), and headings, a run’s card and the runs table name every metric as declared, never title-cased or split at a
.(accandm.acc, never “Acc” or “M acc”). A name that ends in its unit, such aslat.ms, is not followed by the unit again. - A reading is said in one set of digits on its card, its list sentence, its verdict and its figure, and a variant’s level is one number on its card and in the comparison grid (“INT8 is 0.851 (0.826 to 0.876)” beside “INT8 0.851 · 3 seeds”). Past two decimals (two significant figures under 0.1), the page shows no trailing 0 that a column’s or a rule’s digits add:
0.74, never0.740. rb show <run>, a run’s card and its row in a runs table give the run’s numbers to 15 significant digits: a float’s last-bit noise left out (49.69, never49.690000000000005), and no digit given cut (1234.5679, never1234.57).- Each experiment’s row says how many of its rules are met on the estimate alone: “Rules: 1 rule met, on the estimate alone”.
-
Every SQLite row in one command.
--every-rowrecords each row--wherepicks of a SQLite table as its own run, as it does for a CSV, TSV or JSON Lines table:rb run add <exp> --from results.db --table evals --where precision=int8 --set seed=@seed --every-row. Without it, a--wherethat picks several rows is refused and offers it. Each run keeps the row’s rowid beside its--wherekey (in a table with no rowid, its primary key), so a row written again reads as that row, changed, never as a second run, and a run an earlier rb read by its--wherecolumns alone is still that row. -
One variant, one number. On an experiment of one variant,
top1andint8.top1are the same number, so neither the write norrb statussays a run of it counts toward nothing. The line a run no rule counts gets names the number as the rule does, fromrb run add, the SDK and MCP alike: “Its criteria read int8.top1: a run gives them as int8.top1=<value>.” for a rule onint8.top1(the SDK saysrun.log({"int8.top1": ...})), andtop1=<value>for one ontop1. Both spellings are read. -
Prose beside results files.
rb reconcilequotes the prose a folder of results files holds (a Markdown file whose name is no report’s, such aspilot_table.md) at the first look, before any run reads the folder, as it quotes a report’s, and says the folder is still no experiment of its own: “prose in it, quoted (no report by name, so no experiment of its own)”. -
A table with no header line.
rb compilenames no column from a CSV or TSV table whose first line reads as a data row, and counts every row, asrb reconciledoes. -
An
--eachrule keeps its runs. An--eachrule is about every unit, which only runs give, so it keeps reading its runs where an analysis reports its comparison, and says the analysis beside them: “a6pqyge reports wide.top1: 0.83 · read off the paper (a6pqyge); an --each rule reads each unit, so rb reads the runs, with the analysis beside them”. A call recorded on such a rule before the upgrade can read “made on an earlier reading”. For an untested--eachrule beside an analysis,rb statusgives the run that tests it, never an analysis, and for one that no run can test, it says to retract it and declare it again on what the runs report. -
What
rb shownames.rb show <criterion>names an arm’s number as the rule does (fp32.eval.top1for a rule onchange.eval.top1, neverfp32.top1), and names every run outside the rule’s per-run selectors, with its values of what the rule selects by: “3 runs outside this criterion’s per-run selectors are not read: runedhdx7 (batch=64), runf3hlli (batch=64), runprswmo (batch=64)”. -
The unpaired figure. The unpaired figure beside “paired by” reads every run of the two arms, a run that gives both arms read as each arm’s number, and counts their units (“Welch interval, 10 units”); where a run gives only the difference, no unpaired figure is given. It is said in the digits the rule needs, as the paired reading is, and a float’s last-bit noise adds none: “unpaired, the same runs read -0.65”, never
-0.6500. Nor does that noise add digits to a--boundrule’s estimate. -
Repeats and selected settings.
rb comparegives a declared repeat (population.repeat) no row of its own, andrb spec set <exp> population.repeat <setting> --no-verifyis never offered for a setting the rule selects on (--where seed=1). -
Where rb looks, said one way.
rb status,rb context,rb open,rb uiandrb pagesay where rb looks in the wordsrb reconcileuses: “folders named for results (results/, eval/, metrics/, reports/, artifacts/ and the like), down to 4 levels from the top”. -
Words.
rb statuslists the items that ask for nothing under “Facts that ask for nothing”, andrb contextunder ”## Facts that ask for nothing”, where both said “Nothing to do”.- Where a rule’s timing and its question link’s read alike,
rb status,rb show <question>andrb contextsay them once (“rule and question link declared after the evidence existed: ...”, and in an interpretation “criterion and question link ...”); where they differ, each is said by its name. - A rule’s numbers are said in digits, not with an exponent:
top1 = 0.999349 ± 0.0000005, never± 5e-07(a number under 0.000000000001, or of more than 16 whole digits, keeps its exponent).
-
Schema, SDK and MCP.
rb schema allprints formats 1 and 2 as one JSON Schema, committed asschema/rb-v2.schema.json(its$idis https://rabbitbrain.ai/schema/rb-v2.schema.json): a file’srbis 1 or 2, no longer 1 alone, and a format-1 criterion holds none of the four.schema/rb-v1.schema.json, format 1’s, stays as it was.- Every criterion in
--jsonand over MCP carriescount,ofandmeasure(null,nulland""for a rule that uses none of the four); a file in.rb/holds each of them only where its rule uses it. - The SDK’s
exp.criterion(...)takescount=,of=,over="best"andmeasure="relative"ormeasure="means", and MCP’scriterion_addtakescount,of,best,relativeandmeans. - Over MCP,
rb://formatstill serves format 1’s schema (rb-v1.schema.json), as rb 0.7 does, and the newrb://format-2servesrb-v2.schema.json. rb showof a file that is no record says “not a record of Rabbit Brain format 1 or 2: it does not start with rb: 1 (or rb: 2)”, and of a file whose kind rb does not know, “kind ‘thing’ is not a kind the format has”.- The manual now says what a run’s file keeps beside each resource it read or made: where that resource stood when the run was recorded (
inputs[].availability,outputs[].availability), which rb says as then, never as where the resource stands now.
-
A team on two versions. rb 0.7.0 and 0.7.1 read format 1 only, and rb 0.8.0 writes format 2 only in the file of a rule that uses one of the four.
-
A record with no format-2 file: 0.7.0 and 0.7.1 read what 0.8.0 writes with no part they do not know, and write to it as before. Their rules’ readings differ from 0.8.0’s in one place, and 0.7.0’s in two more:
- an
--eachrule beside an analysis of its comparison is read from the analysis in 0.7.0 and 0.7.1, and from its runs in 0.8.0; - on an experiment with one variant, 0.7.0 finds no number for a rule over a number recorded with no variant;
- on an experiment with
population.repeat, 0.7.0 reads the runs as if no repeat were declared.
Where a run gives both arms beside runs of one arm, or gives only the difference, 0.7.1’s unpaired figure beside “paired by” can differ from 0.8.0’s, and 0.7.0 gives no unpaired figure.
A call 0.8.0 records on an
--eachrule beside an analysis of its comparison reads “made on an earlier reading” in 0.7.0 and 0.7.1, which read that rule from the analysis, so their--fail-on undecidedfails there. In 0.7.0, a call 0.8.0 records on either of the other two readings reads so too. The same holds the other way: a call 0.7.0 or 0.7.1 records on such an--eachrule, and a call 0.7.0 records on either of the other two readings, reads “made on an earlier reading” in 0.8.0, and 0.8.0’s--fail-on undecidedfails there. - an
-
A record with a format-2 file: in 0.7.0 and 0.7.1,
rb doctornames each such file (“is format 2; this rb reads format 1: it was written by a newer rb”),rb statuscounts them, and both say to upgrade (pip install --upgrade rabbit-brain). They read every other file and fail every--fail-ongate. They write nothing to the record: a note, a criterion and a call are refused, andrb doctor --restorechanges nothing. They refuse to clone a bundle that holds such a file. Theirrb exportstill writes the page, which counts such files (“1 record here was written by a newer rb and this one cannot read it”).
Upgrade everyone who shares a record together.
-
0.7.1 (2026-11-03)
Section titled “0.7.1 (2026-11-03)”rb reconcile names every file that holds results, readings say what they pair by, and no note offers a command that would take away a miss rb has just named.
-
Every results file, named.
rb reconcilenever says “0 results folders” or “Nothing to compile” while a results folder holds files. It names each file with its kind, its rows and number columns, and the line that records a row. What it cannot read (a file a folder further down, a database, a form rb does not read) it names, with why.- A results file in the top folder is named too: a table (
results.tsv,results.csv,results.jsonl, or one namedmetrics,scores,runs,ledgerand the like) with the line that reads a row, and a report in prose (RESULTS.md,report.md,summary.mdand the like), from which rb reads no run. - Once a table’s rows are recorded, it says how many (“40 of 40 rows recorded as runs”). It never turns back to “0 results folders”.
rb reconcilereads a CSV or TSV table whose first line holds a number with a decimal point as having no header line: it says so, counts every row, and names no column from that line.
- A results file in the top folder is named too: a table (
-
JSON Lines. A
.jsonlor.ndjsonfile is a table, one row a run, picked with--whereas a CSV row is. A nested object’s keys are joined with dots.rb compilelists its rows and numbers. A--wherethat picks no row, or several, says what the rows hold or differ in. -
Every row in one command.
rb run add --from <table> --where ... --every-rowrecords each row--wherepicks as its own run, with its@<column>values (--set,--produced-at,--commit) from its own row. A row already recorded is passed. Every row is read before any is written. Without the flag, a--wherethat picks several rows offers it. CSV, TSV and JSON Lines only. -
Rows.
--commit @<column>reads the commit from the row, and refuses a cell that holds no commit.- A table row reads “(row)” in
rb show. - A CSV, TSV or JSON Lines row recorded as a run in two experiments is said at the write and listed by
rb status, as a database row is. rb spec set ... --source <file>.csv#[...]reads the column of the row the brackets pick. A path that names no file is refused.
-
What a reading pairs by. A reading that pairs arms recorded apart says what it pairs them by, in the terminal, on the page and in
--json(pairing), such as “paired by epoch”. The unpaired reading is beside it, unless it says exactly the same. rb never decides whether runs are linked: you do.rb spec set <exp> population.repeat epoch --no-verify(orseed,epoch) says a setting only numbers repeats. No reading is then said to pool it, it is never a value a rule is read at alone, and it is never offered as the unit.- A note offers that command only for a setting whose values are whole numbers that every arm holds alike, and says it is for a counter only. Where a rule misses, no note offers any command that changes how its runs are counted.
- “pooled over N values” is never said of a declared repeat. A value is said to miss alone only where it holds enough units of its own.
-
Cited rules.
- A direction cited with
--source --quoteand no number is read as its rule says. “not comparable” only where the cited number is one rb’s reading could miss, and the two readings would say different things. - A cited rule met on a reading with no interval, or one that crosses its line, reads “reproduced on the estimate”, as an own rule reads “estimate meets criterion” there.
rb analysis add ... --source <file>reads an estimate and its bounds stated on separate lines of one table or paragraph, where no one line states them all.
- A direction cited with
-
Dates and uncommitted code.
- A setting is dated only against the runs the reading counts, and a variant’s setting against that variant’s runs.
- rb’s own page, export and tour folder never make a run’s code read as uncommitted, and add no snapshot ref.
- Where a rule counts a run made on uncommitted code, its integrity line says “a run it counts ran on code with uncommitted changes” in place of “ok”, and
rb statuskeeps the note it points to in view. - The SDK:
exp.record({...})outside a run block takesproduced_at=. Without it, when its numbers were made is unknown, as for numbers typed in. So--fail-on lateorunestablishedcan fail where it passed. A run block keeps its time, and so does one an earlier rb recorded.
-
Runs no rule counts. On an experiment with one variant, a rule reads a number that names no variant as that variant’s.
rb showsays which runs a rule does not count, and why: one by one where they are outside its arms, and how many where they are outside its per-run selectors. A refusal names only the experiment’s declared metrics, and offers the--variantthe run’s config names. -
The tally. Where rules are met on the estimate alone, the tally says how many: “Rules: 5 reproduced, 2 of them on the estimate alone”, on
rb status, and on the page’s questions and its Rules tab. -
The page.
- A card lists every problem it has, never “(and 1 more)”.
- Two runs whose names read the same are told apart by their results folder or file, never by the second each was recorded.
- A run’s card and its row in a runs table no longer round a number to two decimals. Grids and sentences give a reading in the rule’s digits, one way everywhere.
- A metric whose name joins words with
_is named as declared (val_bpb), never split at_or called by its unit.
-
Words.
- A tolerance is said as the amount it is: “within 0.05 points of 49.6%”, never “within 0.05%”.
- A note’s number carries the digits its rule’s tolerance needs (
+0.4821beside± 0.005, never+0.48). - The hint that gives a run its time asks for the per-run values too:
rb run add <exp> <the same numbers and per-run values> ....
-
Tool files.
rb compilenever proposes a lockfile or a tool’s config (pyproject.toml,package.json) as a run, and a folder that holds only such files is no results folder. A lockfile is known by its name (package-lock.json,poetry.lock,pylock.tomland the like), soeval_lock.jsonis a run’s file again. -
A team on two versions. rb 0.7.0 reads a record that 0.7.1 writes, with no part it does not know. Its readings differ in two places:
- on an experiment with one variant, 0.7.0 finds no number for a rule over a number recorded with no variant;
- on an experiment with
population.repeat, 0.7.0 reads the runs as if no repeat were declared.
A call made on such a reading reads “made on an earlier reading” in 0.7.0.
Upgrade everyone who shares a record together.
0.7.0 (2026-11-02)
Section titled “0.7.0 (2026-11-02)”rb reconcile finds results where a repository keeps them and reads each run folder’s own record, and readings say what they rest on: a time rb cannot know, arms that ran differently, values pooled together.
- Results where they are.
rb reconcilelooks in every folder named for results (results/,eval/,metrics/,reports/,artifacts/and the like), down to four levels, not only in five folders at the top. rb’s journal looks in the same folders.- It passes over code, packages and environments.
rb reconcileand rb’s journal never proposepyproject.toml,package.json,Cargo.tomlorenvironment.ymlas a run, nor list them as a changed file nothing cites. - Under “Not read” it names a folder it passed over that holds results files of its own, and a folder of more than 300 folders.
- It names every place its loose results folders are, up to 100, and names a folder once on a file system that does not tell
Results/fromresults/. - A large Markdown file no longer stalls it.
- It passes over code, packages and environments.
- Each run folder’s own record. Where each run keeps a folder with its config and its numbers,
rb reconcilesays what each folder records: the command, the commit, whether the code had uncommitted changes (dirty: true), and the settings that differ between folders. Where an experiment reads the folder, it prints therb run addline that records it, with its arm, its keys and its settings.- On an experiment that compares two or more variants, numbers that name no variant are refused, unless a rule reads them as given. The message says how to name one.
--command-sourcetakes a command that YAML folds over several lines, and a log first committed up to a week after the run.- A run whose config says
dirty: truesays so on every rule that rests on it. A folder reads “unchanged since the last commit” where it read “clean”. - A file’s first commit is never “written after the run”, and no hint offers a database where there is none.
- CSV and TSV.
rb run add --from <file>.csv --where <column>=<value>reads one row as a run, as a database row is read. A CSV, TSV or database row picked by a varied setting names its variant.rb compilelists a table’s numbers. Given more than 200 sources, it says it lists the first 200 and how to read the rest. - A time rb cannot know. Numbers typed in on the command line or through MCP with no
--produced-athave no time of their own. A rule over them reads “order unknown”, never “declared before the evidence”, and they count among the runs recorded afterwards. A rule still reads a watched command’s numbers and the SDK’s as made when they were recorded.- Records an earlier rb wrote read the same way after the upgrade. Nothing in
.rb/is rewritten. - So
rb status --fail-on late,unestablishedorundecidedcan fail where it passed, and a call made before the upgrade can read as made on an earlier reading. rb run add <exp> <the same numbers and per-run values> --produced-at <when> --replaces <run> --why "<when they were made>"gives such a run its time.
- Records an earlier rb wrote read the same way after the upgrade. Nothing in
- Arms that ran differently. Where the arms a rule compares differ in a per-run setting (500 problems against 250, or another grader), the reading says so. Arms that each ran at one value of a setting pool nothing.
- Pooled readings. Where
population.unitis declared and a rule’s runs pool several values of a setting, its headline says so. It names the value that misses, with what its runs read there: for an--eachrule, the units outside it and the farthest. Such a rule is never drawn as a clean pass. - Cited numbers.
- A cited value on a per-run setting that differs from the runs makes the rule not comparable, not “not reproduced”.
- So does a cited difference of means that rb’s paired reading cannot give. Where each arm is recorded in runs of its own, rb’s own difference of means is shown beside it.
rb spec set --citedsays the value back, and a cited value’s note shows only on rules that cite a number.
- The page says what the terminal says. A rule’s card carries the notes
rb showgives: typed in, pooled, arms that differ, a run with uncommitted changes. A pooled reading leads withrb show’s words for it, and numbers keep the rule’s digits. “checks ok” shows only on a card with no notes. Runs replaced with--replacesare not among the reasons to doubt. - rb’s own files. The files
rb initandrb skill addinstall do not make a run’s tree read as uncommitted, on Windows too. - Words.
- A call that goes against its rule says so: “accepted c1 against its rule (criterion not met)”, and “accepted c1 while rb cannot judge its rule (criterion undetermined)”.
- An unpaired reading’s timing counts both arms’ units.
- No suggested next run (after
rb criterion add, inrb status) offers<metric>=<value>or--command. - Counts say “1 run”.
rb retract <exp>/<name>takes back a--variesname that has no setting.
0.6.3 (2026-10-31)
Section titled “0.6.3 (2026-10-31)”rb login waits through a dropped connection, and rb clone says no part of a password in git’s message.
rb loginthrough a dropped connection. Whilerb loginwaits for you to approve the code in the browser, a check whose connection is refused, reset or dropped, or times out, is asked again, as a busy hub’s is: three in a row at most. The fourth ends it as before: the hub could not be reached, and nothing is stored. A hub that cannot be reached at the first request is still said at once.- No part of a password in
rb clone. Wherever an@follows an address’s first/,?or#, rb cannot tell the host from a password or a token, sorb clonesays “an address rb cannot read safely” in its place. For such an address it says why in its own words and leaves git’s message out, since git, curl and ssh name the host they tried. For any other address written with://, git’s message asrb clonesays it leaves out the user and password before the host, forgit://andssh://too: 0.6.2 said them there whole, in what it printed and in--json. Neither ever reached the record in.rb/.
0.6.2 (2026-10-30)
Section titled “0.6.2 (2026-10-30)”rb looks at the repository as each agent session starts and as each turn ends, and no address rb says carries any part of a password.
- A session hook. With rb’s instructions for each agent it sets up,
rb initinstalls a session hook in the agent’s project file:SessionStartandStopin.claude/settings.jsonfor Claude Code and in.codex/hooks.jsonfor Codex, andsessionStartandstopin.cursor/hooks.jsonfor Cursor. The hook runsrb journal scan --hook <agent>with a 10-second limit, so rb looks at the repository as a session starts and as each turn ends, whether the agent reads rb’s instructions or not. It says nothing, tells the session nothing, records nothing and starts no analytics delivery. The look is kept in the journal as the agent’s. Codex runs a project’s hooks once it is trusted, in/hooks.rb skill addinstalls the hook for one agent later. - A hook file that was there changes only once you agree, as
AGENTS.mdand.mcp.jsondo. rb’s handler goes beside the hooks there, and every byte that was there is kept.rb init --dry-runshows what rb would add. Where a file holds an earlier rb’s handler, rb puts today’s in its place, so one is left. A handler that runsrb journal scanin a form no rb wrote is yours: rb leaves it as it is, adds nothing beside it, and says so in one line.rb removetakes rb’s handler in any form rb wrote, and puts back the handlers it replaced or cut, each where it stood in the list. - The record line says where the record began. Where nothing was recorded afterwards, it says “Kept with rb since it began.” Where the record holds work from before it began, it says which: a record that began as a clone of another record, experiments that reproduce another record’s, and experiments
rb reconcileread from their results folders. A relation to another record’s experiment never reads as a clone. - No part of a password. Where an address holds a password with a
/in it, or a user name with an@, rb leaves the whole password out wherever it says or writes the address: inrb clone, in git’s message as rb says it, and in a clone’s parent on the page and in an export. An origin whose password holds a/names no code inrb exportandrb sync. Where an@follows an address’s first/after credentials or a:, rb cannot tell the host from a password, sorb clonesays “an address rb cannot read safely” in its place: in what it prints, in the clone’sparentand in git’s message. - Words. A question’s changes say a freeze as “spec and rules locked”, in
rb uiandrb open. - Windows (still a preview, #2): Claude Code runs the session hook in Git Bash. Without Git Bash it runs the hook in PowerShell, which does not read it: the hook fails, the session goes on, and rb does not look. Cursor’s hook and Codex’s on Windows look for rb on the PATH alone. Where rb is not there, Cursor’s exits 0, and Codex’s fails while Codex goes on.
0.6.1 (2026-10-30)
Section titled “0.6.1 (2026-10-30)”rabbitbrain.ai opens for everyone, every view says one wording, and the record says what rb saw happen and what it recorded afterwards.
- rabbitbrain.ai is open.
rb loginandrb syncwork for anyone who signs up.rb sync --with-recordnames the code where the repository’s origin is an https address, with any credentials taken out, so a reader who reruns a shared record gets the code with it; otherwise it names none and says so. Plainrb syncnames no repository. - One rule for the code’s address.
rb exportandrb syncname the code only at an https address a reader can fetch, with credentials, query and fragment taken out, and otherwise say in one line that the bundle names none (data.repo_unnamed). - What rb saw, and what it recorded afterwards. Each run, analysis, call and finding says whether rb saw it as it happened or recorded it afterwards (
witnessinrb status --json). One line says it for the record, on the page, inrb open,rb status,rb contextandrb ui: “Kept with rb since 2026-10-28. 2 runs and 1 call were recorded afterwards, not as they happened.” (the page andrb openwrite the day as 28 Oct 2026), in place of “Recorded since ..., what came before it is Retrospective”. A run recorded afterwards says so in the Runs table and on its card, and a finding counts the runs it rests on that were. - One wording in every view.
rb status,rb contextandrb uisay “Rules: 1 met · 1 can’t tell yet”, andrb status,rb contextand the pagerb exportandrb syncwrite say “Needs judgment” (rb pageandrb opensay “Needs your judgment”). Commands, flags,--jsonkeys and MCP tool names are unchanged. History, the cards and the model name a run, an analysis and a call the same way: an analysis with no title reads by its experiment and the minute it was recorded (analyses.{id}.named). - Words.
rb freezesays a rule a session declared is locked now;rb confirmnames the notes a design leaves not confirmed, in--jsontoo;rb logand a setting’s history say each op in words; a cited value that differs is said where it is set, by the CLI and the SDK alike; a miss with a call proposed on it says the call waits. - Reconcile carries the command. The
rb run addlinerb reconcileproposes for a file a watched command wrote carries that command, so the run’s command reads as observed. A recipe tells runs with the same label apart. - The rerun prompt says what the bundle holds: whether it names the code, and whether it holds the record.
- Sources read again.
rb openandrb sync --watchread each source again at each look, andrb uinotices a source rewritten with as many bytes. - Windows (still a preview, #2): a clone counts its changes from its record’s commit, however late git dates it; the code’s address is said with
/, with git’s own error rather than a fragment of its advice; rb’s generators write UTF-8 on any terminal. - The README and the manual lead with
pip install rabbit-brain. - rb no longer infers a provider for Gemini CLI, which it does not detect.
0.6.0 (2026-10-28)
Section titled “0.6.0 (2026-10-28)”The record is plain YAML in .rb/, rb reconcile compiles what the repository already says, every write records who made it, and every finding is read on its axes.
- Research overview with detail on demand. The exported page and local browser open on compact inquiry summaries. Tabs separate the work’s views, and findings, experiment matrices and provenance expand when requested. A deep link opens the corresponding tab and record. Exported pages remain self-contained and work offline.
- Select the evidence and declare its units. Criteria can select shared and per-arm per-run settings. Fixed design factors report cell coverage and ranges instead of sampling intervals across design choices. A merge that brings a declared unit to the arms it separates reads as an integrity problem (
unit_separates_arms), with the repair inrb status. Explicit reversal relationships show both comparisons, their alignment and uncertainty, and travel with question decision snapshots. - Each inquiry reads its own evidence. Criteria can be filed directly to questions with
rb criterion add -q <question>orrb criterion file <criterion> <question> --why "...". An optional--when-met "..."keeps the author’s interpretation of a met rule, with attribution and its own declaration time. Question readings separate direct evidence, descendants and unassigned criteria; missed rules never imply the inverse, and integrity problems prevent an interpretation from becoming active. New question calls keep that scope and become outdated when its evidence, interpretation or integrity changes, without being moved by unrelated results in a shared experiment, rb’s own wording or a move of the question. In a record where no criterion is linked to a question or related to another criterion yet, a question call is made on every criterion filed under the question and the questions below it. Revisions and withdrawals keep their history, frozen changes require an amendment, and clones inherit the structure without the parent’s calls or evidence. Two branches that file one criterion to one question, or relate one pair, merge into a record that reads with neither declaration active (question_link_ambiguous,relation_ambiguous), and one filing or relation repairs it. rb’s own next steps, items and guides link each criterion they offer to its question (-q), and once any criterion is linked or related,rb statusgives the agentunassigned_criterionfor each criterion filed under a question and linked to none, with the filing that links it. CLI, Python and MCP share the same operations, andstate.relations(<criterion>)reads a criterion’s relations in Python asrb showreads them. - A question’s counts are its linked criteria. In
questions[].rollup,rules,resultsand the criteria inuntestedcount only the criteria linked to that question (-q,rb criterion file), so a criterion only filed under it, or linked to a question below it, no longer counts there;experiments,children,open_childrenand the planned experiments inuntestedstill count the work filed under it and below it, andrb statussays the two apart aslinked hereandbelow it. - Retitle a project.
rb project title "<new title>" --why "<reason>"changes the title while keeping the project’s identity, evidence and calls. The log keeps the old and new titles, who changed it, why, and any words passed with--words. The same write is available asstate.set_project_title(...)in Python andproject_titleover MCP. - Optional usage analytics. Off until
rb telemetry enable. Limited, inspectable PostHog events measure operations and research-record milestones across the CLI, SDK, MCP, TUI and local web viewer. No research content or command arguments are sent.rb telemetry previewshows pending events;rb telemetry disableremoves local analytics state. UI polling, tutorials and CI do not count as engagement. See PRIVACY.md. - Prebuilt wheels for Linux x86_64 and aarch64, macOS Intel and Apple Silicon, and Windows x86_64. Each wheel carries the executable, SDK and TUI; installing one needs no Rust compiler.
- Many branches and worktrees, one question. Agents fork worktrees and branches freely; the record is built for it. Whatever is added to an experiment is a file of its own (
variants/<name>.yaml,notes/<id>.yaml,criteria/<id>.yaml, besideruns/andanalyses/; the project’s notes in.rb/notes/), so parallel work merges without a conflict; an experiment kept the earlier way (variants and notes inexperiment.yaml, criteria in onecriteria.yaml) reads as it is and moves on rb’s next write to it. A comparison counts a run while the arms it compares are as they were: a variant added on another branch, or an ablation added afterwards, does not move other arms’ runs to an earlier spec. A record git merged from two sides rb wrote is rb’s own when both sides are kept, and named (merged_outside_rb, with adoption first) when the merge dropped one; an adoption takes an author from either side and never a forged one. Ids carry six random characters; two branches that mint one id are named (id_collision), andrb doctor --restoregives the second record its own. Every run keeps the git tree of its code underrefs/rb/snapshots/, so squashing or deleting a branch does not lose the code a receipt names. The journal lives in the clone’s shared git directory by worktree, so what a removed worktree’s commands printed is still found.rb statussays what is elsewhere: for each other worktree and unmerged branch, what it adds to the record. Branches and worktrees are context on a run’s receipt, never the record’s structure. - A worktree’s runs keep its own code. A git worktree nested in the checkout, with no record of its own, writes to the checkout’s record, and a run’s or an analysis’s receipt and
rb journal runtake the worktree’s commit, branch, uncommitted changes and exact code;rb journal runruns the command there. Every write from a linked worktree keeps its path (worktree, in the data alone). Other worktrees nested in a checkout are not its code, nor files its journal watches, and a submodule moved on is not a large file. A clone that lacks a run’s tree says so. rb’s reads leave git’s index as it was. In a nested worktree,rb statusandrb contextsay once where it writes. A worktree outside the checkout with no record is told where the project’s record is and how to bring it here, andrb initthere needs--new-project.rb statusnames each place elsewhere by its branch and path, says when a worktree was removed since, and tells a separate project apart. A worktree name git gives again no longer shares the earlier worktree’s journal, and a moved worktree keeps its own. - See every worktree from your own screen.
rb opensays which place it shows, by its branch and path, and its Where and Version pickers show another worktree’s record or a past version of the record (a commit that changed.rb/, by its commit and time), read only: nothing is written there, and a call that waits there shows the one command that confirms it. A past version is read from a checkout rb keeps in its own folder of the clone while the screen is open, and takes away when it stops.rb uisays the same andwswitches;y,nandcact only where it started. A run recorded in another worktree says where, and a finding that counts one says so once. Each place is named in short, by its branch and its path from the main checkout or from~, with the whole path on hover, and a file inside a nested worktree by its path there. While another place or a past version is shown, a bar says so in words, with the way back, and the address keeps the place, the version and the tab, so a reload, Back and a bookmark show them again. Where a run was recorded links to that place, the home lists the other places with their own counts and the calls that wait there, and where a reading’s runs ran other code, rb says which, from their receipts (a page by commit alone). Nothing is summed across places, andrb pageandrb exportcarry none of these labels: a file inside a nested worktree is named there by its path inside it, as in another worktree. - rb asks before it changes a file that was there.
rb initmakes the agents’ instruction files that are not there. AnAGENTS.mdor.mcp.jsonthat was there changes only once you agree: in your own terminal rb asks once, showing exactly what it would add, and No is the default;--yesanswers up front for a script. Anywhere else rb leaves the file as it is and says what to add and the one command that adds it. An edit keeps every byte that was there, in the file’s own line endings and indentation:.mcp.jsonis never reformatted. A skill or Cursor rule at rb’s path that rb did not write stays as it is.rb skill add(--agents-md,--claude --project,--cursor) andrb reconcile --apply(now with--no-agents) follow the same rule. - Leaving rb.
rb init --dry-runlists every file and integrationrb initwould write, every file that was there it would change and how, and what later commands add, writing nothing; a write a link takes elsewhere is named with the file it lands in, and one outside the repository waits for your yes. rb keeps an ownership ledger of every write it makes outside.rb/(<git common dir>/rb/owned.jsonl): the instruction files it installs, what it adds to a file that was there, with the file’s sha256 before and after, its pages and exports, the refs that keep a run’s code and its own folders.rb page --outandrb export --zip --outnever write over a file rb did not write, nor rb’s page, export or export folder once someone changed it or added a file to it: such a file is refused and left as it is, unless--replace.rb removetakes rb out of a checkout, and only what its ownership ledger says rb wrote there, as rb wrote it (rb’s text a teammate committed stays): it shows the plan first (what it will remove and how it knows each is rb’s, what it will keep and why, what is not rb’s to touch), says when the record holds writes no commit keeps, names what git’s index holds staged of rb’s with the command that unstages it, and asks once in your terminal (--yeselsewhere;--dry-runremoves nothing). It never deletes a git object, a worktree, history or a research file, and it is refused whererb confirmis. A removal stopped midway is finished by running it again. A round-trip test holds it: afterrb init, an afternoon’s work andrb remove, every file with its time, the refs, the objects git reaches, git status, HOME and TMPDIR are as they were before rb, but for directory times and the objects only rb’s refs reached, which rb names and leaves for git’s gc. - rb is one program, on Linux, macOS and Windows.
pip install rabbit-brainbringsrbas a compiled binary, with the Python package beside it for the SDK (import rabbit_brain) and the terminal screen (rb ui), which ask that binary for everything: the command line, MCP, the SDK and the screen read and write through the same code. It reads YAML, JSON and TOML configs itself and asks git each question once per command. On Windows paths are written with/, in the record and in what rb prints, and a path typed with\is the same path; a watched command (rb journal run) writes to a pipe there, not to a terminal of its own. A record made on one system reads on another:.rb/.gitattributestells git to leave the record’s files as rb wrote them, and where git converts line endings (Git for Windows does by default) a text file checked out with\r\nis the file rb recorded with\n, so a setting’s source, a run’s files and an indexed folder do not read as changed. - Every failure is said one way.
rb <command>: <what happened>, then a line that says what to do (a command line that does not parse shows the usage first); with--json, the same inerrors[]. When the system under rb is what failed, rb’s sentence names the file or the address and carries the system’s own words for why (Could not write shared/page.html: Permission denied, with the directory to check). A fault in rb’s own code isE_INTERNAL, exit 3, and asks to be reported; so is an install that cannot run a part of rb (the screen without its Python), which says what to install. Every other failure exits 2, and a tour that stops exits as the step that stopped it. Where there need be no failure there is none:rb page --outmakes the directory it is asked for,rb journalpasses over an entry it cannot read and counts it,rb initstarts the record when an agent’s instruction file cannot take rb’s section (it says which, and leaves the file as it is), andrb logoutremoves the credential here whatever stops the revoke. A credentials file that is not one holds no credential forrb sync;rb logoutsays it cannot look in it, and leaves it as it is. A port out of range (rb open --port), a hub address that is not one and an argument that is not UTF-8 text are usage errors;rb clonesays a bundle path that is not there as a missing file, and what is wrong with a bundle it fetched of the address it was given; a missing credential names the hub that was asked; an address that answers, and not as the hub does, is said as that.rb show <file>gives a file of an odd shape its problems, field by field. Over MCP, params a method does not take are-32602with a sentence, and a tool’s arguments are an object. Rabbit Brain YAML refuses a number too large to hold (1.0e+999). - Versions before 0.6 were a different tool; their commands and records are not carried forward.
- The agent contract.
bench/agent/is the executable definition of an rb-compatible agent: a spec of nine invariants (record the moment that mattered, skip the chatter, keep the user’s words verbatim, never manufacture a finding, reconcile before finishing, notice a failed check, use the grammar, cite files, start honestly where the work already is) and nine cases, each a synthetic repository with rb installed the way a user installs it, a short session, and checks read from what the agent left in.rb/and what it said.python bench/agent/run.pyplays them with scripted agents;--agent-cmdplays them with a real one, behind a countingrb. CI holds the contract to account: the ideal agent passes every check, and each careless agent fails exactly the checks it is written to fail. - The grammar: the same work, recorded the same way. A rerun for variance is another run; a changed hyperparameter, a variant; an ablation, a variant with role ablation; an invalid run, a new run that replaces it (
rb run add ... --replaces <run> --why "...", which retracts the old one, keeps it on record and links the two); a different approach, a new experiment thatfollows_fromthe old. In SKILL.md, AGENTS.md and the contract. rb journal runreads what a command reports of itself. The output passes through as it comes (a pty in a terminal; with--json, on standard error so the envelope is alone on standard output). A last line{"metrics": {...}}is kept with the command, andrb reconcileproposes the run that records it, with the command, when it ran and the code it ran with, once an experiment reads those names;rb journal runsays which run it proposes, or why none. A failed command keeps its last ten lines, and a failing check shows them.- rb_delta: what changed since you last looked. In an agent session every command on the record ends with the facts the session must not silently forget, each told once: results not recorded, folders not compiled or changed, failed checks and audits, moved manifests, stale decisions, calls made on a failing line, edits outside rb, and what the user or another session wrote, each with what it changed, its reason and the user’s words. In
--jsonand over MCP asrb_delta, where a write’sbysays who made it; in text underSince this session last looked:. It never says what research to do. rb initsets up the agents it finds. The skill and rb’s MCP server in.mcp.jsonfor Claude Code, rb’s marked section inAGENTS.mdfor Codex and the agents that read it, a rule for Cursor: found from the repository’s own instruction files, added beside what is there, said line by line;--no-agentsleaves them alone. It says when.gitignorekeeps a file it installs out of git. Run inside an agent session it installs nothing, since that session would load none of it, and names the commands for later sessions. It ends with the one line to paste into the agent’s chat, the same line the README,rb uiand llms.txt give:Use Rabbit Brain to track this: read rb context, then record where the work stands.- Starting the record in a project already under way.
rb docs --start(the MCP resourcerb://start) prints a short start protocol for a session joining work already done: it asks the researcher nothing up front, takes the current frontier as its scope (the question actively worked on, with the experiments, runs, findings and open calls around it), inferred from the session’s context and the files and never asserted, records it first with each rule linked to the question its comparison answers (comparisons that share an arm in one experiment), checks memory by rules rb reads, reads runs from their stores, takes criteria only from documents (a criterion whose reason is a file and line alone is refused when that line does not state its threshold; a direction rule only where a document states the direction), records a dated decision a committed file states as a past call that waits for one confirmation and no call from memory, and ends with a report that says what it took as the frontier and why, points at what waits, and ends with no question. While the record holds no question in a repository with history (a commit beyond a new repository’s README, license and ignore files, a results folder or a database),rb statusandrb contextpoint at it (no_question); so doE_NO_PROJECT, andrb compileandrb reconcilewith no record. The agent contract gains abrownfieldcase with its acceptance checks. - The exact code behind a run. A run recorded or watched with uncommitted changes keeps the git tree of the files as they were: a digest anyone with the same files computes, written through a temporary index (the user’s index, branch and files untouched) and kept under
refs/rb/snapshots/as a ref to the tree, with no commit and nothing ingit log --all.rb show <run>gives the diff;rb run add --treerecords the code of a run made earlier. rb reconcilecompiles what the repository already says. Coding agents leave research in the repository: results folders with a README, a manifest of the source hashes that made them, analysis and audit files, large arrays kept out of git, audit scripts and tests, a reproduction guide, a commit.rb reconcilereads it against the record, experiment by experiment. Each results folder with a report is an experiment that points at its files by path and hash, with what git does with each; nothing is copied. A results folder a recorded run named (--from,--config,--artifact) is read by that run’s experiment, and a new number in it that rb saw land after its first look here, or one a command underrb journal runprinted, is proposed as another run of that experiment (a file already there, or one a checkout brought, is listed and never proposed), with each per-run value its own config beside it (passed as--config) or the line of the one command that wrote it gives, and a line that would need a value neither gives, or one they give apart, is never run by--apply; a folder of results files alone that nothing reads is listed as unplaced, never made an experiment (many such folders are one counted line, and the MCP tools keep the first 100 of each long list unless asked for all), and a file in it that rb saw land after its first look here is proposed as a run where exactly one experiment’s criteria read its numbers. The next step rb suggests namesrb reconcile --applyonly while it would add no experiment, and work from before the record began points atrb docs --start. Its reports’ title, opening lines and the sections their own headings call results, limitations and next steps are quoted verbatim with file and line, and so are the commands of the reproduction guide they link. A manifest’s hashes are checked against the code as it is now; an audit file is read for what it says of itself; the repository’s own checks (audit_*,test_*,check_*,verify_*) are registered with the command the repository gives for them, their outcome read from the last run rb watched; and each folder says whether it is clean enough to checkpoint.rb reconcile --applykeeps it in the record as whoever runs it, records the runs it placed without doubt, and logs one line saying what changed in the research, whichrb statusprints. From then on every read checks the repository as it stands:reindex,unindexed_folder,manifest_changed(thestalegate),audit_failedandcheck_failed(a newchecksgate). A decision on an indexed experiment is made on what its repository said and reads as made on an earlier reading when that changes, and a call that moves a line on while its own audit or check says failed is shown to the user (call_on_failed_check). A results folder’s numbers go only to the experiment that reads the folder, never to another on a metric name. rb chooses spans by the files’ own headings and words nothing itself. It needs no record to start: in a repository with no.rb/,rb reconcilereads it all the same and writes nothing, and--applystarts the record at the repository’s root, titled as its README titles itself.- A session’s reading reads as not confirmed. A note a session wrote (
rb note add, or a run’s--note), and an interpretation a session declared (--when-met), read as not confirmed on every screen and in rb’s text (· not confirmed,declared when met, not confirmed,Recorded interpretation · not confirmed), naming no writer; who wrote it and the session stay in the JSON, the log, MCP and the SDK. - A note’s words show beside it. The words given with
rb note add --wordsshow beside the note on rb status, rb context, rb show, rb open and rb ui, as words given (the user’s own when they gave them in their terminal), never on a page, and underwordson each note inrb status --jsonandrb context --json. So do the words given withrb experiment gate --words, beside the gate, and undergate_wordson the experiment. - One comparison, one number. Where a criterion reads a variant against the baseline otherwise than the experiment table’s unpaired means (paired by unit, or over runs a
--whereselects), rb compare, the page and rb open show that criterion’s reading in the cell, never a change of their own;rb compare --jsongives it asread_by, withunpairedsaying what the means are over. - A call is the user’s. A call recorded in an agent session (
rb decide, the SDK’sdecide, MCP’sdecide) is proposed, not made: questions, experiments, criteria and settings read as if it were not there, andrb statusandrb contextlist it apart from the calls (Proposed, not made), with its subject, action, reason as given and the one command that confirms it;rb openandrb uishow it in a quiet group of its own, and a page lists it as rb status does. The user confirms it with one command in their own terminal,rb confirm <id>(--allsays each call, then confirms them all; alone it lists what waits), orconrb ui: the same call, recorded as theirs, with its log line naming what it confirms, so the record’s format does not change; its reason still reads as the session’s note on it.rb confirmin an agent session is refused, and a write where a session left its id or entry point (CLAUDE_CODE_SESSION_IDwithCLAUDECODEunset) is that session’s, asrb confirmreads it.rb decide --past [--when "..."]records a call made before it was recorded, Retrospective and dated as given; from an agent session it is a past call mentioned, not confirmed, until onerb confirm, whoever ranrb initand whatever document it cites, since a session cannot attest the past it reconstructs. With--source <file>[:<line>] --quote "..."rb checks the document and says what it found beside the call, inrb confirm,rb showandauthority.basis: the quote is in that file and line, committed on a day before the record began, or why rb cannot tell. Until it is confirmed, a past call reads in the file’s voice: the document’s words verbatim with its file and line, and the reason a session gave only as a note on it, never as the user’s call; a past park or stop says which runs of its experiment were produced after the day it gives (runs_after).rb confirm --allleaves a subject more than one call waits on torb confirm <id>, andrb confirmsays what the call is made on now. Words given in an agent session read aswords (as given): "..."on every surface, never as the user’s, until the user confirms the call they came with. Each decision carries its computedauthorityin--json, over MCP and in the SDK;rb statusgivespendingbesidedecisions, and the open itemscall_proposedandpast_call_unconfirmed. - An experiment’s design waits for you. Its design is what it records of how it tests its question: its questions, hypothesis or purpose, why, variants, what it varies, settings with their sources, criteria and their interpretations, gate and freeze. One a session wrote any part of says
design not confirmedonce on its own views (rb show <exp>, its pane and row onrb open, its card onrb ui, its part of a page), with the one command that confirms it, andwhy: not recordedorhypothesis or purpose: not recordedwhere it has none;rb status,rb contextand a page’s summary say every such design in one line.rb confirmlists each part by part, andrb confirm <exp>@<hash>confirms it as listed, with its gate, in your own terminal only: one log line that changes no file. The listing, and the line under what waits onrb status, end with the one command that confirms all of it as listed, by id and hash, in place ofrb confirm --all, which confirms what reads when it runs. Its standing is read from the log alone, by a snapshot rule: it reads confirmed only while it is exactly a design you own, one you confirmed or one your own write made from a design you owned then (an adoption only where rb is sure what its files held before), which that write’s line keeps (detail.owned: a hash over rb’s structured form of the parts, so a later rb that words a part otherwise reopens nothing). A session’s later change reopens it, said part by part with what each was and is now against the closest design you own; your own changes to a confirmed design keep it confirmed. Once a session touched a design it waits for one confirmation, even after you rewrite what it wrote, and a merge, a clone, an older rb or a clock can only make rb ask again. A gate waits with its design while a session wrote a branch or its words as they read now, in a session’s voice until it reads as in a design you own, your branch or words beside them included; once you confirm it, it stays yours until a session changes it, and the interpretations a session gave its criteria wait again once a session changes a design you confirmed. A design that waits in a record waits in its clone (rb clone), until you confirm it there. Confirming changes no finding or reading. Where something a session wrote can wait for you, every screen draws a character a terminal would hide as its escape, asrb confirmlists it, and--jsonkeeps the text as written.designon each experiment,designs_pendingandcounts.design_pending_countin--json. An older rb lists the confirmation of a design with no gate inrb logas a gate line. - Preview a write.
rb run add,rb spec set,rb criterion add,rb decideand the 21 other write commands AGENTS.md lists take--dry-run(dry_runon the same tools over MCP and the same writes in the SDK, where it returns aDryRun). rb makes every check the write makes and exits as it would, with its refusal or its warnings, and writes nothing: no record file, no log line, no cache, no lock file, no git object, ref or index. It changes no file’s time, though a clean filter the repository gives git, such as Git LFS’s, runs as it does on any read. It says what the write would write, each record file with its content and the log’s new lines (data.would_write), and its text names no writer. A check that fails leaves no mark when it is previewed. A preview reads the record as it stands, so a write that rests on one not yet made is previewed after it. The bulk writes preview in their plain form, as before:rb reconcilewithout--apply,rb doctorwithout--restoreor--adopt,rb fmt --check. - Source checks read numbers and names as files write them. Digit separators are part of the number:
48_200, and LaTeX’s48{,}200and48\,200, state 48200 as48,200does, never 48 or 200. A name LaTeX and Markdown escape (seq\_len) namesseq_len, in a source check and inrb compile. - A row of a SQLite database is a run, read where it is.
rb run add <exp> --from results.db --table evals --where precision=int8,seed=1reads exactly one row of a database, told by its first bytes whatever its name: its columns that name a declared metric are the run’s numbers (--selectnarrows them), and--set seed=@seedand--produced-at @finished_attake a per-run value and the time the run was made from its columns (a database’s own times never date a row). No row, or several, is refused with how many and the first of them, and nothing is written. The receipt keepsrow: the database’s path and sha256, the table, the row’s key and rowid, and the column each value came from; reading the same row again records nothing, however the database has grown, the same row read in a second experiment is warned of at the write and listed byrb status(row_in_several_experiments), naming both, and a row whose values changed since is refused, with--replacesor--againas the way on. After the spec changes,--replaces <run> --why "..."reads the same row or file again under the spec as it is now, with the same receipt;rb spec setandrb spec varypoint at it, and so does the warning on the same row given again without it. The database is opened read-only and never written, with no -wal, -shm or journal file made beside it; one that cannot be read without writing (writes still in its -wal, an unfinished write in its journal) is refused.rb compilelists a database’s tables, their columns and row counts, and the columns that name a declared metric.exp.record_from(...)in Python andrun_addover MCP read a file or a row asrb run add --fromdoes. A setting is verified from one column of one row as from a file’s key (--source results.db#configs[config_id=c1].width), and--set seed=@configs.seedreads a per-run value from the row of another table that holds what the run’s row holds in the column they share, the receipt naming that row. SQLite is built into rb. - What a run rests on, said as such. A command given for a run made before it was recorded reads as a command given later, not observed, unless
rb journal runwatched it make the numbers or--command-source <file>:<line>cites a log line written by then that holds it;rb run addsays so,command.givensays how rb knows in--json, andrb statuscounts such runs (command_later). A file written after its run, an export, reads asfile written <date>, after the run, never as the run’s own output (file_written_after).rb showandrb pagesay when a run was produced, as reported, beside when it was recorded, and the repository’s state as the state when it was recorded; the page’s receipt is lines, not a stored dict.rb status,rb context, a page,rb openandrb uisay in one quiet line when the record began, after how many of its runs, and that what came before it is Retrospective. Words an agent writes about its own writing are pointed out and counted (writer_words), never refused. An experiment’s late criteria are one item, which asks for no run where none is expected. - The next experiment carries the decision it gates.
rb experiment add --if-holds "..." --if-not "..."(if_holdsandif_notover MCP and in the SDK) keeps what happens either way, in its author’s words, andrb experiment gatesets or revises it later, the earlier words kept in the log.rb statusreads as the state of the research under each question: the hypothesis and the gate of what is planned, what the files say of what ran, each experiment’s latest note, and the calls made on the work with the user’s words.rb show,rb context,rb openandrb pageshow the gate too. - Findings are read on axes, never in one word. A criterion is a rule over one comparison (subject, reference, metric, estimand) with a role (primary, secondary, exploratory, gate). Its finding says, computed on every read: the comparison with its interval and units; the result (gain, loss, unresolved, equivalent, point only, measured); whether the rule meets, misses, is undetermined or untested; the timing (declared before the evidence, after it, after the data was seen, or in an order nobody knows); how many independent units it rests on; every integrity problem with what it depends on; the latest decision on it and whether it was made on the reading as it stands; and the readings it replaced. The headline is
rule · result(criterion met · loss,gate missed · point only). rb reads the runs itself: a paired difference per unit, averaged within units, then over units, with a Student t interval from 3 units (Welch when the arms never share a unit). Thresholds read the estimate (--at-most,--at-least,--equals;--eachfor every unit,--boundfor the interval’s bound);--better,--noninferior Mand--equivalent Mread the interval. A missed gate is a priority call, never a refutation. - Units, not rows. Runs with the same per-run values are one unit;
population.unitnames the setting that defines units (2 photographs with 4 noise cases each are 2 units);--repeats <run>records a check of a run, averaged with it.--min-ncounts units. A run’s identity is its provenance: the same output files are one run; equal numbers from two executions are two runs. - Analyses.
rb analysis add <exp> --compare "a/b:m@estimand=est[lo,hi]" --method --unit --units --reads --input --sourcerecords a computation over saved outputs and the comparisons it produced, checked against the document that states them. An analysis that reports a comparison is read in place of the runs, and rb’s own reading stays as history; a corrected one--supersedesthe old with a reason, and every earlier reading stays on record. What an analysis rests on is traced every step back, and one that counts more units than the runs it read form (by the declared unit) is an integrity problem. - Timing and exposure. A criterion is declared when it is written, by whoever wrote it. Its timing compares that with when its evidence existed (
--produced-at, the journal, git, the file’s time, or the record time, and it says which) and with when its evaluation data was seen (rb resource expose, or another experiment evaluating on the same resource). Evidence it could have been fitted to counts even after a spec change.latefails when a primary criterion is not shown to precede what it reads. - Decisions. A decision (
rb decide <subject> accept|reject|investigate|continue|park|stop|pivot|ship --why --revisit) records the reading of every finding it was made on; when a reading moves, or a new one appears under it, it says it was made on an earlier reading. - rb tracks who did what. rb never refuses a write because of who makes it. Freezing, amending, deciding (accept, reject, investigate, continue, park, stop, pivot, ship), vouching for a setting, retracting anything and
rb doctor --adoptwork for anyone and are recorded as whoever made them; a call recorded in an agent session waits for the user’s confirmation (above). Attribution is the integrity: every write records who made it (human:<name>oragent:<name>), the chat session when the runtime exports one (on every log.jsonl line, on decisions, on run receipts), and the reason as given (--why); inside a detected agent sessionRB_ACTOR=human:<name>is ignored, so the record shows who actually did what. When an agent acts on the user’s words from the chat, it passes them verbatim with--words "..."(on every command that takes-m/--why, and onquestion add,experiment addandnote add;wordsin MCP tools and in the SDK’sdecide,retractandfreeze): they are kept beside the reason on decisions, freezes, amendments, retractions and criteria, and on the log line, and shown asyour words: "..."once they are the user’s (above). Nothing is paraphrased.rb statusitems carrywhoas a hint of who usually acts, not a permission: calls on results and on lines of work (undecided, gate missed, decision outdated, investigating) are the user’s, setting items the agent’s, and drift, changed cited sources and state from a newer rb anyone’s; an agent records a call when the user asks for it, with their words. - Which agent made each write, and on which model. An agent’s write records
agenton its log line and, for a run or an analysis, on its receipt: its name, its version (fromAI_AGENTor the MCP client’sclientInfo.version), its provider (as it said it, else inferred for Claude Code on Anthropic’s own API, Codex and Gemini CLI) and its model, only as it said it:rb context --model <id>(or the context tool’smodel) for the rest of its session, orRB_MODELfor one command. With no session id rb keeps no model. A session’s agent facts get onesessionline in the log, for each writer and channel in it, when they are first known or change; it records no write, and rb versions before it read past it.rb_deltasays once how to give the model while none is recorded,rb contextsays it in every briefing until one is, and--jsongives the actor withagent. Cursor’s agent (CURSOR_AGENT=1, its chat inCURSOR_CONVERSATION_ID) and Codex known only by its thread or session id are recorded as agents. A human’s write keeps a session only whenRB_SESSIONnames one, so a Cursor terminal’s trace id is no longer taken for a chat. An adoption can no longer change a receipt’s actor,actor_via,via, session oragent, nor theviabeside who wrote a record. - Who made a write is in the data, not in rb’s words. rb’s text and every sentence it builds name no writer, a human or an agent, so a missing name never marks an agent’s write: a call reads as made, when and why (
accepted 2026-10-10: within budget), a criterion as declared before or after its evidence, a line ofrb logas what changed. No session id, channel or detection source is in them either, and no branch or worktree is named as a write’s:rb statusnames each place elsewhere by its branch and path, as rb names a place in the labels it computes for one; what a command printed in a worktree removed since is found as such, with no worktree’s name. The record and the fields of--json, MCP and the SDK keep all of it;rb doctorstill says how this shell’s writes are recorded. - Publishing the record is a choice.
rb exportandrb syncpublish the page alone, with a manifest hashing it: no record, no file a check reads, nobundle.zipand no Reproduce panel withrb clone.--with-record(with_recordin the SDK’sexportand the MCPexporttool) carries the record too, as the bundle rb wrote before, and rb says then what the record keeps: who made each write, an agent’s chat session, name, version, provider and model, the user’s words passed with--words, and each run’s host, platform and branch; the bundle’s README says it too.--urltakes--with-record, since there is nothing to clone without it;rb cloneof a page alone says it was exported without the flag;--jsonsays which was written (with_record). - No screen names a writer either. The page
rb page,rb exportandrb syncwrite,rb open(with the/state.jsonand/card.jsonits tab draws, where each field that kept who wrote now holds"", whatever the record keeps there) andrb uiname no writer, session, channel, agent or branch, and use no word for a role: runs have no “recorded by” column, a relation’s declarations print what has a value, “For your agent” is the Next step, and what is not the user’s call is “still open in the record” (open in the record · Nonrb ui, whoseakey opens that list).rb uicounts what was written since the user last looked, whoever wrote it (Since you last looked: 3 writes, 2 runs recorded · last <day>), and nothing before their first look, and itsyprompt says what a yes records, not as whom. The user’s words show onrb openandrb uiwithout theyour words:tag, and never on a page, which anyone may be sent; nor does a page show the platform a run’s receipt keeps, whichrb openshows. The screen refuses an agent session asrb uidoes when it is started withpython -m rabbit_brain.tuitoo. - Plain record text. rb shows the text anyone writes exactly as written, on every screen, so the manual and the contract
rb compileprints ask for plain text (a statement lifted from a report keeps its words and drops its markup): Unicode symbols (≥, ×, Δ, σ, ∼) instead of LaTeX, a macro’s name spelled out, no citation keys or table and figure references, and no name of the agent or tool that did the work. After an agent’s write whose new text holds LaTeX, rb says so in one line that names the field and what to write instead: after the write’s text, amongdata.warningsin--jsonand over MCP, and as arabbit_brain.RBWarningfrom the SDK, which now also warns of whatrb run addandrb analysis addwarn of beside a write (an input whose copy here changed, numbers typed with no document, numbers that differ from the run already recorded with the same output files). The user’s words, quotes of files and commands as given are never flagged, nor is a price, a path, a shell or Make variable, a regex or a code span; a human’s writes get none, and rb never refuses or changes the text. - A project is titled for the work’s theme.
rb init "<the work's theme>"names what the work shares (a paper’s title, a model, a line of work), and each inquiry is a top-level question with the experiments that answer it. rb init’s help and the next steps it prints (rb question add, thenrb experiment add -q),rb statuson an empty record,rb context,rb compile, theE_NO_PROJECTfix, the tour, the skill and the docs say so. - Questions are a tree.
rb question add --parent,rb question move, and an experiment filed under several questions (-q q1 -q q2,rb experiment file); a question’s state comes from the decisions on it, and its roll-up counts the findings linked to it by rule and by result, and the sub-questions and planned experiments below it. - Integrity is local and forged authority is none. A record changed outside rb is an integrity problem of the findings that depend on it, and only those; the rest keep their readings. A decision rb did not write is nobody’s call, and an adoption can never change who wrote or decided something (
E_ADOPT_AUTHORITY); a run deleted outside rb is named by the findings it fed;rb showof an edited record says what changed. - The record is plain YAML in
.rb/, one file per record..rb/project.yamlandnotes/;questions/<id>.yaml;experiments/<id>/experiment.yamlwith a file per variant, note and criterion undervariants/,notes/andcriteria/, per run underruns/and per analysis underanalyses/;metrics/,resources/,decisions/;log.jsonl; and, local and gitignored,cache/. Every file opens withrb: 1and then itskind. The files keep to a strict subset of YAML, Rabbit Brain YAML (mappings, lists, strings, numbers, booleans, null, comments; no anchors, aliases, tags or merge keys; a value YAML 1.1 and 1.2 read differently must be quoted), read and written by rb’s own parser, fuzzed against PyYAML. Comments survive rb’s rewrites, and a comment or a reformat is not an edit: tamper detection compares each record’s content hash.rb fmt [--check]writes the canonical form;rb show <file>.yaml --jsongives canonical JSON and the hashes log.jsonl records. - One vocabulary, everywhere. The record’s words are project, question, experiment, variant, setting, criterion, run, analysis, comparison, result, resource, metric, finding, decision, note and relation: the same words in the files, the JSON Schema, the CLI, MCP, the SDK, the docs and every screen. A hypothesis is what an experiment tests, and a direction nothing has run yet is a planned experiment; an assumption is a note.
- The format is a published contract.
rb schema allprints format 1 as one JSON Schema, committed asschema/rb-v1.schema.json(its$idis https://rabbitbrain.ai/schema/rb-v1.schema.json), with a test that fails when it drifts from what rb enforces. - Resources: identity, location and availability are separate.
rb resource add <id> --type dataset --name "..." [--version --file --sha256 --digest --at PROVIDER:REF --url --recipe]records what a dataset, checkpoint, code or artifact is and where its bytes have been (local, url, git, W&B, S3, GCS, Hugging Face, RunPod);rb resource locateadds a place. Availability is computed on every read: present, changed (a copy is here, not the bytes named), remote, reconstructible, lost. Identity is as exact as what was recorded, and says so: exact (a hash or digest), version, or name only. Deleting a 100 GB copy never loses what the benchmark was. - A run pins what it touched.
rb run add --input RES[:ROLE] --output RES[:ROLE] --host provider=runpodrecords, per resource, where it stood and the bytes of the copy here when the run was recorded; a copy whose bytes differ from the resource’s identity is recorded as found, with a warning. Every experiment says what a rerun would need and where each piece stands (code, environment, machine, resources, files), and a dataset that changed after the runs read it is named as such. - Experiments carry their story. Purpose, hypothesis and why; notes, verbatim with their writer; relations to other experiments, shown from both sides; a computed state (planned, open, or what the latest decision made it: accepted, rejected, investigating, parked, stopped, pivoted, shipped). Experiments, runs, analyses and resources carry global uids.
rb statusanswers where the record stands on its own: the questions and their findings, then what needs a human and what an agent can do (each item saying who usually acts), what the experiments depend on, what ran (and whether every receipt is what rb wrote), and the project’s notes. A long list inrb statusorrb contextnames its first or latest ones and counts the rest, with the command that lists every one, so thousands of results folders or hundreds of runs keep the first screen readable;--jsonkeeps every item. In the user’s own terminal, the Next line namesrb confirmfirst while a call waits on them. Its first line says the project’s title once where the title is the project’s id, and a count of two reads as both (after both its runs were made,both units), of one as the only one, neverall 2. A test cuts the network and removes any account, then runs status, show, compare, context, log, doctor, env check, fmt, schema, page, export, reconcile and clone: none of them may need rabbitbrain.ai.rb openonly reads: every POST gets 405, and there is no launch key. What rb found in the repository and nobody recorded shows as facts, not requests: under “Your call” inrb uiandrb page, under “Found in the repository” inrb open, beside how many calls wait on you, whichrb statuslists under Needs a human. The list of the calls waiting on you, onrb openand the page, names each one once, by what it asks and the title of what it is on, linked to the deepest question that lists it, so it holds as many as the count says.- Bundles are
rb-bundleversion 2 (the record inside is.rb/, format 1). A bundle’s page is computed from the bundle itself, carries the documents its analyses were checked against, andrb export --with-recordnames the resources it leaves behind and says what the record it carries keeps, which anyone who can read the bundle can read. A clone keeps each setting’s source (verification is redone here withrb spec verify), gives every experiment a new uid and areproducesrelation to the parent’s, and keeps resources’ identities while leaving another machine’s paths behind. A bundle holds at most 512 MB, zipped or unpacked:rb exportrefuses one that would hold more, naming its largest parts, andrb cloneone that does, and whatrb clone,rb exportandrb synchave half made is taken away when Ctrl-C, SIGTERM or SIGHUP stops them; a clone keeps its parent’s address without the credentials, query or fragment it was fetched with, and a published fork’s page links the parent only at a web address. - rb never publishes. rb and rabbitbrain.ai are like git and a code host.
rb publishfails withE_NOT_AVAILABLEand says what to use.rb exportwrites the page you can host anywhere, and with--with-recorda bundle thatrb clonereads;rb loginandrb syncare an optional bridge to the site, where sync puts a private copy of the page (and of the record with--with-record) in the user’s account and making it public happens on the site, from the record’s page. The hosted service is not open yet: until it is,rb loginandrb syncaddressed to rabbitbrain.ai say so (E_NOT_AVAILABLE), send nothing, and name what works without it (rb page,rb open,rb export --with-record); a hub named with--huborRB_HUB_URLis still called.rb --helpgroups the commands to match: “Calls” (freeze, decide, retract), “Carrying it” (export, clone, env), and “rabbitbrain.ai (not yet available)” (login, logout, sync) last. - Agents are told when to record. The MCP server’s instructions say to record when the research state materially changes (a question or hypothesis introduced, revised or dropped; an experiment or control proposed or run; evidence that changes a conclusion; a baseline, variant or metric established; an important uncertainty; the next experiment or a decision chosen) and not implementation chatter, to point at the repository’s own files rather than copying their prose, and that
rb reconcilefinds results that landed unrecorded. The packaged skill (rb skill) says the same in about fifty lines and defers torb docsfor the rest. - Where rb looks, said where it reads.
rb status,rb context,rb open,rb uiand the page say where rb looks on its own (out/,outputs/,results/,eval-out/,metrics/and any folder a recorded run named, the files in the repository’s top folder, and the .json, .yaml and .toml files git shows as new or changed), when it last looked, that it reads what code, configs, docs and database rows say only where the record points to them, and that it sees only the commands run throughrb journal run;rb reconcilereads the same list. Where git’s folder cannot be written (an agent’s sandbox can keep it read-only while the project stays writable), the journal and the ownership ledger are kept beside the record in.rb/and that line says so; the first look that can write git’s folder moves them there. - Changes nothing in the record cites. A .json, .yaml or .toml file outside the results folders rb reads that changed since the record’s start, or since the last
rb reconcile --applyin the user’s own name, and that no setting, run, analysis, criterion, call, resource or indexed folder cites, is listed byrb reconcileby path with the commit the count starts at, and counted in one line onrb status,rb contextand the user’s screens (uncited_changesin--json). rb says what changed and that nothing cites it, never whether it matters.rb reconcile --applykeeps their paths on its line, so they are recorded while their bytes are those. - Recovering what nobody recorded, honestly.
rb reconcilesays where it looked, never proposes a file of progress numbers as a run, nor one that was there whenrb initfirst looked (that is the work the record began after), moved or copied from it, or put back from git by a stash or a restore (a result rb saw land keeps that look through a stash or a checkout, and is still proposed), and the journal shows when a file was last written, not only when rb saw it.rb compilelists files matched by metric name as candidates with when each was written, never as a deterministic fact. rb tourruns on a clean install and in the loop’s order: the criteria are declared and frozen before the runs write their outputs.- Fail closed, in more places. A record file only a newer rb reads is named and never read, written over or restored, and this rb writes nothing while one exists; so is a declared relation of a kind or orientation only a newer rb knows, while the rest of its record still reads, and
rb clonerefuses a bundle that holds either rather than leave it behind; an unreadable criteria file stops its criteria withE_STATE_CORRUPTinstead of hiding them; a second copy of a record in another file is caught as written outside rb, set aside byrb doctor --restore, and never adoptable; retracting an experiment keeps its criteria in every gate, andrb statusgives each of them the one repair it takes (experiment_retracted:rb retract <criterion>), never a command that writes to the retracted experiment. - A hand edit is named as one. Format 1 is fixed: a later rb that adds a field writes a new format, which an older rb reads as needing an upgrade. A field or value rb does not know, in a file that is not what rb last wrote, is said as edited by hand or written by a newer rb (still
E_STATE_NEWER), naming the file, a part’s own where an experiment is read with it, and the fix offersrb doctor --restorebeside the upgrade where rb holds its last write;rb doctorsays the same, once,rb opendraws the fix under the message (fixbesideerror), andrb uigives the terminal back and says both. - A misspelled metric is named, not refused.
rb criterion addwith a--metricthat no run or analysis reports and the catalogue does not hold, close to a name one of them does (change.val_losbesideval_loss), says so in one warning line naming that name (data.warnings, and an RBWarning from the SDK’scriterion()); the criterion is declared as given, since its metric declares the name by use. Declared before any run, a run under the close name says the criterion likely misspells it, andrb statusgives it the retraction as its step, never the misspelt name. - A run that counts toward nothing says so.
rb run addwhose numbers no criterion of the experiment reads (a flat{"val_loss": ...}where the criteria readfp32.val_lossandint8.val_loss) records the run and says in one line that it counts toward nothing, with the names its criteria read and the shape a file gives them in,{"fp32": {"val_loss": ...}, "int8": {"val_loss": ...}}, or--variant <name>for one variant’s numbers; the SDK’srun.logsays the same as an RBWarning. The README shows that shape. - After
uvx, the way to keep going.rb tourrun byuvx --from rabbit-brainends with how to run its next commands whilerbis not on the path (uvx --from rabbit-brain rb ui, oruv tool install rabbit-brain), and the README says so after theuvxroute. - Third-party notices ship with rb. Each wheel, the sdist and the GitHub release carry
THIRD-PARTY-NOTICES, the licenses of the crates built into therbbinary.
Rabbit Brainrabbitbrain.ai · GitHub · PyPI · Privacy · Apache-2.0