v0.9.8 — v0.9.8 - the graphical installer can open its own window
Added
-
recall calibration driftsays whether the corpus under a live calibration has moved far
enough to need refitting. Until now the drift question could only be asked after a rebuild:
resolvecompares fingerprints and answersSTALEon any mismatch, which is a yes/no about
identity rather than a statement about magnitude, andcarry-forwardneeds a new generation to
already exist. Nothing could be asked the question an operator actually has between rebuilds.Two tiers, and which one produced the verdict is always in the output. The screen is a
manifest comparison over(uri, sha256): no embedding, no retrieval, not even a model load. The
probe replays the calibration's own stored labelled query set and measures what the frozen
threshold now costs, per class, by the same two conditionscarry-forwardenforces. ⛔ The
screen firing is never reported as a verdict: where the probe cannot run the strongest verdict
isrecalibrate_recommended, and the report names the check that was not made, so a directory can
never reachrecalibrate_requiredhowever total its delta. -
RECALL_AUTO_CALIBRATEandrecall calibration autore-establish a calibration without asking
for labels. After a generation build:offdoes nothing,warn(the default) reports,auto
additionally carries the threshold forward, or refits it on the same stored labelled evidence
when it has to move. Neither path loosens certification, so the automation is in what gets
run and never in what gets accepted, and neither invents questions: a tenant with no published
calibration reportsskipped, because deciding what the labelled questions should be is not a
decision to make unattended. -
recall calibration carry-forwardand thecorpus_delta/threshold_error_rates
primitives, rescued from an unmerged branch and rebased onto master.
Changed
-
⚠️ There is deliberately no corpus delta at which recalibration is demanded outright, and this
reverses the design this feature started with. Measured over 57 snapshots of three real corpus
histories (docs/preregistrations/2026-08-21-calibration-drift-trigger.md): the frozen threshold
first crossed the 0.10 error bound at a delta of 0.945 and never below it, so a delta-only
rule at 0.25 fires on 56 of 57 snapshots and is right about 5, a precision of 0.09.
The labels also proved far more durable than that rule assumed: at delta 0.981 only 27.5% of
the answerable queries' original evidence still existed and the false-abstain rate was 0.025.
What moved was the false-confirm rate, tracking corpus growth rather than change as such.This does not license raising
--max-corpus-deltaon carry-forward, and it was left at 0.25.
That bound governs whether a threshold may be inherited, and what the numbers establish is that
a delta is a poor alarm, not that a large delta is safe.
recall uninstallalso removes the desktop app's handoff file from the user config directory
(%APPDATA%/RE-call/runtime.json), which the previous release looked for in the wrong place.--selftestresolves the embedder only when a model cache already exists, and says which branch
it took. It was downloading model weights on a cold cache.
Fixed
- A desktop upload no longer silently shrinks the corpus. Carried-forward files that live
outside the upload staging directory (which is every file a wizard install indexed) are kept and
the build reader is widened to reach them. Only a file whose bytes are genuinely gone is dropped,
and the count is now named in the upload's own message rather than passing unmentioned. - A failed
docker compose downis reported. With the docker daemon unreachable,recall uninstallrecorded no failure, printedRemoved N item(s)., and deleted the stack file naming
the containers that were still running — which then made them unnameable by the tool that left
them. The stack file andwizard.jsonare now kept whenever the teardown fails, so the uninstall
can be retried. - An uninstall no longer overwrites the install-time backup of the MCP client config. It writes
its own under a name that is never reused, and copies the source file's mode rather than creating
it at the umask default, since that file carries bearer tokens. - A volume the stack declared
externalis never removed. A fallback that derived the
historical volume name could reinstate exactly the volume that had just been excluded as not
ours. The fallback now applies only to a legacy stack that declares no volumes at all. - A failure inside
build()no longer strands a generation. The desktop upload's cleanup path
called onlyabandon, which refuses any state butready, so failures beforevalidate()left
a full copy of the corpus thatgccould not collect. - A blank or relative data folder is refused rather than silently becoming the process's
working directory, and the terminal interview reports it as a refusal instead of a traceback. corpus_versionmay no longer begin withdesktop-. That prefix decides which generations
the desktop upload path abandons; a wizard corpus carrying it would have been reclaimed.
-
⛔ The graphical installer could not open its own window, and 0.9.7 shipped that way.
install_maincalledrun_window(InstallerWindow(...)). Python evaluates an argument before the
call, so the window was constructed beforerun_windowhad created theQApplicationit needs.
Qt answers a widget built with no application by printingQWidget: Must construct a QApplication before a QWidgetand aborting the process on its fatal handler: one line of stderr, no window,
no dialog, exit0xC0000409. Every copy of the 0.9.7 installer did this, on every machine.
run_apphad the identical call shape, so the main desktop window carried the same defect.run_windownow takes a factory rather than a window, so the application is created first and
the ordering cannot be decided at a call site. Passing an already-built widget raises aTypeError
naming the mistake instead of killing the process. -
⛔ The self-test that exists to prove the bundle runs was green for that build, and now cannot
be.--selftestconstructed its ownQApplicationand then its own window, in the correct
order, which is not the order the entry point used. It rehearsed a launch sequence nothing ships.
Every desktop test in the suite had the same shape, and not by carelessness: once any test in a
pytest session creates aQApplicationit cannot be unmade, so no in-process test can observe a
process that has none. The self-test now builds its window throughapplication_and_window, the
same code the entry point orders its launch with, andtests/test_desktop_launch_order.pyadds a
check that starts the real entry point in a fresh interpreter and asserts on the exit code,
which is the only thing here that reproduces a double-click.