The docs described a "drop-in" OpenClaw memory backend enabled with `memory.backend = "clawhdf5"`. Checked against OpenClaw's source and docs (v2026.2.26 through v2026.9.6): that config was never valid — v2026.2-v2026.7 accepted only "builtin"/"qmd" and rejected unknown keys, so a Gateway given it refuses to start, and v2026.8.1 (OpenClaw 2.0) removed the key. No plugin was ever built (no manifest, no registration, no tools), nothing was tested against OpenClaw, the linked github.com/redclawsystems/openclaw is a 404, and @redclaw/clawhdf5 was never published. Decision (2026-09-25): not pursuing an OpenClaw plugin for now; ZeroClaw is the integration target. - Remove openclaw-integration.md, openclaw-config.md and migration-guide.md; add docs/openclaw.md: the status, what a memory plugin needs against v2026.9.6 (plugins.slots.memory, manifest with kind "memory", registerMemoryCapability / MemorySearchManager, prebuilt native packages), and what this repo has as building blocks. - README, QUICKSTART, USE_CASES, ROADMAP (Track 7 withdrawn), CLAUDE.md and the `openclaw` module docs describe ClawhdfBackend as what it is: a Markdown-oriented library backend, not an OpenClaw plugin. The QUICKSTART example is corrected (the old one called a three-argument create that does not exist) and states its limits. - packages/clawhdf5-node: marked unpublished and broken, "private": true so it cannot be published by accident; its bugs (snake_case vs camelCase fields, wrong addon path, no way to store an embedding, wrong WAL name) are recorded in docs/known-issues.md. - Two broken rustdoc links fixed along the way. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
11 KiB
clawhdf5
Purpose
Pure-Rust HDF5 format implementation with HNSW vector search, WAL-backed persistence, agent memory storage, and GPU-accelerated I/O. Used by ZeroClaw as its persistent memory and knowledge graph backend.
Architecture
Cargo workspace with 16 crates under crates/ (plus libaec-sys, an internal FFI bindings crate for the optional szip feature):
| Crate | Role |
|---|---|
clawhdf5-format |
HDF5 binary spec parser (superblock, B-tree, heap) — also holds shared type definitions and physical constants |
clawhdf5-io |
Read/write implementation |
clawhdf5-filters |
Compression filters (gzip, LZ4, Zstd, Blosc) |
clawhdf5-derive |
Proc-macro derive for HDF5-serializable structs |
clawhdf5 |
Main facade crate |
clawhdf5-netcdf4 |
NetCDF-4 compatibility layer |
clawhdf5-ann |
HNSW approximate nearest-neighbor vector index |
clawhdf5-agent |
Agent memory, session history, knowledge graph storage |
clawhdf5-gpu |
GPU-accelerated I/O via wgpu (hand-written WGSL compute shaders) |
clawhdf5-accel |
CPU SIMD acceleration path |
clawhdf5-migrate |
SQLite → HDF5 agent-memory migration |
clawhdf5-android |
Android JNI bindings |
clawhdf5-cli |
Command-line interface |
clawhdf5-napi |
Node.js native addon bindings |
clawhdf5-py |
PyO3 Python bindings |
clawhdf5-bench |
Benchmark suite |
Key Features
- Zero-C-dependency HDF5 read/write: no libhdf5, and deflate defaults to
pure-Rust zlib-rs (
fast-deflateopts into zlib-ng, which needs cmake).ci-test.shfails if a C-building crate enters the core crates' default tree. flate2 must keepruntime_detectionwith zlib-rs — without it zlib-rs loses SIMD and inflates 3.5x slower. MSRV is 1.92 (rust-version, checked in CI). - HNSW vector index for semantic similarity search over agent memories — the
clawhdf5-agenthnswfeature is on by default, sohybrid_searchuses the approximateclawhdf5-annindex for the vector stage (the index mirrors the cache and self-heals on drift). Build the agent with--no-default-features --features float16to force the exact linear cosine scan. The agent'sparallelfeature (also default) builds the index on a thread pool; the graph is identical with or without it. The index uses the HNSW paper's diversity heuristic for neighbour selection (plain closest-M capped recall on clustered data: 0.31 recall@10 at 100K). Its graph is saved to<store>.h5.annat each checkpoint and reloaded byopen()(tied to the checkpoint by a generation id; stale/damaged sidecars are ignored and the index rebuilt).MemoryConfig::quantized_index(on by default for new stores, persisted; stores predating the setting load asfalseand keep their f32 index — guarded bytests/fixtures/store_v2_5_0.h5; CLI opt-out iscreate --f32-index) stores the index's own copy of the embeddings asi8, which roughly halves a loaded store's memory (2.72x -> 1.74x the raw vectors at 100K); because quantised distances are approximate andefcannot compensate, the query path then re-scores the candidate pool against the exact embeddings, which holds recall at the f32 index's level. It is also faster at equal recall: 1.63x the QPS on x86-64 (AVX2) and 1.18x on a Raspberry Pi 5 (clawhdf5_accel::dot_i8, NEONSDOTvia inline asm since the intrinsic is unstable; plain NEON on pre-dotprod cores). The aarch64 code iscfg'd out on x86, so x86 CI never compiles or lints it — test it on real ARM (rpivision02, 10.0.2.3, is a Pi 5).hybrid_searchkeeps one incremental BM25 index for the life of the store and never writes the store: Hebbian activation boosts are persisted by the next checkpoint (or on drop), not per query. Measure any search-path change withcargo run --release -p clawhdf5-bench --bin search_harness(baselines inBENCHMARKS.md). - WAL (write-ahead log) for crash-safe persistence, with a chained CRC32
trailer per entry (each entry's CRC folds in the previous entry's CRC) so a
corrupted, reordered, duplicated, or spliced entry stops replay cleanly
instead of loading bad or tampered data. The pre-chaining per-entry-CRC
format (v2) is still fully readable; the oldest no-CRC format (v1) is only
reachable through the one-time migration path in
HDF5Memory::open, not through the publicWalFile::read_entries. What the WAL guarantees: integrity, ordering, and recovery from a process crash at any point — including between a checkpoint and the WAL truncate (each checkpoint records aWalMarkin/meta, andopen()skips the WAL prefix the.h5already contains, so entries are never applied twice). Checkpoints and snapshots are made durable as a unit (temp file synced, renamed, directory synced). What it does not guarantee: individual WAL appends are not fsynced (a deliberate latency trade-off), so saves made since the last checkpoint can be lost on power failure or kernel panic. Current header version is 4 (adds theUpdaterecord used bysave_or_update); v3 files are read and upgraded in place. - A store has a single writer:
HDF5Memory::create/openhold an exclusive advisory lock on<store>.h5.lockand a second opener getsMemoryError::Locked. UseHDF5Memory::open_read_onlyfor a lock-free, never-writing point-in-time view (the CLI'srecall/stats/agents-md/exportdo). An unreadable WAL (torn header, bad magic) is quarantined to<store>.h5.wal.corrupt-<ts>rather than blockingopen(); a WAL with an unknown newer version still fails and is left untouched. MemoryConfig::float16(on by default for new stores, persisted; existing stores keep their recordedfalse— guarded by the v2.5.0 fixture intests/float16_store.rs; CLI opt-out iscreate --f32) writes/memory/embeddingsas IEEE half precision (48% smaller file at 100K; LongMemEval with real MiniLM embeddings identical to f32).MemoryCache::half_precisionrounds each embedding as it enters the cache (push, update, WAL replay, and on load of a store stillf32on disk), so memory and file agree bit for bit; the conversions live inclawhdf5_format::float16and must stay the single implementation. Values beyond ±65504 areMemoryError::InvalidEntry. Interop: every file must open in h5py —f32datasets and empty datasets did not until 2026-09-23 (seedocs/known-issues.md); the agent'sh5py_interoptest guards a whole store.HDF5Memory::search(query_emb, text, &SearchOptions)is the full search path: optional source-channel filter (applied before ranking; exact scan of the allowed records whenever cheaper thanpool × Mindex distance evaluations, and as the fallback when the pool comes back short), fusion, activation scaling, optional re-ranking and confidence rejection.hybrid_search/hybrid_search_withare thin wrappers;ClawhdfBackend(theopenclawmodule) issearchwith re-rank + confidence on.- OpenClaw is not supported (decided 2026-09-25): clawhdf5 is not an
OpenClaw memory plugin and never was — the old
memory.backend = "clawhdf5"config was never valid. Don't reintroduce OpenClaw claims;docs/openclaw.mdrecords what a real plugin would need. ZeroClaw is the integration target. Measure changes withsearch_harness --options-study. MemoryConfig::compressionis off by default; when on, embeddings are deflate-compressed, or Zstd with the agent'szstdfeature (links libzstd).- Signed checkpoints (
clawhdf5-agentsigningmodule): withHDF5Memory::set_signing_keyevery checkpoint stores an Ed25519-signed manifest (SHA-256 per record in a Merkle tree + settings/sessions/graph hashes; per-record hashes in/integrity/record_hashes);HDF5Memory::verify(path, &pk)locates edits. The hashes must cover exactly what the file persists in the form the loader returns it (strings lose trailing NULs; an empty WAL mark is not written) or untouched stores stop verifying —tests/signed_store.rsround-trips awkward strings. The key is never persisted; a signed store refuses to checkpoint without it (MemoryError::SigningKeyRequired, andMemoryErroris#[non_exhaustive]). WAL entries after the checkpoint are not covered. Dataset::verify_provenance()(clawhdf5 facade,provenancefeature, on by default) recomputes a dataset's SHA-256 and compares it against the_provenance_sha256attribute written automatically on save whenDatasetBuilder::with_provenanceis used. It's opt-in per call, not run automatically on open — it decodes and hashes the whole dataset. The hash is unkeyed (tamper-evident, not tamper-proof): it detects accidental corruption, not a deliberate actor able to modify both the data and the stored hash.clawhdf5-agent'sHDF5Memory::save/save_batch/save_or_updaterun every write through an in-memory (session-scoped, not persisted to disk) provenance ledger and write-anomaly detector: a content hash per record (provenance.rs) for detecting accidental mid-session corruption, plus rate-limit/injection-pattern/source-distribution checks (anomaly.rs). Alerts never block a save — drain them withHDF5Memory::take_anomaly_alerts.MemorySourcefor this bookkeeping is inferred from the caller-suppliedsource_channelstring (a heuristic, not an authenticated trust boundary).- GPU-accelerated batch I/O for large dataset processing
- Python and Node.js bindings for cross-language use
- NetCDF-4 compatibility for scientific data interop
Workflows
Build
cargo build --release
Test
cargo test --workspace
CI
.gitea/workflows/ci.yml has two jobs, both green as of 2026-09-22:
test(ubuntu-latest, inrust:latest) runsscripts/ci-test.shwith the h5py/netCDF4 interop suites required (CLAWHDF5_REQUIRE_INTEROP=1). Served by thetankandarchitectrunners.test-arm64(linux_arm64) lints and tests the aarch64 code — the NEON kernels arecfg'd out on x86, so this is the only place they are built. Served byvision-01(host mode) andvision-02(Docker), so steps must work in both.
Keep workflows free of JavaScript actions (actions/checkout, actions/cache,
…): rust:latest has no node, and not every runner reaches GitHub, where
they are fetched from. Check out with plain git instead. The test job
installs cmake for the opt-in fast-deflate (zlib-ng) steps; the default
build needs no C toolchain, so test-arm64 does not.
All runners are on gitea-runner 3.5.0, from docker.gitea.com/act_runner
— gitea/act_runner:latest on Docker Hub is frozen at 0.6.1.
CLI
cargo run -p clawhdf5-cli -- --help
# create, save, search, recall, stats, flush-wal, agents-md, export, snapshot subcommands
Python bindings
cd crates/clawhdf5-py
maturin develop
python -c "import clawhdf5; print(clawhdf5.__version__)"
Integration
ZeroClaw imports this as a Cargo feature (clawhdf5 feature flag) to persist agent memory with HNSW vector search for context retrieval.