Merge release/v2.7.0
CI / test (push) Failing after 2s

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-20 18:24:07 -07:00
co-authored by Claude Opus 5
20 changed files with 106 additions and 94 deletions
+62 -50
View File
@@ -1,56 +1,20 @@
# Changelog # Changelog
## Unreleased ## v2.7.0 (2026-09-20)
### Tuning ### Upgrade Notes
- `clawhdf5-agent`: **the HNSW parameters are configurable** - **Two read-path bugs fixed, one of them silent.** Datasets indexed by an
`MemoryConfig::hnsw_m`, `hnsw_ef_construction` and `hnsw_ef_search` Extensible Array (any dataset with one unlimited dimension) returned data
(defaults 16, 64, and 0 meaning "scale with `k`", i.e. today's behaviour). from the wrong chunks past their first few dozen. If you have readings taken
They were constants, so a deployment could not trade recall against memory from such a dataset with an earlier release, they may be wrong; re-read them.
or query speed at all. All three are persisted with the store. Values are - **A corrupt chunk index is now an error.** Fixed and Extensible Array
clamped where the index requires it: `clawhdf5-ann` asserts a graph degree structures carry checksums that were previously ignored, so damage surfaced
of at least 2, so a configured 0 — from a file, or from a caller who took 0 as plausible data from the wrong offset. Code that read a damaged file and
to mean "default" — used to abort the process inside the builder. Lowering got numbers will now get `ChecksumMismatch` instead. That is the point.
`ef_search` also no longer narrows the candidate pool that fusion sees. - **Breaking:** `MemoryConfig` gained `hnsw_m`, `hnsw_ef_construction` and
**Breaking:** `MemoryConfig` gained fields, so literal constructions need `hnsw_ef_search`, so literal constructions need updating;
updating; `..Default::default()` does not. `..Default::default()` does not. All three default to the previous
behaviour.
### Performance
- `clawhdf5-agent`: **opening a store is ~28% faster** (455 ms -> 327 ms at
100k x 384). `read_from_disk` memory-mapped the file and then copied the
entire mapping into a `Vec` for `File::from_bytes`, when `File::open`
memory-maps it directly — so every open paid a full-file memcpy for nothing.
Process peak memory is unchanged: the peak falls after the parse, during the
index build, so the transient never reached the high-water mark. The
footprint harness now reports that peak next to the retained figure, which
is how this was checked rather than assumed.
### Integrity
- `clawhdf5-format`: **Fixed and Extensible Array chunk indexes now verify
their checksums** (the `checksum` feature, on by default). Every structure
in both — header, index block, super block, data block and each data block
page — carries a Jenkins lookup3 checksum that was parsed past and ignored.
The consequence of skipping it is not a missing warning but wrong data: a
single flipped bit in a chunk address still parses, still points inside the
file, and the reader hands back whatever bytes now sit there as the chunk's
contents. Verified in both directions — the checksums accept files written
by HDF5 2.0 at 100 to 200 000 chunks, dense, sparse, filtered and paged,
and an interop test corrupts an address to confirm the read now fails
instead of returning data (it does return data when the check is removed).
### Performance
- `clawhdf5-accel`: **`dot_i8`, a runtime-dispatched int8 dot product** (AVX2:
sign-extend each half to `i16`, then `madd_epi16`; scalar fallback
elsewhere). The quantised HNSW index used a scalar loop while the `f32` path
it was measured against ran AVX2, so the ~13% throughput cost recorded for
`MemoryConfig::quantized_index` was a missing kernel rather than a property
of int8. With the kernel, at N = 100 000 x 384 and equal recall, the
quantised index answers **1.63x as many queries per second** (21 848 vs
13 399 at ef=64, recall 0.9940 vs 0.9945) and builds **1.8x faster** (1778
vs 3197 ms) — on top of holding a quarter of the vectors. Medians of three
alternating runs. It remains off by default only because the kernel is
AVX2-only and aarch64 falls back to the scalar loop. Integer arithmetic, so
the SIMD path is tested to agree with scalar bit for bit.
### Correctness ### Correctness
- `clawhdf5-format`: **datasets indexed by an Extensible Array returned wrong - `clawhdf5-format`: **datasets indexed by an Extensible Array returned wrong
@@ -100,6 +64,54 @@
including a depth-2 HDF5 2.0 chunk index with 40 000 records, now covered by including a depth-2 HDF5 2.0 chunk index with 40 000 records, now covered by
an interop test. an interop test.
### Integrity
- `clawhdf5-format`: **Fixed and Extensible Array chunk indexes now verify
their checksums** (the `checksum` feature, on by default). Every structure
in both — header, index block, super block, data block and each data block
page — carries a Jenkins lookup3 checksum that was parsed past and ignored.
The consequence of skipping it is not a missing warning but wrong data: a
single flipped bit in a chunk address still parses, still points inside the
file, and the reader hands back whatever bytes now sit there as the chunk's
contents. Verified in both directions — the checksums accept files written
by HDF5 2.0 at 100 to 200 000 chunks, dense, sparse, filtered and paged,
and an interop test corrupts an address to confirm the read now fails
instead of returning data (it does return data when the check is removed).
### Performance
- `clawhdf5-agent`: **opening a store is ~28% faster** (455 ms -> 327 ms at
100k x 384). `read_from_disk` memory-mapped the file and then copied the
entire mapping into a `Vec` for `File::from_bytes`, when `File::open`
memory-maps it directly — so every open paid a full-file memcpy for nothing.
Process peak memory is unchanged: the peak falls after the parse, during the
index build, so the transient never reached the high-water mark. The
footprint harness now reports that peak next to the retained figure, which
is how this was checked rather than assumed.
- `clawhdf5-accel`: **`dot_i8`, a runtime-dispatched int8 dot product** (AVX2:
sign-extend each half to `i16`, then `madd_epi16`; scalar fallback
elsewhere). The quantised HNSW index used a scalar loop while the `f32` path
it was measured against ran AVX2, so the ~13% throughput cost recorded for
`MemoryConfig::quantized_index` was a missing kernel rather than a property
of int8. With the kernel, at N = 100 000 x 384 and equal recall, the
quantised index answers **1.63x as many queries per second** (21 848 vs
13 399 at ef=64, recall 0.9940 vs 0.9945) and builds **1.8x faster** (1778
vs 3197 ms) — on top of holding a quarter of the vectors. Medians of three
alternating runs. It remains off by default only because the kernel is
AVX2-only and aarch64 falls back to the scalar loop. Integer arithmetic, so
the SIMD path is tested to agree with scalar bit for bit.
### Tuning
- `clawhdf5-agent`: **the HNSW parameters are configurable**
`MemoryConfig::hnsw_m`, `hnsw_ef_construction` and `hnsw_ef_search`
(defaults 16, 64, and 0 meaning "scale with `k`", i.e. today's behaviour).
They were constants, so a deployment could not trade recall against memory
or query speed at all. All three are persisted with the store. Values are
clamped where the index requires it: `clawhdf5-ann` asserts a graph degree
of at least 2, so a configured 0 — from a file, or from a caller who took 0
to mean "default" — used to abort the process inside the builder. Lowering
`ef_search` also no longer narrows the candidate pool that fusion sees.
**Breaking:** `MemoryConfig` gained fields, so literal constructions need
updating; `..Default::default()` does not.
### Documentation ### Documentation
- `clawhdf5-agent`: `BM25Index::search` claimed to use Block-Max WAND for early - `clawhdf5-agent`: `BM25Index::search` claimed to use Block-Max WAND for early
termination. It never did; it scores every match exhaustively. It now says termination. It never did; it scores every match exhaustively. It now says
+1 -1
View File
@@ -21,7 +21,7 @@ members = [
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.redclaw.dev/quantumclaw/clawhdf5" repository = "https://git.redclaw.dev/quantumclaw/clawhdf5"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-accel" name = "clawhdf5-accel"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "SIMD-accelerated operations for rustyhdf5" description = "SIMD-accelerated operations for rustyhdf5"
license = "MIT" license = "MIT"
+7 -7
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-agent" name = "clawhdf5-agent"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "HDF5-backed persistent memory store for on-device AI agents" description = "HDF5-backed persistent memory store for on-device AI agents"
license = "MIT" license = "MIT"
@@ -10,12 +10,12 @@ keywords = ["agent", "memory", "hdf5", "vector-search", "embedding"]
categories = ["database", "science", "algorithms"] categories = ["database", "science", "algorithms"]
[dependencies] [dependencies]
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0", features = ["parallel", "fast-checksum"] } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0", features = ["parallel", "fast-checksum"] }
clawhdf5 = { path = "../clawhdf5", version = "2.6.0" } clawhdf5 = { path = "../clawhdf5", version = "2.7.0" }
clawhdf5-io = { path = "../clawhdf5-io", version = "2.6.0", features = ["mmap"] } clawhdf5-io = { path = "../clawhdf5-io", version = "2.7.0", features = ["mmap"] }
clawhdf5-accel = { path = "../clawhdf5-accel", version = "2.6.0" } clawhdf5-accel = { path = "../clawhdf5-accel", version = "2.7.0" }
clawhdf5-ann = { path = "../clawhdf5-ann", version = "2.6.0", optional = true } clawhdf5-ann = { path = "../clawhdf5-ann", version = "2.7.0", optional = true }
clawhdf5-gpu = { path = "../clawhdf5-gpu", version = "2.6.0", optional = true, default-features = false } clawhdf5-gpu = { path = "../clawhdf5-gpu", version = "2.7.0", optional = true, default-features = false }
serde = { workspace = true } serde = { workspace = true }
byteorder = "1" byteorder = "1"
half = { workspace = true, optional = true } half = { workspace = true, optional = true }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-android" name = "clawhdf5-android"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Android JNI bridge for edgehdf5-memory HDF5 backend" description = "Android JNI bridge for edgehdf5-memory HDF5 backend"
license = "MIT" license = "MIT"
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-ann" name = "clawhdf5-ann"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "HNSW approximate nearest neighbor index stored as HDF5" description = "HNSW approximate nearest neighbor index stored as HDF5"
license = "MIT" license = "MIT"
@@ -10,9 +10,9 @@ keywords = ["hdf5", "ann", "hnsw", "nearest-neighbor"]
categories = ["algorithms", "science"] categories = ["algorithms", "science"]
[dependencies] [dependencies]
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0" } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0" }
clawhdf5-io = { path = "../clawhdf5-io", version = "2.6.0" } clawhdf5-io = { path = "../clawhdf5-io", version = "2.7.0" }
clawhdf5-accel = { path = "../clawhdf5-accel", version = "2.6.0" } clawhdf5-accel = { path = "../clawhdf5-accel", version = "2.7.0" }
rayon = { version = "1", optional = true } rayon = { version = "1", optional = true }
[features] [features]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-bench" name = "clawhdf5-bench"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Benchmark harnesses for clawhdf5-agent (Track 8)" description = "Benchmark harnesses for clawhdf5-agent (Track 8)"
license = "MIT" license = "MIT"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-cli" name = "clawhdf5-cli"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
description = "CLI for clawhdf5 agent memory — create, save, search, recall, stats" description = "CLI for clawhdf5 agent memory — create, save, search, recall, stats"
@@ -14,7 +14,7 @@ name = "clawhdf5"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
clawhdf5-agent = { path = "../clawhdf5-agent", version = "2.6.0" } clawhdf5-agent = { path = "../clawhdf5-agent", version = "2.7.0" }
clap = { version = "4", features = ["derive", "env"] } clap = { version = "4", features = ["derive", "env"] }
serde_json = "1" serde_json = "1"
serde = { workspace = true } serde = { workspace = true }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-derive" name = "clawhdf5-derive"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Derive macros for rustyhdf5 HDF5 traits" description = "Derive macros for rustyhdf5 HDF5 traits"
license = "MIT" license = "MIT"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-filters" name = "clawhdf5-filters"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Filter and compression pipeline for clawhdf5" description = "Filter and compression pipeline for clawhdf5"
license = "MIT" license = "MIT"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-format" name = "clawhdf5-format"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Pure-Rust HDF5 binary format parsing and writing — no C dependencies" description = "Pure-Rust HDF5 binary format parsing and writing — no C dependencies"
license = "MIT" license = "MIT"
@@ -25,7 +25,7 @@ pco = { version = "1.0", optional = true }
[dev-dependencies] [dev-dependencies]
serde_json = "1" serde_json = "1"
criterion = { workspace = true } criterion = { workspace = true }
clawhdf5-derive = { path = "../clawhdf5-derive", version = "2.6.0" } clawhdf5-derive = { path = "../clawhdf5-derive", version = "2.7.0" }
[[bench]] [[bench]]
name = "bench" name = "bench"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-gpu" name = "clawhdf5-gpu"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "GPU-accelerated vector operations for rustyhdf5 using wgpu compute shaders" description = "GPU-accelerated vector operations for rustyhdf5 using wgpu compute shaders"
license = "MIT" license = "MIT"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-io" name = "clawhdf5-io"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "I/O abstraction layer for rustyhdf5" description = "I/O abstraction layer for rustyhdf5"
license = "MIT" license = "MIT"
@@ -10,7 +10,7 @@ keywords = ["hdf5", "io", "science", "data"]
categories = ["filesystem", "science"] categories = ["filesystem", "science"]
[dependencies] [dependencies]
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0" } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0" }
memmap2 = { version = "0.9", optional = true } memmap2 = { version = "0.9", optional = true }
libc = { version = "0.2", optional = true } libc = { version = "0.2", optional = true }
tokio = { version = "1", features = ["fs", "io-util"], optional = true } tokio = { version = "1", features = ["fs", "io-util"], optional = true }
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-migrate" name = "clawhdf5-migrate"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "CLI to migrate SQLite agent memory databases to HDF5 format" description = "CLI to migrate SQLite agent memory databases to HDF5 format"
license = "MIT" license = "MIT"
@@ -14,9 +14,9 @@ name = "clawhdf5-migrate"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
clawhdf5-agent = { path = "../clawhdf5-agent", version = "2.6.0" } clawhdf5-agent = { path = "../clawhdf5-agent", version = "2.7.0" }
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0" } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0" }
clawhdf5 = { path = "../clawhdf5", version = "2.6.0" } clawhdf5 = { path = "../clawhdf5", version = "2.7.0" }
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.31", features = ["bundled"] }
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
half = { workspace = true } half = { workspace = true }
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-napi" name = "clawhdf5-napi"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Node.js native addon (napi-rs) exposing clawhdf5-agent to TypeScript/JavaScript" description = "Node.js native addon (napi-rs) exposing clawhdf5-agent to TypeScript/JavaScript"
license = "MIT" license = "MIT"
@@ -10,7 +10,7 @@ repository = "https://git.redclaw.dev/quantumclaw/clawhdf5"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
clawhdf5-agent = { path = "../clawhdf5-agent", version = "2.6.0" } clawhdf5-agent = { path = "../clawhdf5-agent", version = "2.7.0" }
napi = { version = "2", default-features = false, features = ["napi9"] } napi = { version = "2", default-features = false, features = ["napi9"] }
napi-derive = "2" napi-derive = "2"
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-netcdf4" name = "clawhdf5-netcdf4"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "NetCDF-4 read support built on rustyhdf5 — pure Rust, no C dependencies" description = "NetCDF-4 read support built on rustyhdf5 — pure Rust, no C dependencies"
license = "MIT" license = "MIT"
@@ -10,8 +10,8 @@ keywords = ["netcdf", "netcdf4", "hdf5", "science", "climate"]
categories = ["parser-implementations", "science"] categories = ["parser-implementations", "science"]
[dependencies] [dependencies]
clawhdf5 = { path = "../clawhdf5", version = "2.6.0" } clawhdf5 = { path = "../clawhdf5", version = "2.7.0" }
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0" } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0" }
[dev-dependencies] [dev-dependencies]
tempfile = { workspace = true } tempfile = { workspace = true }
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5-py" name = "clawhdf5-py"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Python bindings for rustyhdf5 — a pure-Rust HDF5 library" description = "Python bindings for rustyhdf5 — a pure-Rust HDF5 library"
license = "MIT" license = "MIT"
@@ -14,8 +14,8 @@ name = "clawhdf5"
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
clawhdf5_rs = { path = "../clawhdf5", version = "2.6.0", package = "clawhdf5" } clawhdf5_rs = { path = "../clawhdf5", version = "2.7.0", package = "clawhdf5" }
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0" } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0" }
pyo3 = "0.29" pyo3 = "0.29"
numpy = "0.29" numpy = "0.29"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project] [project]
name = "rustyhdf5" name = "rustyhdf5"
version = "2.6.0" version = "2.7.0"
description = "Python bindings for rustyhdf5 — a pure-Rust HDF5 library" description = "Python bindings for rustyhdf5 — a pure-Rust HDF5 library"
requires-python = ">=3.8" requires-python = ">=3.8"
license = { text = "MIT" } license = { text = "MIT" }
+6 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clawhdf5" name = "clawhdf5"
version = "2.6.0" version = "2.7.0"
edition = "2024" edition = "2024"
description = "Pure-Rust HDF5 reader/writer — no C dependencies" description = "Pure-Rust HDF5 reader/writer — no C dependencies"
license = "MIT" license = "MIT"
@@ -10,16 +10,16 @@ keywords = ["hdf5", "science", "data", "binary"]
categories = ["parser-implementations", "science", "encoding"] categories = ["parser-implementations", "science", "encoding"]
[dependencies] [dependencies]
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0" } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0" }
clawhdf5-io = { path = "../clawhdf5-io", version = "2.6.0" } clawhdf5-io = { path = "../clawhdf5-io", version = "2.7.0" }
rayon = { version = "1", optional = true } rayon = { version = "1", optional = true }
[dev-dependencies] [dev-dependencies]
tempfile = { workspace = true } tempfile = { workspace = true }
criterion = { workspace = true } criterion = { workspace = true }
clawhdf5-io = { path = "../clawhdf5-io", version = "2.6.0", features = ["mmap"] } clawhdf5-io = { path = "../clawhdf5-io", version = "2.7.0", features = ["mmap"] }
clawhdf5-format = { path = "../clawhdf5-format", version = "2.6.0", features = ["parallel", "fast-checksum"] } clawhdf5-format = { path = "../clawhdf5-format", version = "2.7.0", features = ["parallel", "fast-checksum"] }
clawhdf5-filters = { path = "../clawhdf5-filters", version = "2.6.0" } clawhdf5-filters = { path = "../clawhdf5-filters", version = "2.7.0" }
[[bench]] [[bench]]
name = "mmap_bench" name = "mmap_bench"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@redclaw/clawhdf5", "name": "@redclaw/clawhdf5",
"version": "2.6.0", "version": "2.7.0",
"description": "Node.js bindings for clawhdf5 — HDF5-backed agent memory with hippocampal consolidation", "description": "Node.js bindings for clawhdf5 — HDF5-backed agent memory with hippocampal consolidation",
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",