Skip to content

Releases

nullius v0.8.9

Choose a tag to compare

@github-actions github-actions released this 17 Oct 14:11
4e99dfd

Two gates that cost a project which runs code a turn they did not need to: the session start
recited the stable rules every time, and every Markdown file was a draft.

  • session_start: brief holds the stable rules back while no unit is open. The session
    start printed the project's state, then every file in invariants/ and the command list, at
    each session start and again after each compaction: 15.5 KB in a project measured on
    17 Oct 2026, of which the state was about 3 KB. The rules bind on a unit's drafts, so with
    nullius config session_start brief they are named, not recited, while no unit is open, and
    come back the moment one opens; kept search rows are counted, not listed. The default stays
    full. Tested in both directions in tests/smoke.sh.
  • A project can name its drafts, and a file outside the root is never one. Any file with a
    draft extension anywhere was recorded as an unclaimed draft, so a research log, a
    measurement registry or a memory note kept outside the project each refused the stop until
    declared. A file outside the project root is now none of the gates' business, and
    nullius config draft_globs "paper/*" (fnmatch over the root-relative path; a JSON list or
    comma-separated) limits the stop gate's question to the files named. Out of scope is not out
    of reach: the identifier guards still refuse an invented citation in any file with a draft
    extension. The default, an empty list, keeps every such file a draft. Tested in both
    directions in tests/smoke.sh.

nullius v0.8.8

Choose a tag to compare

@github-actions github-actions released this 08 Oct 19:57

Two gates that held the stop on work already in order: a journal DOI read as an arXiv id, and a
draft that had been declared scratch but was recorded under a path nothing could clear.

  • A DOI ending in YYYY.NNNN was read as an arXiv id and could not be cited.
    normalise_identifier tried the end-anchored ARXIV_RE before DOI_RE, so
    10.1001/jamanetworkopen.2024.8064 was looked up on arXiv as 2024.8064 and refused,
    bare, as doi: and as https://doi.org/. Journal DOIs of that shape are common (JAMA
    Network Open, JAMA Psychiatry, Phil Trans R Soc B, Information Systems Research), and the
    gate then held the stop on a reference that exists. An identifier that is a DOI on its face
    is now read as one; arXiv ids and arXiv's own 10.48550 DOIs still go to arXiv. Tested in
    both directions in tests/smoke.sh.
  • A draft redirected to its absolute path refused the stop for good. The shell hook
    recorded an unclaimed draft under whatever the command typed, while the Write hook,
    scratch and artifact use the path relative to the project root. A redirect such as
    python3 run.py > /abs/project/log.txt, which is how a subagent writes its run log, left a
    key neither scratch nor artifact could clear, and the stop gate went on refusing a draft
    somebody had already declared scratch. A relative redirect typed from a subdirectory had the
    same fault. Every draft key is now the project-relative path, and a key an older version
    recorded raw is cleared by the decision about its own file, so a project that met the old
    spelling needs no hand edit. Tested in both directions in tests/smoke.sh.

nullius v0.8.7

Choose a tag to compare

@github-actions github-actions released this 13 Sep 11:25

