Skip to content

Changelog

This page is rb’s own CHANGELOG.md, every release in full, newest first.

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: 1 and holds one of the four is refused (E_STATE_CORRUPT: “is format 1 and holds over: count, which only format 2 has”), and rb show of 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: 2 by hand that holds none of the four, and its next write of that file puts back rb: 1.
    • rb export --with-record writes record_format: 2 in the bundle’s manifest when a criterion’s file is in format 2, else 1 (every earlier rb wrote 1); the bundle stays rb-bundle version 2. rb clone keeps each of the four whole, in format 2, and refuses a bundle whose record_format is above 2.
  • Four new kinds of rule. rb criterion add takes 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-most or --at-least, or, for all but a best of N, --equals with --tolerance), and each is said in the same words by rb criterion add, rb show, rb status, rb context, the SDK and MCP.

    • A count. --count K reads whether at least K units meet the threshold, judging each unit as --each does, and --of N says how many units the rule is of: --metric int8.val_loss --at-most 0.890 --count 4 --of 5 declares “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-most or --at-least, reads the unit best in the threshold’s direction against it, and --of N says 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 --relative declares “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” on rb show’s reads line, comparison.method in --json); with fewer it has none, and a met rule reads “estimate meets criterion”.
    • A cited difference of means. --means, beside --source and --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 --bound on their interval’s bound (“change.val_loss as a relative change, upper bound ≤ 20%”). With --each, --count, --best or 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-on gates 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 --of is refused (“a count rule’s count is at most the units it is of”), and an interval rule takes none of --each, --bound, --count and --best (“a ‘better’ rule already reads the interval; --each, --bound, --count and --best are for thresholds”).
  • 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 --where selects, 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 --each does.
    • 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 --where selects, 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.
  • One way to read a threshold. rb criterion add and MCP’s criterion_add refuse two of --each, --bound, --count and --best together (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 --bound as --each and dropped --bound without 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 . (acc and m.acc, never “Acc” or “M acc”). A name that ends in its unit, such as lat.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, never 0.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, never 49.690000000000005), and no digit given cut (1234.5679, never 1234.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-row records each row --where picks 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 --where that picks several rows is refused and offers it. Each run keeps the row’s rowid beside its --where key (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 --where columns alone is still that row.

  • One variant, one number. On an experiment of one variant, top1 and int8.top1 are the same number, so neither the write nor rb status says a run of it counts toward nothing. The line a run no rule counts gets names the number as the rule does, from rb run add, the SDK and MCP alike: “Its criteria read int8.top1: a run gives them as int8.top1=<value>.” for a rule on int8.top1 (the SDK says run.log({"int8.top1": ...})), and top1=<value> for one on top1. Both spellings are read.

  • Prose beside results files. rb reconcile quotes the prose a folder of results files holds (a Markdown file whose name is no report’s, such as pilot_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 compile names no column from a CSV or TSV table whose first line reads as a data row, and counts every row, as rb reconcile does.

  • An --each rule keeps its runs. An --each rule 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 --each rule beside an analysis, rb status gives 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 show names. rb show <criterion> names an arm’s number as the rule does (fp32.eval.top1 for a rule on change.eval.top1, never fp32.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 --bound rule’s estimate.

  • Repeats and selected settings. rb compare gives a declared repeat (population.repeat) no row of its own, and rb spec set <exp> population.repeat <setting> --no-verify is 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 ui and rb page say where rb looks in the words rb reconcile uses: “folders named for results (results/, eval/, metrics/, reports/, artifacts/ and the like), down to 4 levels from the top”.

  • Words.

    • rb status lists the items that ask for nothing under “Facts that ask for nothing”, and rb context under ”## 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> and rb context say 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 all prints formats 1 and 2 as one JSON Schema, committed as schema/rb-v2.schema.json (its $id is https://rabbitbrain.ai/schema/rb-v2.schema.json): a file’s rb is 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 --json and over MCP carries count, of and measure (null, null and "" 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(...) takes count=, of=, over="best" and measure="relative" or measure="means", and MCP’s criterion_add takes count, of, best, relative and means.
    • Over MCP, rb://format still serves format 1’s schema (rb-v1.schema.json), as rb 0.7 does, and the new rb://format-2 serves rb-v2.schema.json.
    • rb show of 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 --each rule 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 --each rule 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 undecided fails 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 --each rule, 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 undecided fails there.

    • A record with a format-2 file: in 0.7.0 and 0.7.1, rb doctor names each such file (“is format 2; this rb reads format 1: it was written by a newer rb”), rb status counts them, and both say to upgrade (pip install --upgrade rabbit-brain). They read every other file and fail every --fail-on gate. They write nothing to the record: a note, a criterion and a call are refused, and rb doctor --restore changes nothing. They refuse to clone a bundle that holds such a file. Their rb export still 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.

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 reconcile never 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 named metrics, scores, runs, ledger and the like) with the line that reads a row, and a report in prose (RESULTS.md, report.md, summary.md and 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 reconcile reads 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.
  • JSON Lines. A .jsonl or .ndjson file is a table, one row a run, picked with --where as a CSV row is. A nested object’s keys are joined with dots. rb compile lists its rows and numbers. A --where that 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-row records each row --where picks 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 --where that 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 (or seed,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 --quote and 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.
  • 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 status keeps the note it points to in view.
    • The SDK: exp.record({...}) outside a run block takes produced_at=. Without it, when its numbers were made is unknown, as for numbers typed in. So --fail-on late or unestablished can 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 show says 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 --variant the 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.4821 beside ± 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 compile never 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.toml and the like), so eval_lock.json is 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.

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 reconcile looks 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 reconcile and rb’s journal never propose pyproject.toml, package.json, Cargo.toml or environment.yml as 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/ from results/.
    • A large Markdown file no longer stalls it.
  • Each run folder’s own record. Where each run keeps a folder with its config and its numbers, rb reconcile says 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 the rb run add line 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-source takes 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: true says 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 compile lists 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-at have 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, unestablished or undecided can 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.
  • 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.unit is 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 --each rule, 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 --cited says 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 show gives: typed in, pooled, arms that differ, a run with uncommitted changes. A pooled reading leads with rb 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 --replaces are not among the reasons to doubt.
  • rb’s own files. The files rb init and rb skill add install 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, in rb status) offers <metric>=<value> or --command.
    • Counts say “1 run”.
    • rb retract <exp>/<name> takes back a --varies name that has no setting.

rb login waits through a dropped connection, and rb clone says no part of a password in git’s message.

  • rb login through a dropped connection. While rb login waits 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, so rb clone says “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 as rb clone says it leaves out the user and password before the host, for git:// and ssh:// too: 0.6.2 said them there whole, in what it printed and in --json. Neither ever reached the record in .rb/.

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 init installs a session hook in the agent’s project file: SessionStart and Stop in .claude/settings.json for Claude Code and in .codex/hooks.json for Codex, and sessionStart and stop in .cursor/hooks.json for Cursor. The hook runs rb 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 add installs the hook for one agent later.
  • A hook file that was there changes only once you agree, as AGENTS.md and .mcp.json do. rb’s handler goes beside the hooks there, and every byte that was there is kept. rb init --dry-run shows 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 runs rb journal scan in a form no rb wrote is yours: rb leaves it as it is, adds nothing beside it, and says so in one line. rb remove takes 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 reconcile read 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: in rb 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 in rb export and rb sync. Where an @ follows an address’s first / after credentials or a :, rb cannot tell the host from a password, so rb clone says “an address rb cannot read safely” in its place: in what it prints, in the clone’s parent and in git’s message.
  • Words. A question’s changes say a freeze as “spec and rules locked”, in rb ui and rb 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.

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 login and rb sync work for anyone who signs up. rb sync --with-record names 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. Plain rb sync names no repository.
  • One rule for the code’s address. rb export and rb sync name 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 (witness in rb status --json). One line says it for the record, on the page, in rb open, rb status, rb context and rb ui: “Kept with rb since 2026-10-28. 2 runs and 1 call were recorded afterwards, not as they happened.” (the page and rb open write 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 context and rb ui say “Rules: 1 met · 1 can’t tell yet”, and rb status, rb context and the page rb export and rb sync write say “Needs judgment” (rb page and rb open say “Needs your judgment”). Commands, flags, --json keys 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 freeze says a rule a session declared is locked now; rb confirm names the notes a design leaves not confirmed, in --json too; rb log and 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 add line rb reconcile proposes 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 open and rb sync --watch read each source again at each look, and rb ui notices 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.

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 in rb 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> or rb 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 status gives the agent unassigned_criterion for each criterion filed under a question and linked to none, with the filing that links it. CLI, Python and MCP share the same operations, and state.relations(<criterion>) reads a criterion’s relations in Python as rb show reads them.
  • A question’s counts are its linked criteria. In questions[].rollup, rules, results and the criteria in untested count 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_children and the planned experiments in untested still count the work filed under it and below it, and rb status says the two apart as linked here and below 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 as state.set_project_title(...) in Python and project_title over 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 preview shows pending events; rb telemetry disable removes 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, beside runs/ and analyses/; the project’s notes in .rb/notes/), so parallel work merges without a conflict; an experiment kept the earlier way (variants and notes in experiment.yaml, criteria in one criteria.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), and rb doctor --restore gives the second record its own. Every run keeps the git tree of its code under refs/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 status says 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 run take the worktree’s commit, branch, uncommitted changes and exact code; rb journal run runs 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 status and rb context say 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, and rb init there needs --new-project. rb status names 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 open says 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 ui says the same and w switches; y, n and c act 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, and rb page and rb export carry 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 init makes the agents’ instruction files that are not there. An AGENTS.md or .mcp.json that 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; --yes answers 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.json is 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) and rb reconcile --apply (now with --no-agents) follow the same rule.
  • Leaving rb. rb init --dry-run lists every file and integration rb init would 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 --out and rb export --zip --out never 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 remove takes 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 (--yes elsewhere; --dry-run removes nothing). It never deletes a git object, a worktree, history or a research file, and it is refused where rb confirm is. A removal stopped midway is finished by running it again. A round-trip test holds it: after rb init, an afternoon’s work and rb 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-brain brings rb as 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/.gitattributes tells 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\n is 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 in errors[]. 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 is E_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 --out makes the directory it is asked for, rb journal passes over an entry it cannot read and counts it, rb init starts the record when an agent’s instruction file cannot take rb’s section (it says which, and leaves the file as it is), and rb logout removes the credential here whatever stops the revoke. A credentials file that is not one holds no credential for rb sync; rb logout says 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 clone says 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 -32602 with 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.py plays them with scripted agents; --agent-cmd plays them with a real one, behind a counting rb. 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 that follows_from the old. In SKILL.md, AGENTS.md and the contract.
  • rb journal run reads 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, and rb reconcile proposes 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 run says 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 --json and over MCP as rb_delta, where a write’s by says who made it; in text under Since this session last looked:. It never says what research to do.
  • rb init sets up the agents it finds. The skill and rb’s MCP server in .mcp.json for Claude Code, rb’s marked section in AGENTS.md for 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-agents leaves them alone. It says when .gitignore keeps 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 ui and 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 resource rb://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 status and rb context point at it (no_question); so do E_NO_PROJECT, and rb compile and rb reconcile with no record. The agent contract gains a brownfield case 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 in git log --all. rb show <run> gives the diff; rb run add --tree records the code of a run made earlier.
  • rb reconcile compiles 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 reconcile reads 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 under rb journal run printed, 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 names rb reconcile --apply only while it would add no experiment, and work from before the record began points at rb 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 --apply keeps 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, which rb status prints. From then on every read checks the repository as it stands: reindex, unindexed_folder, manifest_changed (the stale gate), audit_failed and check_failed (a new checks gate). 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 reconcile reads it all the same and writes nothing, and --apply starts 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 --words show 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 under words on each note in rb status --json and rb context --json. So do the words given with rb experiment gate --words, beside the gate, and under gate_words on 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 --where selects), rb compare, the page and rb open show that criterion’s reading in the cell, never a change of their own; rb compare --json gives it as read_by, with unpaired saying what the means are over.
  • A call is the user’s. A call recorded in an agent session (rb decide, the SDK’s decide, MCP’s decide) is proposed, not made: questions, experiments, criteria and settings read as if it were not there, and rb status and rb context list it apart from the calls (Proposed, not made), with its subject, action, reason as given and the one command that confirms it; rb open and rb ui show 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> (--all says each call, then confirms them all; alone it lists what waits), or c on rb 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 confirm in an agent session is refused, and a write where a session left its id or entry point (CLAUDE_CODE_SESSION_ID with CLAUDECODE unset) is that session’s, as rb confirm reads 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 one rb confirm, whoever ran rb init and 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, in rb confirm, rb show and authority.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 --all leaves a subject more than one call waits on to rb confirm <id>, and rb confirm says what the call is made on now. Words given in an agent session read as words (as given): "..." on every surface, never as the user’s, until the user confirms the call they came with. Each decision carries its computed authority in --json, over MCP and in the SDK; rb status gives pending beside decisions, and the open items call_proposed and past_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 confirmed once on its own views (rb show <exp>, its pane and row on rb open, its card on rb ui, its part of a page), with the one command that confirms it, and why: not recorded or hypothesis or purpose: not recorded where it has none; rb status, rb context and a page’s summary say every such design in one line. rb confirm lists each part by part, and rb 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 on rb status, end with the one command that confirms all of it as listed, by id and hash, in place of rb 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, as rb confirm lists it, and --json keeps the text as written. design on each experiment, designs_pending and counts.design_pending_count in --json. An older rb lists the confirmation of a design with no gate in rb log as a gate line.
  • Preview a write. rb run add, rb spec set, rb criterion add, rb decide and the 21 other write commands AGENTS.md lists take --dry-run (dry_run on the same tools over MCP and the same writes in the SDK, where it returns a DryRun). 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 reconcile without --apply, rb doctor without --restore or --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’s 48{,}200 and 48\,200, state 48200 as 48,200 does, never 48 or 200. A name LaTeX and Markdown escape (seq\_len) names seq_len, in a source check and in rb 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=1 reads 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 (--select narrows them), and --set seed=@seed and --produced-at @finished_at take 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 keeps row: 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 by rb status (row_in_several_experiments), naming both, and a row whose values changed since is refused, with --replaces or --again as 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 set and rb spec vary point 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 compile lists a database’s tables, their columns and row counts, and the columns that name a declared metric. exp.record_from(...) in Python and run_add over MCP read a file or a row as rb run add --from does. 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.seed reads 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 run watched it make the numbers or --command-source <file>:<line> cites a log line written by then that holds it; rb run add says so, command.given says how rb knows in --json, and rb status counts such runs (command_later). A file written after its run, an export, reads as file written <date>, after the run, never as the run’s own output (file_written_after). rb show and rb page say 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 open and rb ui say 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_holds and if_not over MCP and in the SDK) keeps what happens either way, in its author’s words, and rb experiment gate sets or revises it later, the earlier words kept in the log. rb status reads 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 open and rb page show 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; --each for every unit, --bound for the interval’s bound); --better, --noninferior M and --equivalent M read 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.unit names 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-n counts 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 --source records 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 --supersedes the 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. late fails 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 --adopt work 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> or agent:<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 session RB_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 on question add, experiment add and note add; words in MCP tools and in the SDK’s decide, retract and freeze): they are kept beside the reason on decisions, freezes, amendments, retractions and criteria, and on the log line, and shown as your words: "..." once they are the user’s (above). Nothing is paraphrased. rb status items carry who as 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 agent on its log line and, for a run or an analysis, on its receipt: its name, its version (from AI_AGENT or the MCP client’s clientInfo.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’s model) for the rest of its session, or RB_MODEL for one command. With no session id rb keeps no model. A session’s agent facts get one session line 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_delta says once how to give the model while none is recorded, rb context says it in every briefing until one is, and --json gives the actor with agent. Cursor’s agent (CURSOR_AGENT=1, its chat in CURSOR_CONVERSATION_ID) and Codex known only by its thread or session id are recorded as agents. A human’s write keeps a session only when RB_SESSION names 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 or agent, nor the via beside 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 of rb log as 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 status names 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 doctor still says how this shell’s writes are recorded.
  • Publishing the record is a choice. rb export and rb sync publish the page alone, with a manifest hashing it: no record, no file a check reads, no bundle.zip and no Reproduce panel with rb clone. --with-record (with_record in the SDK’s export and the MCP export tool) 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. --url takes --with-record, since there is nothing to clone without it; rb clone of a page alone says it was exported without the flag; --json says which was written (with_record).
  • No screen names a writer either. The page rb page, rb export and rb sync write, rb open (with the /state.json and /card.json its tab draws, where each field that kept who wrote now holds "", whatever the record keeps there) and rb ui name 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 · N on rb ui, whose a key opens that list). rb ui counts 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 its y prompt says what a yes records, not as whom. The user’s words show on rb open and rb ui without the your words: tag, and never on a page, which anyone may be sent; nor does a page show the platform a run’s receipt keeps, which rb open shows. The screen refuses an agent session as rb ui does when it is started with python -m rabbit_brain.tui too.
  • Plain record text. rb shows the text anyone writes exactly as written, on every screen, so the manual and the contract rb compile prints 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, among data.warnings in --json and over MCP, and as a rabbit_brain.RBWarning from the SDK, which now also warns of what rb run add and rb analysis add warn 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, then rb experiment add -q), rb status on an empty record, rb context, rb compile, the E_NO_PROJECT fix, 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 show of an edited record says what changed.
  • The record is plain YAML in .rb/, one file per record. .rb/project.yaml and notes/; questions/<id>.yaml; experiments/<id>/experiment.yaml with a file per variant, note and criterion under variants/, notes/ and criteria/, per run under runs/ and per analysis under analyses/; metrics/, resources/, decisions/; log.jsonl; and, local and gitignored, cache/. Every file opens with rb: 1 and then its kind. 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 --json gives 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 all prints format 1 as one JSON Schema, committed as schema/rb-v1.schema.json (its $id is 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 locate adds 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=runpod records, 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 status answers 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 in rb status or rb context names 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; --json keeps every item. In the user’s own terminal, the Next line names rb confirm first 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, never all 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 open only 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” in rb ui and rb page, under “Found in the repository” in rb open, beside how many calls wait on you, which rb status lists under Needs a human. The list of the calls waiting on you, on rb open and 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-bundle version 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, and rb export --with-record names 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 with rb spec verify), gives every experiment a new uid and a reproduces relation 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 export refuses one that would hold more, naming its largest parts, and rb clone one that does, and what rb clone, rb export and rb sync have 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 publish fails with E_NOT_AVAILABLE and says what to use. rb export writes the page you can host anywhere, and with --with-record a bundle that rb clone reads; rb login and rb sync are 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 login and rb sync addressed 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 --hub or RB_HUB_URL is still called. rb --help groups 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 reconcile finds results that landed unrecorded. The packaged skill (rb skill) says the same in about fifty lines and defers to rb docs for the rest.
  • Where rb looks, said where it reads. rb status, rb context, rb open, rb ui and 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 through rb journal run; rb reconcile reads 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 --apply in the user’s own name, and that no setting, run, analysis, criterion, call, resource or indexed folder cites, is listed by rb reconcile by path with the commit the count starts at, and counted in one line on rb status, rb context and the user’s screens (uncited_changes in --json). rb says what changed and that nothing cites it, never whether it matters. rb reconcile --apply keeps their paths on its line, so they are recorded while their bytes are those.
  • Recovering what nobody recorded, honestly. rb reconcile says where it looked, never proposes a file of progress numbers as a run, nor one that was there when rb init first 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 compile lists files matched by metric name as candidates with when each was written, never as a deterministic fact.
  • rb tour runs 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 clone refuses a bundle that holds either rather than leave it behind; an unreadable criteria file stops its criteria with E_STATE_CORRUPT instead of hiding them; a second copy of a record in another file is caught as written outside rb, set aside by rb doctor --restore, and never adoptable; retracting an experiment keeps its criteria in every gate, and rb status gives 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 offers rb doctor --restore beside the upgrade where rb holds its last write; rb doctor says the same, once, rb open draws the fix under the message (fix beside error), and rb ui gives the terminal back and says both.
  • A misspelled metric is named, not refused. rb criterion add with a --metric that no run or analysis reports and the catalogue does not hold, close to a name one of them does (change.val_los beside val_loss), says so in one warning line naming that name (data.warnings, and an RBWarning from the SDK’s criterion()); 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, and rb status gives it the retraction as its step, never the misspelt name.
  • A run that counts toward nothing says so. rb run add whose numbers no criterion of the experiment reads (a flat {"val_loss": ...} where the criteria read fp32.val_loss and int8.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’s run.log says the same as an RBWarning. The README shows that shape.
  • After uvx, the way to keep going. rb tour run by uvx --from rabbit-brain ends with how to run its next commands while rb is not on the path (uvx --from rabbit-brain rb ui, or uv tool install rabbit-brain), and the README says so after the uvx route.
  • 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 the rb binary.

Rabbit Brainrabbitbrain.ai · GitHub · PyPI · Privacy · Apache-2.0