Repository navigation
Releases: opendb-ai/openDB
Release list
v2.0.1 — a superseded fact could outrank the fact that replaced it
Patch for a knowledge-update regression introduced in 2.0.0. Upgrade if you are on 2.0.0.
2.0.0 shipped rank_weight_recency = 0.5. At that weight a stale memory beat the memory that replaced it whenever the stale row matched one more query token.
Two rows, 947 and 795 days old, asked "frontend framework team":
"The team uses React for the frontend." lexical=1.0000 recency=0.0051 -> 1.3038 ← won
"We migrated the frontend from React to Svelte." lexical=0.4735 recency=0.8412 -> 1.1940
Lexical ratio-to-best handed the older row a 0.53 edge while recency returned only 0.42 after weighting. The row saying the team migrated away from React lost to the row saying it uses React — the knowledge-update case this project is built around, regressed against 1.5.0.
memory_stress_bench.py catches it. 2.0.0 published that suite's result as 23/23 while the shipped code scored 22/23, confirmed by A/B against the commit before the architecture-review branch.
The fix
rank_weight_recency 0.5 → 0.75. The inversion flips below 0.63, so this leaves margin without reaching 1.0, which would assert recency and relevance matter equally — a stronger claim than anything measured here.
| 2.0.0 | 2.0.1 | |
|---|---|---|
| memory stress suite | 22/23 | 23/23 |
| pooled LongMemEval R@5 | 79.1% | 79.1% |
| CodeMemEval R@5, questions as written | 100% | 100% |
| CodeMemEval R@5, questions restated | 75.0% | 79.2% |
| temporal ranking suite top-1 | 80.0% | 80.0% |
Nothing regressed, including the two cases the temporal suite already fails.
The temporal suite cannot select this value — it returns an identical 80.0% top-1 with identical per-category results for every weight in [0.25, 1.5], and its own cross-validation prefers 0.25, which fails the case harder. The choice rests on the two suites that can see the difference.
Also fixed
RankingWeightscarried a second, independent default.from_settingsreads config, but a bareRankingWeights()used the dataclass default — changing onlyconfig.pyleft every caller that skips settings on the old weights. A test now asserts the two agree.ranking.py's module docstring described an algorithm the module does not implement (Reciprocal Rank Fusion plus a bare hyperbolic recency map; the code has used ratio-to-best and a span-weighted blend since the rewrite). It is the first thing read when a ranking result looks wrong, and it sent two separate investigations down the wrong path.- Removed a duplicated
tierassignment infuse().
Docs
REPORT and README carry numbers re-measured against the fixed scorer: the restated CodeMemEval split rises to 79.2% R@5 (19/24, 95% CI [59.5%, 90.8%]) and pooled R@1 to 52.3%.
Tests 417 → 420, including a regression test pinning the 0.63 boundary so a future weight change fails loudly instead of silently degrading knowledge-update recall.
Full changelog: https://github.com/opendb-ai/openDB/blob/main/CHANGELOG.md
v2.0.0 — architecture review remediation
An architecture review of the storage and memory layers found defects reproducible on the default configuration. This release fixes them, adds regression coverage that fails against the previous code, and withdraws or restates published claims that were measured on an instrument that could not fail.
Verification is A/B — the same behaviour probe against the previous release and against this one:
v1.5.0: 14 defects present in this tree
v2.0.0: 0 defects present in this tree
Tests 282 → 417. With PostgreSQL available: 417 passed. Without: 403 passed, 14 skipped.
⚠️ Breaking: defaults changed
| setting | was | now |
|---|---|---|
backend |
postgres |
sqlite |
host |
0.0.0.0 |
127.0.0.1 |
cors_origins |
["*"] |
[] |
vision_enabled |
true |
false |
With auth unenforced, the old combination made the workspace reachable from the local network, scriptable by any web page the developer visited, and uploaded indexed images to a third-party API off an ambient env var. An upgrade that does not set these explicitly will find a Postgres-backed server running on SQLite and no longer reachable off localhost.
Data loss and correctness
- Supersede destroyed data on the default memory type. Three dated bug-fix records about one component collapsed into one, and
superseded_idpointed at a row that no longer existed. Prior versions now go tomemory_revisions, readable viamemory_history(). - Reading a memory reset its time-decay clock. A 400-day-old fact that had been read once outranked the fresh fact that replaced it.
- Recall set
confidence = 1.0on every hit, so the FSRS curve could only demote memories nobody read. Recall is read-only now. totalwas the candidate-window size, not the match count. Anyoffset >= 60returned nothing however many memories matched.
Transactions, retrieval, PostgreSQL parity
- All writes go through one
write_txn():BEGIN IMMEDIATE, rollback on anyBaseException, bounded retry with jitter onSQLITE_BUSY. CreateInvoicewas a single unicode61 token, soinvoice,create invoiceand "where do we create invoices" all returned zero results — on the project's own flagship example.- Japanese and Korean were routed to jieba, a Chinese segmenter. Tokenization is now fingerprinted and skew is reported by
opendb doctor. - PostgreSQL had carried none of the SQLite fixes, plus an unconditional
BEFORE UPDATEtrigger. It now passes the same conformance suite, every assertion parametrised over both backends.
Security
ApiKeyMiddleware was implemented, documented, unit-tested — and never mounted, so FILEDB_AUTH_API_KEY did nothing. _resolve_workspace swallowed the ValueError from relative_to() and returned the path anyway, making the only containment check a no-op. A crafted grep pattern hung over 20 seconds on a two-line file. sort_field/sort_dir were interpolated into ORDER BY unvalidated on PostgreSQL. Uploads read the whole body then checked the size.
⚠️ Benchmark claims withdrawn and restated
"100% R@5" is withdrawn. It was measured on longmemeval_oracle.json, where all 500 questions satisfy set(haystack_session_ids) == set(answer_session_ids) with zero distractors — R@5 is arithmetically guaranteed for 497/500. Pooled mode indexes every question's sessions into one 882-session corpus: R@5 = 79.1%, median recall 7.5 ms.
CodeMemEval is restated with its error bars. 96.3% is 26/27, whose 95% interval is [81.7%, 99.3%] — 17.6 points wide, overlapping the cheap reader's 92.6% across nearly its whole width. This benchmark cannot tell those two readers apart, and per-category results (n = 3–6) are now reported as counts.
The paraphrase split is now measured rather than asserted. Same corpus, same gold sessions, only the question wording differs:
| Questions | R@1 | R@5 |
|---|---|---|
| as written | 100% (24/24) | 100% (24/24) |
| restated, 0% lexical overlap | 45.8% (11/24) | 75.0% (18/24) |
That gap is the honest boundary of a pure-lexical retriever, and it was previously averaged away.
The LLM judge is unvalidated. Both accuracy figures come from a judge sharing a model family with the reader it grades, and nothing has yet measured how often it accepts a wrong answer — treat them as upper bounds. codemem_hard.py --run-judge now measures it, scoring 10 plausible-but-false answers against correct-answer controls (a judge that rejects everything otherwise scores a flawless 0% false-accept rate).
Notes
- Eight existing tests asserted the defective behaviour (e.g.
test_recall_resets_confidence_to_oneliterally tested the bug). They are rewritten to assert corrected semantics, not deleted. - PostgreSQL tests skip unless
OPENDB_TEST_PG_DSNis set, so CI stays green without a server.
Full changelog: https://github.com/opendb-ai/openDB/blob/main/CHANGELOG.md