Two control runs, a literature outside AI, and the finding that the harness engages on the
prompt.

  • The control arm, run a fifth time, on a literature
    three months old.
    The fourth run was on forty years of refereed work and the arms differed
    in what a reader could verify, which is a real difference and a quiet one. This one asks
    whether large reasoning models reason or pattern-match: preprint-dominated, contested, with a
    popular account well ahead of the evidence. The difference gets loud. The unharnessed session
    cites The Illusion of Thinking -- the paper the whole dispute is about -- through
    arize.com/blog/, a monitoring vendor's marketing post about it, and five of its ten links
    are not papers
    : that blog, a conference poster page, a company's own publication venue, a
    benchmark product page and a Substack. No DOI anywhere in it. The harness arm holds the same
    work resolved to NeurIPS, peer-reviewed, read to method depth, quoted verbatim against
    cached text. A blog post is not a work, so cite cannot turn one into a citation.

  • The independence arithmetic said yes for the first time. Across four earlier runs no claim
    ever reached established. Here one did, on two independent groups both measuring, and the
    contested heart of the dispute is recorded as contested over the two papers that disagree. A
    gate that only ever refuses is a gate nobody can calibrate.

  • Nine of eighteen sources in that run are not peer reviewed and the ledger says so on every
    one.
    It reaches the prose: "Lawsen (2025, preprint)", "Shojaee et al. (2025, NeurIPS)".
    The unharnessed answer names no venue for any source at all.

  • A slow index crashed lit instead of being one index that did not answer. TimeoutError
    is an OSError and is not a URLError, so a socket timeout arrived unwrapped and escaped
    as a traceback, discarding the results the other indexes had already returned. This is the
    same defect class control-2026-09-12 records against
    audit, which was fixed at that one site and never swept for: http_json, http_text,
    arxiv_search and arxiv_lookup all had it. Found by using the tool, not by reading it.
    Covered offline by patching urlopen to time out and asserting every helper converts rather
    than raising.

  • The control arm, run a fourth time, on a question
    in the philosophy of language rather than in AI.
    Four cells this time, because the prompt
    was varied as well as the arm, and the prompt is what decided the result. Asked only to
    answer the question, a session inside a nullius project opened no unit, logged no search,
    resolved nothing, and the stop gate allowed it
    -- correctly, since with no unit open there
    is no unit state to refuse. Asked one sentence more (do the literature search properly),
    the same setup ran 20 searches, resolved 21 references and wrote 19 notes. So three earlier
    runs have been measuring the advice arm while calling it the tool arm, and what mode certain buys over mode advice is still untested.

  • The arm that scored best on the instrument scored best by writing nothing checkable. In
    a nullius project with no unit open, the session had read the invariants, knew the rule that
    refuses a credited surname with nothing resolved behind it, and satisfied it by crediting
    nobody: zero identifiers, zero names, a clean row on every measure audit computes, and not
    one source a reader can chase. The arm with no harness at all named five people you can
    search for. audit counts unbacked names and cannot count absent ones.

  • audit was undercounting the arm with no harness, in the direction that flatters this
    repository.
    A draft carrying nine working links scored zero identifiers named. The
    lookbehind that stops a DOI matching mid-token also stops every DOI inside a publisher URL,
    and the one special case written for it covered doi.org/ -- the single URL shape a session
    with no ledger does not write. pnas.org/doi/10.1073/... and
    onlinelibrary.wiley.com/doi/10.1002/... read as nothing at all. Fixed, with the case in
    both directions in smoke.sh, and the identifier counts for the unharnessed arms in runs
    one to three should be read as floors.

  • The gates were optional, in the one way nobody checks. Not by stepping around a gate --
    by never entering the state a gate applies to. hook_stop opened with
    if store.unit is None: return 0, so a session that wrote a research answer into a nullius
    project and opened no unit passed every check there was, which is what the fourth control run
    measured. A draft written here with no unit behind it now refuses the stop. Recorded at
    write time on both paths, Write|Edit and the shell heredoc, because the measured session used
    the heredoc. Two exits: nullius artifact <path> under an open unit, and every unit gate then
    applies, or the new nullius scratch <path> "<why>", which says it was a thinking file and
    puts that on the record. Refusing every unit-less session would be the wrong fix -- a
    research folder is also where you answer unrelated questions -- so what is refused is the one
    thing that is unmistakably the work. Both directions in smoke.sh, including that a unit-less
    session which wrote nothing still finishes.

  • And the gate was re-run against the cell that found the hole. Same prompt, word for word.
    Under 0.8.6 the session opened no unit and the stop allowed it; under the gate it opened a
    survey, declared what would close it, tracked the draft, closed on a locator and generated a
    report -- and recorded, in the ledger rather than in the chat, that it had "zero query
    vocabularies logged against a suggested three ... I wrote from what I already knew rather than
    from a logged search"
    . OpenAlex was rate limiting from this session's own earlier use, so
    cite could not resolve and the research path itself was not exercised. What was exercised is
    the gate, and the session stripped the author names it could not resolve rather than leave
    them credited.

  • The front page had been promising this for four releases. "states a session cannot finish
    in"
    was the first sentence of the repository and was not true as written. It is now, for the
    case it names, and the sentence says what it does not reach: research that produced no draft,
    or a draft written outside the project.

  • Score nothing until the arm's process has exited. The harness arm was read and audited
    while it was still working, at 611 words with fourteen DOIs inline. It then cut itself to
    355, moved the identifiers into nullius report and left a 68-word provenance note in their
    place, so every number from that first reading described a file that no longer existed -- and
    the first draft of the write-up and the front page both carried them. Corrected against the
    final artifacts, and the rule is now written down in the run, because nothing in
    control.md said it and no earlier run had scored an arm mid-flight.

  • A new scenario, and deliberately not an AI subject.
    There are now eight scenarios, and the first seven test the tool against one literature's
    habits: a canon fifteen years old, everything on arXiv, founding papers phrased in today's
    words.
    Whether the language you speak changes what you can see breaks all three. It exercises four
    paths nothing else reached -- a field whose indexes exclude arXiv, a trade book the indexes
    do not carry, a kept work closed by decision with frontier, and the weakness
    algorithms/graph.md had declared and never run: era proximity misjudges a field whose
    canon genuinely is old.
    Berlin and Kay 1969 arrives at the highest multiplicity in the
    walk and is flagged ancestor?. The flag firing is correct and the label is wrong, and the
    scenario asserts the first while recording the second.

  • Two runs of the same walk stopped for different reasons. Same three queries, same limit,
    same depth, different two-works-per-vocabulary seed sets: one converged at hop 3 on 60%
    saturation with 210 works reached, the other never converged and spent the budget at 475.
    Saturation was not monotonic either -- 12, 38, 44, 7, 46, 32 -- so one hop's number is
    not a curve. graph.md fixed circularity between the walk and its seeds; nothing yet checks
    whether the seeds are independent of each other, and that is now measured rather than
    suspected.

  • The front page shows two real sessions instead of a funnel. 0.8.6 settled on coverage
    output as the After column, which was honest and mismatched: the Before was a conclusion and
    the After was a receipt, so the table replaced a claim with a log rather than with a better
    claim. Both columns are now verbatim output from the fourth control run, both carry a
    conclusion, and the left one is not a straw man -- it found a 2026 replication the harnessed
    session missed. The example says so.

  • The harness arm missed three recent works, and not for the reason it looked like. The
    obvious reading was that a web search reaches this year and a bibliographic index lags.
    Checked before releasing, and wrong: the 2025 scoping review it missed is in OpenAlex at
    10.1017/langcog.2025.13, the 2026 replication at 10.1073/pnas.2538139123, and the arm's
    own 639 records include eighteen works from 2024 on. None of the three was ever retrieved
    by any of its twenty vocabularies, every one of which names a paradigm, a population or a
    mechanism. Recency is not one of the vocabularies librarian.md asks for, nothing counts
    it, and nothing refuses without it. Recorded as an open gap rather than patched, because the
    fix is a gate and a gate wants a run behind it.

