mindex 1.1.0
⚠️ REQUIRED BEFORE THIS RELEASE WORKSReindex every project after upgrading, or its search stops working.
# 1. stop the server, 2. start it once (migrations 5 and 6 apply in place), then: mindex-index --root <repo> --force # for every projectThen drop the leftover
*_v1Qdrant collections by hand. The vector collection is
named{guid}_v2now, and the rename is not self-healing: the new name names no
collection while SQLite still reports every fileindexed. Search then fails one of two
ways —503 qdrant.unavailable, which reads as an infrastructure fault rather than a
missing index, or, once anything has touched the project and created an empty
collection, empty results with no error anywhere. Dropping the old collections is
deliberately not automated: leaving them is what makes a rollback possible.This release publishes
mindex_stale_collectionsandmindex_orphaned_collections
precisely so that state is visible instead of silent.
FOR HUMANS
A research report can now be argued with. Point a challenge at any stored report and
a second run takes it as the subject under examination: it extracts the report's claims
and spends a whole research budget trying to break each one against the live index.
Nothing in the original counts as evidence — every location has to be re-derived through
the challenger's own tools, and that re-derivation is the check. The result is a
verdict per claim, and a trust badge that follows the original report everywhere it is
listed from then on. Two rules are enforced rather than suggested, because a weak local
model will otherwise oblige you: an inconclusive challenge is not an acquittal, and a
challenge that showed no code of its own can dispute but never refute. A challenge whose
own evidence later goes stale quietly stops counting.
And it can be checked without spending anything. Verify re-scores a report's
citations offline — no model, no GPU, seconds rather than minutes — and answers two
questions it deliberately keeps apart: whether the report's provenance still matches what
was recorded when it was written, and whether the files it rests on have moved since. The
first is a fact about the journal and never changes; the second is the number that
actually tells you whether to re-run.
A run that runs out of time now comes back with something. The report used to be a
single generation at the end, so a model that could not produce it produced nothing — a
fifteen-minute run returning zero. Reports are written section by section now, and the
run banks what it can already answer as it goes, so a section that fails costs that
section rather than the document, and a run stopped by its budget assembles the findings
it had instead of an apology. Alongside it, three guards for the thing that actually
breaks long runs on a shared GPU: a turn that is still producing but has eaten the whole
run's wall clock is abandoned rather than waited on. One measured plan turn took 912
seconds for 702 tokens while every other turn that week ran thirty times faster.
The server stopped making clients guess. GET /health says ok, degraded or
unhealthy — degraded meaning only the optional local-model server is down, which is
exactly the state where you should keep searching and stop offering research. With only
two words, every client needed its own copy of which dependency was load-bearing, and the
VS Code extension's copy did not match the server's. Relatedly, a running server now
serves /llms.txt: the whole workflow as one document, with the live model list and the
measured cost of each effort level, so pointing an agent at one URL is enough.
Your index got about half as large. The ColBERT rerank vector was 99.6% of a
collection's bytes — 838 MB per segment against 2.6 MB for dense and 0.5 MB for sparse.
It is stored at half precision now, which is not a quality trade the way quantization
would be: that vector only orders results the other two already agreed on. This is what
the reindex above is for.
Downloads, at last. mindex-index and mindex-watch are built for Linux, Windows
and macOS (Intel and Apple silicon), the server for Linux x86-64, plus the VS Code
.vsix — all on native runners. Install the extension with
code --install-extension mindex-vscode-1.1.0.vsix --force.
The changelog
has the rest, including two dozen fixes for failures that previously produced no error at
all.
FOR MACHINES
Two removals. Everything else is additive.
callersis gone, with the reference half ofproject_file_symbolsand the repo map
that ranked by it. It was measured, not guessed: 23 810 reference rows against 3 397
definitions — 87.5% of the table — serving one tool called twice across twenty-five
recorded runs at a 50% miss rate. The edges are lexical, so the most-referenced names
wereassert_eq(1084),clone,Ok,unwrap,map, several with exactly one
definition in the tree. Separating a core abstraction from a name shared with a language
builtin is name resolution, which is the wall this project declines to climb.grep
answers "who uses this name" lexically and says so.parent_name/parent_kind
survive.POST /v0/{guid}/symbolsrejectsrole:with a400(request.malformed_body)
rather than ignoring it. Accepting it would answer arole: "reference"query with the
definitions — the one wrong answer that costs nothing to detect and looks exactly like a
right one.
Challenge. POST /v0/{guid}/research/{run_id}/challenge — same loop, same semaphore,
same budgets, its own citation gate. The subject is injected as hearsay under examination
and never seeds the evidence. The stream is byte-for-byte an ordinary research stream plus
one event, verdict ({challenged_run_id, overall, grounded, claims}), after excerpts
and before done. overall is confirmed/disputed/refuted or null = challenged,
inconclusive, which no reader may render as an acquittal. The grounding cap is symmetric:
grounded = verified > 0 AND unverified <= verified; an ungrounded refuted caps at
disputed, an ungrounded confirmed resolves to null. 400 when the subject is invalid
(staleness must not be spendable as refutation) or is itself a challenge (trust
aggregation is single-level). trust is derived at read time over valid challenges
only, severity wins; one challenge stands per report, and a newer one evicts the older
only if it has a parseable verdict.
Offline verification. GET /projects/{guid}/research/{run_id}/verification — pure
function over journal rows. provenance_matches: false is a journal bug, never news about
the code; staleness is recomputed against the index now. Nothing is stamped. Pre-migration-5
rows answer the staleness half only, with spans_available: false.
Live runs. GET /research/active, DELETE /research/active/{run_id} — global, not
per project, because the semaphore is. A run is now named at admission, streamed as the
first frame and registered while it runs; previously a run had no id until it ended, so an
occupied slot was an unattributable outage whose only remedy was a restart. The 429
names the endpoint.
GET /llms.txt — narrative plus a live section from the same snapshot /config
serves. Deliberately outside the OpenAPI spec; a test asserts the absence.
GET /health is tri-state. status ∈ ok/degraded/unhealthy; severity wins.
checks.* is exactly "ok" or "error" — test == "ok", never a prefix, since an
older server spells it "error: <reason>". Failure reasons go to the log, not the
response: this endpoint is readable by anything that can reach the port and a driver's
error chain carries paths, URLs and versions. HTTP is always 200. New:
research.{slots_total, slots_busy, oldest_inflight_age_ms} — a busy slot is never a
degradation, a wedged one is unhealthy.
Migrations 5 and 6, both applied in place. 5 (v1.3.0_research_verification.sql)
rebuilds research_runs for the validation and challenge columns and adds
research_run_evidence / research_run_citations / research_run_steps — the structured
journal the offline check reads. 6 (v1.4.0_symbol_definitions.sql) drops
project_file_symbols.role; it does not delete the reference rows, because symbol rows
are wholly derived and SYMBOLS_DERIVATION_VERSION 1.0 → 1.1 removes them on the next
indexing run, keeping the rule in one place.
Request-shaped budgets. max_report_sections, max_report_words,
checkpoint_every_steps and evidence_width join the per-axis overrides, each capped by a
[research] ceiling that startup refuses to set below effort.high. Out-of-range gets its
own code, validation.research_shape_out_of_range, because these carry floors and two
accept 0 = off.
citations gained three fields, all because a number was ambiguous on the wire:
shown_paths (how many files the run saw the inside of — the denominator verified: 0
needed), path_resolved (a cited path may be the unambiguous tail of exactly one shown
path) and server_written (a forced-synthesis report cites nothing by construction and
otherwise scored byte-for-byte what a clean report scores). The admission rule
steps > 0 && verified > 0 is a machine check now rather than a reader's discipline.
PROMPT_VERSION is 2.7. Partition a stored corpus on it.
New config, all optional: [research].max_turn_seconds, slow_turn_tokens_per_second,
slow_turn_unaccounted_ms, first_token_timeout_ms, allowed_models,
max_request_report_{sections,words}, max_evidence_width, checkpoint_every_steps;
[qdrant].timeout_ms / connect_timeout_ms / search_hnsw_ef; [model].encode_timeout_ms;
[limits].max_research_delete_ids; [slicer].fill_gaps. config.example.toml documents
all of them.
New metrics, contract-stable like the rest: mindex_stale_collections,
mindex_orphaned_collections, mindex_project_vectors, mindex_search_orphaned_winners,
mindex_search_unscorable_winners, mindex_worker_running, mindex_worker_exits_total,
mindex_research_unjournalled_runs, and the challenge families. The two collection gauges
are seeded at -1, never 0 — 0 is the healthy reading, so an unreachable Qdrant must
not be able to spell it.
Architecture, invariants and the measurements the reasoning rests on are in
.claude/CLAUDE.md.