Repository navigation
Releases
nullius v0.8.9
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: briefholds the stable rules back while no unit is open. The session
start printed the project's state, then every file ininvariants/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 briefthey 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 intests/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 intests/smoke.sh.
nullius v0.8.8
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.NNNNwas read as an arXiv id and could not be cited.
normalise_identifiertried the end-anchoredARXIV_REbeforeDOI_RE, so
10.1001/jamanetworkopen.2024.8064was looked up on arXiv as2024.8064and refused,
bare, asdoi:and ashttps://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 own10.48550DOIs still go to arXiv. Tested in
both directions intests/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,
scratchandartifactuse 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 neitherscratchnorartifactcould 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 intests/smoke.sh.
nullius v0.8.7
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 tomethoddepth, quoted verbatim against
cached text. A blog post is not a work, socitecannot turn one into a citation. -
The independence arithmetic said yes for the first time. Across four earlier runs no claim
ever reachedestablished. Here one did, on two independent groups both measuring, and the
contested heart of the dispute is recorded ascontestedover 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
litinstead of being one index that did not answer.TimeoutError
is anOSErrorand is not aURLError, 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_searchandarxiv_lookupall had it. Found by using the tool, not by reading it.
Covered offline by patchingurlopento 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 whatmode certainbuys overmode adviceis 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 measureauditcomputes, and not
one source a reader can chase. The arm with no harness at all named five people you can
search for.auditcounts unbacked names and cannot count absent ones. -
auditwas 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 covereddoi.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 insmoke.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_stopopened 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 newnullius 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 insmoke.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
citecould 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 intonullius reportand 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 withfrontier, and the weakness
algorithms/graph.mdhad 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 flaggedancestor?. 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.mdfixed 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 at10.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 vocabularieslibrarian.mdasks 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
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 realcoverageformat, 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
A timeout is not an absence, and the control arm run a third time.
- A timeout is not an absence, and
audittreated it as a crash. Four arXiv lookups
timed out during the third control run andTimeoutErroris anOSError, 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
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_rootnow 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.jsonmeant two sessions shared a slot, so the second
startreplaced 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
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 againstfound 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.statusopens with the same two.
Three rules invoice.mdfollow: 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
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 -- theinitpath
fromCLAUDE_PLUGIN_ROOT, the two files only the user can write, and which unit kind fits
which question.- The README promised a
/nulliusslash 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
One mistyped letter turned every gate off.
modeis an enum of two, validated.modewas a free string wherecertainwas
magic and anything else silently behaved as report-only, with no validation anywhere:
nullius config mode certianwas accepted and the stop gate went from exit 2 to exit 0
without a word. It is an enum of two now,certainandadvice, and a session inadvice
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
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.mdandalgorithms/already carry. All four tables stay: they are the vocabulary,
not prose.voice.mdlost most (560 -> 262) and had been injecting a reference toprose,
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 inWHY.mdandalgorithms/. - 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, socheckcalled 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.mdsection 12, which was credentials and a build order; section 5's
table, which iswarrant.md's;algorithms/README.md's prose, which wasCONTRIBUTING'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.mdmandatedmajor-revisionandminor-revision.DEFAULT_SCALEis
desk-reject / major / minor / accept, so an agent obeying its own file was refused by
nullius verdicton every review. It now names the venue file's scale, which the same file
said two paragraphs earlier.agents/librarian.mdhad thescreenarguments 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 -ainto a name keyed on the date
means a same-day re-run concatenates, andresults/06-2026-09-02.mdis 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.pycturned preflight red on a
clean repository. That is the5 passed, 1 failedseen earlier today and waved away as
transient. It was not transient. It readsgit ls-filesnow. - Three byte-identical venue skeletons, and two duplicate eval results. The three
examples/*/venues/EXAMPLE.mdhad the same md5 as each other and astemplates/venue.md,
whichinitalready writes, so copying a worked field into a project overwrote a copy of
itself.results/04-and05-2026-09-10.mddiffered from their 09-02 twins by a timestamp
and one citation count. examples/README.mdclaimed index presetsinitdoes not pick. True for
clinical-ml, false for the other two:FIELD_INDEXEShas no key matching language
models or social science.- About 900 words of restated argument. WHY.md section 6 was
invariants/status.mdwith
the column headers reworded, and section 7's boundary subsection was the third telling of
whatalgorithms/critique.mdcarries 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 issection 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.