nullius v0.8.6

Choose a tag to compare

@github-actions github-actions released this 12 Sep 13:44

A real example, and a note that invites use instead of apologising.

  • The before/after on the front page shows the point instead of claiming a provenance.
    It went through three versions today. It began as invented numbers presented as a run,
    which is the thing this repository refuses. It then became a real logged eval run, which
    was honest and a poor example: an eval screens one search of three on purpose so a gate has
    something to refuse, so the block showed a scenario mid-test rather than the failure the
    tool exists for, and it needed a paragraph of explanation to not mislead. A caveat that
    long is a sign the example is wrong.
    It is now a representative one, in the real coverage format, chosen to show the failure
    in one glance: one vocabulary of three, 231 results never looked at, four papers that are
    not independent of each other, nothing before 2024. I reviewed the literature survives
    any scrutiny; that block does not.
  • The status note led with what is missing. Everything in it was true and none of it was
    the first thing a reader needs, which is that this is in use, it is not finished, and the
    shape of a research session is not something one person gets right alone. The caveats are
    one click down, where they are now more precise: what is built, the one thing documented
    and not built, and three control runs rather than two.

nullius v0.8.5

Choose a tag to compare

@github-actions github-actions released this 12 Sep 13:09

A timeout is not an absence, and the control arm run a third time.

  • A timeout is not an absence, and audit treated it as a crash. Four arXiv lookups
    timed out during the third control run and TimeoutError is an OSError, which sat
    outside the exceptions the three-bucket logic catches, so one slow index ended the whole
    audit rather than putting four identifiers in the bucket that exists for precisely this.
    On the retry all eleven resolved.
  • The control arm, run a third time. The 40% cut
    to the invariants roughly halved how far the advice arm overshot its brief: +81% to +31%,
    and the gap to the bare arm from +229 words to +97. It did not close it. Six arms across
    three runs, still not one fabricated citation.

nullius v0.8.4

Choose a tag to compare

@github-actions github-actions released this 12 Sep 12:17

A worktree had the harness switched off, and the acks talked to nobody.

Both taken from jig, which hit the same two things first.

  • A linked worktree got "no nullius project here". work/ is gitignored and the rest of
    .nullius/ often is not committed either, so walking up from a worktree found nothing and
    the session carried on ungated. find_root now tests, at each level, this directory's
    .nullius/ and then this directory's checkout -- before climbing, so a worktree parked
    inside another project belongs to the checkout it came from rather than to whatever it was
    parked under.
  • And then one work/current.json meant two sessions shared a slot, so the second
    start replaced the first in silence. The open unit is keyed per working copy now,
    work/current/<stream>.json, while references, notes, claims and searches stay shared: a
    resolved DOI is a fact about the world and does not branch. A unit written before this is
    read once and migrated on the next write.
  • Acknowledgements that only repeat what you typed are gone. '{name}' -> {disposition},
    {severity} / {referent} at {at}, {recommendation}, [{n}] {text}, and the word
    recorded on its own. Where the line reported something -- how many findings now generate
    work, how many reviewer points still have nothing said about them, how many of the walk's
    absences have a word about them -- it says that instead. jig's rule, and it is right: the
    model reads the tool and the person reads the model, so an ack that restates the arguments
    costs a line and tells nobody anything.

Still true and not fixed: two sessions in the same directory share a stream and still
collide, and a worktree that has committed its own .nullius/ keeps a forked ledger. Both
are in algorithms/harness.md.

nullius v0.8.3

Choose a tag to compare

@github-actions github-actions released this 12 Sep 11:45

Where you are before what is wrong, and a README that shows the detail to whoever asks.

  • The README shows the detail to whoever asks for it. 2,888 words with no way to skim
    became 1,213 visible and 1,864 behind folds, about five minutes on the front page instead
    of thirteen. Headings stay headings, so the structure is still readable at a glance and
    you open the one you want; nothing was deleted this time, only moved out of the way.
  • The strongest argument in the repository was on page four of another file. WHY.md
    section 4 has the same claim written twice, as prose a reader nods at and as counts they
    cannot: I reviewed the literature against found 231, screened 0, included 4. It is now
    the second thing on the front page, before the install and before the caveats.
  • The status note led with a hundred and fifty words of caveat. The caveats are real and
    stay -- the unbuilt folklore walk, the two control runs, the arms that scored clean
    without a harness -- but one line up front and the rest behind a fold. It also still said
    456 assertions, which has been wrong for several commits.

Both the fold and the before/after table are lifted from
i-have-adhd, which puts ten rules on the front page
and the full text one link away.

  • It listed the facts
    refusing and left the reader to work out which command answered the first one, and whether
    anything had moved since the last time. Every fact already names its command, so the first
    is lifted to the front; the unit, its thread, what is open and what has closed go on the
    line above. Six refusals went from eight lines to four. status opens with the same two.
    Three rules in voice.md follow: lead with what can be done rather than only with what is
    known, say where they are before saying what is wrong, and name what has closed without
    celebrating it -- a rigour tool that starts handing out congratulations has changed what
    it measures.

    The shape is adapted from i-have-adhd by ayghri (MIT),
    which argues that knowing an answer and doing it are different and that the gap between
    them is where work stops. The clinical framing is not borrowed: what transfers is that
    state a reader is assumed to be carrying is state they are not carrying, which is this
    repository's own argument about context boundaries, pointed at the person instead of the
    model.

nullius v0.8.2

Choose a tag to compare

@github-actions github-actions released this 11 Sep 07:36

A door.

Measured against the published guidance for what a Claude Code plugin is -- skills, slash
commands, subagents, hooks -- this one shipped two of four, and the consequence was testable:
plugin installed, folder without a .nullius/, SessionStart emitted zero characters. The
whole vocabulary arrived only after a project existed, and creating one required already
knowing the command. It worked in practice because a user types set up nullius and the
model reasons its way there, which is luck rather than design.

  • skills/nullius/SKILL.md. Fires on setting the harness up and on the work it gates:
    is this idea already published, cover this literature, read this paper, resolve this
    citation, draft against this venue, review this draft, interpret this result. Sixty lines.
    It does not restate the command vocabulary, which SessionStart already injects inside a
    project; what it carries is the part a session cannot get anywhere else -- the init path
    from CLAUDE_PLUGIN_ROOT, the two files only the user can write, and which unit kind fits
    which question.
  • The README promised a /nullius slash command that has never existed. Six reviews
    today went past that line. It now says what is actually there.

Still two of four: there is no slash command, because the skill covers discovery and a
command to reprint a vocabulary already injected would earn nothing.

nullius v0.8.1

Choose a tag to compare

@github-actions github-actions released this 11 Sep 06:37

One mistyped letter turned every gate off.

  • mode is an enum of two, validated. mode was a free string where certain was
    magic and anything else silently behaved as report-only, with no validation anywhere:
    nullius config mode certian was accepted and the stop gate went from exit 2 to exit 0
    without a word. It is an enum of two now, certain and advice, and a session in advice
    is told so at every start, because otherwise it believes it is being gated and is not.
    Found by being asked why a harness needs a mode setting if there is only one mode. There
    were two; the count in the 0.8.0 notes was of code sites, not modes, and was wrong.

nullius v0.8.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 06:27

Two broken agents, an eval record that read a failure as green, and a third less prose.

The prose

  • invariants/*.md: 1,933 -> 1,158 words. Every rule survives -- the bold rules were
    diffed before and after, 39 in and 39 out, and the one genuine loss, a reviewer-proof
    paper does not exist
    , was restored. What went is the argument around each rule, which
    WHY.md and algorithms/ already carry. All four tables stay: they are the vocabulary,
    not prose. voice.md lost most (560 -> 262) and had been injecting a reference to prose,
    a command deleted two hours earlier. SessionStart on an empty project: 2,569 -> 1,794
    words
    , below where the day started.
  • README: 4,841 -> 2,542 words, twenty minutes to eleven. Twelve sections argued for one
    mechanism each. They are one table of what refuses and one paragraph of what reports; the
    arguments were already in WHY.md and algorithms/.
  • CHANGELOG: two entries at 1,413 and 811 words against a historical median near 430. A
    changelog says what changed and why it was wrong before; those re-argued the design.
  • paragraphs() treated a figure's alt text as prose. The 82-word alt text atop this
    README counted as its opening paragraph, so check called the file no short version at
    the top
    when its first sentence is thirty words. It also inflated the cross-file
    duplication count used to argue for this cut: 34 pairs measured, 11 real. The headline
    number was partly an artifact of the instrument that produced it.
  • Also cut: WHY.md section 12, which was credentials and a build order; section 5's
    table, which is warrant.md's; algorithms/README.md's prose, which was CONTRIBUTING's;
    examples/README.md's account of files that no longer exist; and
    evals/results/06-2026-09-02.md, which held two runs concatenated with a failure first.

Repository prose: 43,974 -> 39,341 words.

The machinery

A second pair of clean-context reviews, one on the prose and one on everything that is not
bin/nullius. The prose one measured what the tool's own repetition detector found when
pointed at the repository for the first time: 34 cross-file paragraph pairs sharing half
their content words or more, and a README that check reads as 4,568 words, twenty minutes,
no short version at the top.

  • agents/referee.md mandated major-revision and minor-revision. DEFAULT_SCALE is
    desk-reject / major / minor / accept, so an agent obeying its own file was refused by
    nullius verdict on every review. It now names the venue file's scale, which the same file
    said two paragraphs earlier.
  • agents/librarian.md had the screen arguments in the wrong order, documenting
    screen <search> <index> <include|exclude> where the index is a flag. An agent following it
    failed on every screening call.
  • The eval runner never truncated its output file. tee -a into a name keyed on the date
    means a same-day re-run concatenates, and results/06-2026-09-02.md is two runs in one
    file: the first ends | 8 | 1 | 0 | with a gate that should have refused and did not, the
    second ends | 9 | 0 | 0 |. A reader taking the last table read a failed run as green.
  • The bytecode check added this morning reported an untruth. It said tracked and
    globbed the working tree, so an untracked, gitignored .pyc turned preflight red on a
    clean repository. That is the 5 passed, 1 failed seen earlier today and waved away as
    transient. It was not transient. It reads git ls-files now.
  • Three byte-identical venue skeletons, and two duplicate eval results. The three
    examples/*/venues/EXAMPLE.md had the same md5 as each other and as templates/venue.md,
    which init already writes, so copying a worked field into a project overwrote a copy of
    itself. results/04- and 05-2026-09-10.md differed from their 09-02 twins by a timestamp
    and one citation count.
  • examples/README.md claimed index presets init does not pick. True for
    clinical-ml, false for the other two: FIELD_INDEXES has no key matching language
    models
    or social science.
  • About 900 words of restated argument. WHY.md section 6 was invariants/status.md with
    the column headers reworded, and section 7's boundary subsection was the third telling of
    what algorithms/critique.md carries with its parse schema. The injected files are
    canonical because they are the copy a session actually sees. The duplication was not only
    bloat: today's correction to the folklore claim reached two of the four places it was
    stated, and section 6 was still describing a citation walk that does not exist.
  • The README's Where the rest of it is section opened by saying nothing is summarised
    back here and then summarised five files for thirty-five lines.

A finding checked and rejected. The machinery review reported that PostCompact is not a
Claude Code hook event and that the entry in hooks/hooks.json is inert. It is a valid event,
per the official documentation, and it fires. The constraint re-injection that
REFERENCES.md cites chen2026 for does happen. Acting on that finding
unchecked would have deleted a working mechanism, which is the whole argument for checking.