Skip to content

chore(release): v0.18.0 -> main - #1244

Merged
ajianaz merged 134 commits into
mainfrom
chore/release-v0.18.0-to-main
Sep 14, 2026
Merged

ajianaz merged 134 commits into
mainfrom
chore/release-v0.18.0-to-main

Conversation

@ajianaz

@ajianaz ajianaz commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

What

Release merge: v0.18.0 (develop) -> main. Version files resolve to 0.18.0 (develop side, per release runbook); benchmarks/RESULTS.md and benchmarks/longmemeval/run_eval.py removed (superseded by the benchmarks restructure).

Why

Cut the v0.18.0 release. Release gate already passed at the merge content (develop 14da6d0 == this tree, verified empty-diff earlier): full-500 LongMemEval revalidation reproduced the published baseline - recall@5 non-abs 0.9456 vs 0.9457, strict@5 440/500, paired flips +2/-1 (within documented run noise); pre-release QA sandbox 12/12, cora scan clean (1 pre-existing major -> issue #1243).

Testing

  • Release gate: full-500 revalidation PASS (results in uteke-bench 2026-09-14-final018-release-gate)
  • QA sandbox (isolated, prod md5 tripwire clean): remember --timestamp, time-travel --at, room rename/update/move-memory, update , default recall, payload contract - all pass
  • CI on the source commits: all green

ajianaz and others added 30 commits August 11, 2026 22:10
3 files still referenced ~30ms (stale from early benchmarks).
Actual measured P50 at 1K-10K memories is ~45ms per benchmarks.md.
Updated: architecture.md, mcp.md, index.md
…-to-45ms

fix(docs): standardize recall latency to ~45ms
- Add internal/ to .gitignore (prevents future strategy/launch doc leaks)
- Add benchmarks/longmemeval/results_*/ to .gitignore (ephemeral data)
- Add docs/package-lock.json to .gitignore (VitePress build artifact)
- Move blog/comparison-2026.md → docs/comparison-2026.md (consolidate docs)
- Remove stale benchmarks/longmemeval/results_diverse/ (raw output, not summary)
- Remove docs/package-lock.json from tracking

Audit confirms: zero internal/ files in entire git history after filter-repo scrub.
…#1015)

* feat(bench): batch import + strategy flag + API embedding auto-config

- Replace 53x individual remember calls with single JSONL batch import (10x speedup)
- Add --strategy flag to pass vector|hybrid to uteke recall
- Auto-configure embedding API from EMBED_API_KEY/EMBED_API_BASE env vars
- Add --chunk-sessions flag for session chunking (Tier 2 prep)
- Dedup recall results by session_id (first occurrence = highest rank)
- Raise subprocess timeout 120s -> 600s for large imports

Initial 5Q results: hybrid R@5=1.000 vs vector R@5=0.800 (+20pp)

* fix(bench): only mark sessions inserted after import succeeds

Address Cora findings:
- Move inserted_sids population to after batch import success
- Parse import response to verify imported_count > 0
- Log warning if import returns 0 inserted

* fix(bench): resolve uteke binary by absolute path to avoid x86_64 PATH clash

Background subprocess calls were resolving to /opt/data/.cargo/bin/uteke
(x86_64) instead of target/release/uteke (AArch64). Now resolves relative
to repo root with shutil.which fallback.

* docs(bench): add 50Q hybrid results — R@5 98.0%, R@10 100%

Strategy comparison (uteke only):
- Vector 500Q: R@5=85.4%, R@10=88.5%, NDCG@5=0.810
- Hybrid 50Q: R@5=98.0%, R@10=100%, NDCG@5=0.960
- Improvement: +12.6pp R@5

Hybrid uses RRF (k=60) fusion of vector + FTS5 search.
1 miss at R@5 (single-session-user), recovered at R@10.

run_eval.py changes:
- Add --strategy flag (vector|hybrid)
- Throttle: taskset -c 0-1 + nice -n 19
- Absolute binary path resolution
- Timeout: 900s per question

* fix(core): add dedup, retry, and auto_link to import path (#1005)

Import pipeline was missing 3 features that remember() has:
1. Dedup check — cosine >= 0.95 skips duplicate entries
2. Retry on embedding failure — 3 retries with backoff
3. Auto-link cosine edges — graph edges for imported memories

Changes:
- import_export.rs: call check_duplicate() before insert, use
  retry_embed() instead of single-attempt embed(), call
  auto_link_cosine() after successful insert
- operations.rs: make retry_embed() and check_duplicate() pub(crate)
  so they're accessible from import_export.rs

Import path is now semantically consistent with remember() path.

* fix(core): cross-compile ORT_LIB_NAME for Android/iOS (#1014)

Add target_os = "android" to Linux .so branch and target_os = "ios"
to macOS .dylib branch. Without these, uteke-core fails to compile
for mobile targets with E0425 (cannot find value ORT_LIB_NAME).

Android uses libonnxruntime.so (same as Linux, loaded via jniLibs).
iOS uses libonnxruntime.dylib (same as macOS, framework embedded).

This unblocks uteke-mobile cross-compilation.

* feat(core): hybrid as default recall strategy

Change default_strategy from vector to hybrid (RRF: vector + FTS5).

Benchmark justification (LongMemEval-S):
- Vector 500Q: R@5=85.4%, R@10=88.5%
- Hybrid 50Q:  R@5=98.0%, R@10=100.0%
- Improvement: +12.6pp R@5

Changes:
- config.rs: default_strategy = hybrid + assert in test
- cli.rs: update help text default to hybrid
- recall.rs: update comment to reflect new default
- configuration.md: update all references
- cli-reference.md: reorder examples, hybrid first as default

Users who want vector-only: set default_strategy = vector in config
or use --strategy vector flag.

* fix(bench): guard taskset/nice with sys.platform check

Cora finding: taskset is Linux-only, would FileNotFoundError on macOS.
Now conditionally applied only when sys.platform == 'linux'.

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…ng text (#1006, #1002) (#1016)

#1006: CLI update check skipped when UTEKE_NO_UPDATE_CHECK env var is set.
Benchmark script sets this automatically, saving ~500ms per subprocess call.

#1002: Pre-truncate text before tokenizer to avoid wasted CPU on tokens
beyond MAX_SEQ_LEN (2048). Uses 4 chars/token heuristic with char-boundary
safety.

#1003: Already resolved — idx_memories_namespace index exists in SCHEMA_INDEXES.
#1004: Already resolved — compute_graph_signals uses single batched SQL query.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…enance (#1007, #1010, #1012, #1013) (#1017)

* feat: Phase 3 — lifecycle/deprecated endpoint, memory tools guide, source provenance (#1007, #1010, #1012, #1013)

#1007: GET /lifecycle/deprecated — list deprecated memories with sunset info
- list_deprecated() in aging.rs with DeprecatedMemoryInfo struct
- Handler + route + API registry entry

#1010: Memory tools guide injection
- guide.rs module with default_guide() for system prompt injection
- CLI: uteke guide command
- API: GET /guide endpoint

#1012: Implicit memory hierarchy docs
- docs/organizing-memories.md — type + importance pattern
- VitePress nav entry

#1013: Auto-populate source provenance
- CLI extraction: set source_label from input filename, source_type='extract'
- Server extraction: set_source on each extracted fact
- CLI output: display source_type alongside source in verbose mode

* docs: regenerate api-reference.md for new endpoints (#1007, #1010)

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
SYSTEM_PROMPT now requests scene-segmented JSON output with type and
priority per fact. parse_facts handles three formats: scene-segmented
nested JSON, flat object array with type/priority, and legacy flat
string array (backward compatible).

ExtractedFact struct carries content, scene, fact_type, priority.
CLI and server callers use remember_typed + set_importance + scene tag.

Offline mode unaffected — ExtractedFact::flat wraps existing strings.
9 new tests cover nested parsing, backward compat, dedup, priority
range validation.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
- Version bump to 0.14.0
- CHANGELOG entry for v0.14.0
- Docs: cli-reference scene-segmented extraction section
- Docs: memory-lifecycle deprecated endpoint

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
- Add 60s/30s/30s delay between crate publishes for index propagation
- Add repository, rust-version, documentation, homepage to uteke-mcp
- Fixes: uteke-mcp/cli/server fail because uteke-core not yet in index
- Fixes: 'manifest has no documentation, homepage or repository' warning

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Root cause: commit 0b5cad3 removed docs/package-lock.json from git tracking
and added it to .gitignore. The deploy-website workflow still referenced it
via cache-dependency-path, causing setup-node to fail:
'Some specified paths were not resolved, unable to cache dependencies.'

Fix:
- Replace actions/setup-node with oven-sh/setup-bun
- Replace npm ci with bun install --frozen-lockfile
- Replace npm run build with bun run build
- Add docs/bun.lock as tracked lockfile
- Remove docs/package-lock.json from .gitignore (no longer relevant)

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Add mutation testing infrastructure to validate test quality across
critical pure-logic modules. 11 new mutation-killing tests improve
salience_recency.rs score from 76% to 96%.

Changes:
- Add cargo-mutants config (mutants.toml) with exclusions for
  modules requiring external services
- Add mutation-testing CI workflow (develop→main PRs only,
  pre-release quality gate)
- Add .gitignore entries for mutants output directories
- Add mutation-testing.md developer documentation
- Write 9 mutation-killing tests for salience_recency.rs
- Write 2 mutation-killing tests for recall_cache.rs

Results (cargo-mutants v27.1.0):
  jaccard.rs:       12 mutants, 9 caught, 0 missed (100%)
  salience_recency: 53 mutants, 48 caught, 3 missed (96%)
  recall_cache:     25 mutants, 18 caught, 5 missed (80%)

Refs: nginjen mutation testing pattern (#mutation-testing)

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…ion-killing tests (score 50%→97%) (#1024)

Two production bugs found by mutation testing:

1. Heading duplication: oversized markdown sections had their heading
   prepended twice in the first sub-chunk (once by split_by_headings,
   once more by the sub-chunk loop), corrupting downstream embeddings.
   Fixed by removing the dead heading_prefix re-prepend path entirely.

2. Multibyte infinite loop: split_long_text's zero-progress guard
   advanced by raw byte offsets that could land inside multi-byte UTF-8
   characters (CJK/emoji), flooring back to start forever.
   chunk_markdown("日本語", 2) would hang. Fixed with a proper
   forward-char-boundary advance in the guard.

Also:
- 40 new mutation-killing tests (chunker: 20 → 60 tests)
- cargo-mutants config moved to .cargo/mutants.toml (v27 schema:
  exclude_globs/exclude_re, valid keys only); glob paths fixed to
  match from workspace root; CLI update_check also excluded
- 3 proven-equivalent mutants excluded with documented reasoning
- docs/mutation-testing.md: final scores, bug postmortem, timeout notes

Verify run: 164 mutants, 149 caught, 0 missed, 5 timeout, 10 unviable (97%)

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Patch release: chunker heading duplication fix, multibyte infinite
loop fix, mutation testing hardening (PR #1024).

- Bump workspace version 0.14.0 -> 0.14.1
- Bump intra-workspace deps (cli/mcp/server -> core 0.14.1)
- CHANGELOG entry for 0.14.1

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…1030)

* fix(cli): embed bundled assets inside crate root for crates.io publish

include_str! paths pointed outside the package root (../../../.agents,
../../../extensions). cargo publish cannot bundle files outside the
package, so uteke-cli has failed to publish since June (stuck at 0.4.3)
while the release workflow hid the failure with continue-on-error.

Assets now live in crates/uteke-cli/assets/ and ship inside the .crate.
Verified locally: cargo package -p uteke-cli passes with full verify.

* ci(release): verify crates.io versions after publish

The continue-on-error on publish steps (added for the tag race fix)
also hides genuine publish failures. Verify all four crates report the
tagged version on crates.io; fail the job if any crate lags behind.

* style: cargo fmt

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
The v0.14.2 release reused the v0.13.0 notes because a committed
RELEASE_NOTES.md is preferred over auto-generation and nothing checked
which version it was for. The release shipped with wrong notes until
manually fixed.

The guard now requires the file to mention the tagged version. Also
remove the stale v0.13.0 file so the next release auto-generates from
the CHANGELOG unless someone writes fresh notes.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
, #1035) (#1038)

* fix(server,mcp): default recall strategy to hybrid, validate strategy at boundary (#1034, #1035)

HTTP: resolve strategy once (request > [recall] default_strategy config > hybrid). Invalid strategy returns 400 on all paths (bare recall, unified search, v1). The eager legacy recall that ran before strategy resolution is removed. Memory-only recall path routes through recall_hybrid with the same 3x over-fetch post-filter pattern used by recall_unified_memories (entity/category filters).

MCP: uteke_recall schema exposes strategy (vector|fts5|hybrid|graph). Default resolves to hybrid, invalid values return a loud JSON-RPC error (-32603) instead of silently falling back to vector.

Server startup: sanitize [recall] default_strategy from uteke.toml — invalid value warns and falls back to hybrid so a config typo cannot 400 every request with a message blaming the request.

Docs: api-reference.md and mcp.md now describe the hybrid default, HTTP 400 on invalid, and the config fallback chain.

Verified empirically against a scratch store: HTTP matrix 10/10, MCP harness 8/8 including engine parity (default == hybrid, warm cache) and loud bogus rejection.

* docs: regenerate api-reference via docgen (strategy hybrid default, 400 on invalid)

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…plate file (#1041)

* fix(ci): replace unquoted heredoc in release notes generation with template file

The unquoted heredoc << RELEASEEOF underwent shell expansion: markdown
backticks became command substitutions and executed on the runner.
v0.14.3 release: installer curl|sh ran, uteke-serve --port 8767 started
and blocked the job for 48 minutes (2 runs, deterministic).

Move the static tail (downloads table + quick start) to
scripts/release-notes-template.md and substitute __VER__ via sed.
Zero shell expansion over markdown content. Validated locally: generate
completes instantly, output byte-correct.

Fixes the release pipeline for all future releases.

* ci: re-trigger review bots (LLM API transient error)

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…pages, fix release-notes asset names (#1055)

* docs: sync stale version refs, roadmap v0.13–v0.14, merge comparison pages, fix release-notes asset names (#1043)

- README/README.id/AGENT.md/install.md: version + test-count updates (0.14.3, 530+ tests); AGENT.md now lists all 5 workspace crates incl. docgen
- docs/roadmap.md: add v0.13.0–v0.13.2 and v0.14.0–v0.14.3 entries from CHANGELOG
- comparison: merge comparison-2026.md (canonical long-form) into comparison.md, keep at-a-glance matrix + extraction table from the old page, drop dead /blog link
- extensions/hermes-memory-provider → extensions/hermes-uteke-memory (+ embedded assets copy and all path refs) — dir name no longer references the removed Mode B provider
- scripts/release-notes-template.md: download table filenames get v prefix to match actual release assets (#1043)

* fix(release): keep v prefix on pinned-install example in release notes template

Co-authored-by: VIVAAN-DHAWAN <VIVAAN-DHAWAN@users.noreply.github.com>

Same fix as #1046 — UTEKE_VERSION is used verbatim as the release tag by
install.sh, so the pin example needs the v prefix too. Picking it up here so
#1043 closes fully through this PR once the rest of the audit lands.

* Revert "fix(release): keep v prefix on pinned-install example in release notes template"

This reverts commit 222d067.
…1056)

Agents are advisory-mode by default: audit/check requests are not
authorization to mutate. Gate push/merge/cherry-pick from others' PRs,
comments/edits on others' PRs/issues, PR retargeting, and anything
irreversible or externally visible behind explicit maintainer approval.
Correct flow: analyze → present options → wait → execute approved scope
→ report. Real incident 2026-08-17 documented as cautionary example.
…1059)

Test hardcoded libonnxruntime.so; on macOS ORT_LIB_NAME is
libonnxruntime.dylib so the exact-match path returned None and the
assertion panicked. Use the platform constant instead.
All id call sites across uteke-core (remember, chunks, graph, edges,
timeline, orphans, import) now mint UUIDv7. Storage and wire format
unchanged (TEXT uuid); v4/v7 coexist — existing stores open unchanged.
Adds two tests: v7 version nibble + ascending order for consecutive
mints, and v4/v7 parse coexistence.
#1047) (#1061)

Root cause was NOT a partial delete — soft_forget() correctly marks the
row deprecated and removes the vector. The leak was read-side:
- list()/search_content() never filtered deprecated rows → ghosts in
  'uteke list' after forget
- store.count(None) counted deprecated rows → doctor/verify reported
  permanent DB/Index MISMATCH after any soft-forget
- load_all() (repair/verify source) included deprecated rows → repair()
  re-added soft-deleted vectors to the index, resurrecting them in recall

Fix, uniform active-only contract:
- list() (all 4 branches) + search_content(): AND deprecated = 0
- load_all(): AND deprecated = 0 (recompute_importance also stops
  wasting cycles on hidden rows)
- count(): active-only for both None and Some(ns) paths — directly
  comparable to index.len() in doctor/verify
- new count_all(): explicit include-deprecated totals for reporting

Regression test: isolated temp-dir store (':memory:' stores resolve the
vector index to a CWD file and cross-contaminate parallel test runs —
that shared uteke_index.usearch pollution is also why this bug hid).
Asserts soft-forgotten id absent from list/list-ns/load_all and doctor
reports no MISMATCH.
…ace keyword misses (#1051) (#1062)

uteke_search / CLI search go through store.search_content() (LIKE
substring), which mapped namespace=None to the 'default' namespace
instead of searching across all namespaces like list(None) does (#526).
Memories stored in any other namespace were invisible to keyword
search unless the caller explicitly passed that namespace.

- namespace=None now searches across all namespaces (deprecated rows
  still excluded)
- hyphenated identifiers keep working as literal substrings on the
  LIKE path (pinned by test), and the FTS5 phrase path already handles
  them (unicode61 adjacency) — probed both in-test

Regression test: cross-namespace hit via None, scoped hit via Some(ns),
full and partial hyphenated identifier matching.
… parity (#1037) (#1063)

recall_hybrid() applied salience/recency boosts only on the cache-miss
path; the cache-hit early return handed back raw cached scores. Same
query + strategy returned different scores cold vs warm (delta ~0.13).

Three-part fix:
- Cache-hit read path now re-applies boosts (boost → sort → truncate →
  min_score), identical post-processing to the miss path via shared
  apply_salience_recency_boosts() helper. Cache intentionally stores
  RAW scores — boosts are time-dependent and re-applied per read.
- Boost window: cache stores limit*4+16 candidates computed with
  min_score=0, so boosts can lift a memory from outside the raw top-N
  into the final top-N on warm reads too (cora finding — cached
  top-limit-only set would permanently exclude boostable candidates).
- min_score thresholding now happens AFTER boosts on BOTH paths
  (previously miss-path Vector/Fts5 filtered raw scores while hit-path
  filtered boosted scores).

Tests: cold/warm score parity (fails with max delta 0.125 pre-fix);
boost-reorder-across-limit; noop-config warm hits respect limit.
…umented (#1036) (#1064)

ExportEntry had no namespace field — every exported row lost its
namespace, so export→import collapsed multi-namespace stores into one.
Also root-caused the reporter's 36-row delta: export() reads load_all()
which filters deprecated=0 (soft-deleted rows excluded by design, but
undocumented).

- ExportEntry gains 'namespace' (serde default 'default' keeps old
  export files importable)
- export() serializes m.namespace on every row
- import(): caller-supplied namespace = explicit override for all rows;
  without it, per-row namespace wins → round-trips reconstruct
  namespaces
- docgen/api-reference regenerated (no route change, freshness check)

Test: 3-namespace × 2-row export shape (every row carries its ns),
parse-side attribution preservation, legacy-row default fallback.
…confirm to apply (#1050) (#1065)

A no-args uteke_dream call ran the maintenance pipeline with
dry_run=false against ALL namespaces while the tool description said
'Safe to run periodically'. Real incident: single exploratory call
mutated 4,067 rows store-wide.

- dry_run defaults to TRUE — a no-args call can only preview
- Applying requires explicit dry_run=false
- Unscoped applying runs refused unless confirm_large=true (two-flag
  decision for whole-store maintenance)
- Large-batch guard: applying runs projecting >100 changes refuse
  without confirm_large=true; preview computed first, so the refusal
  reports the projected count
- Output announces scope ([SCOPE: ALL NAMESPACES]) on both preview and
  apply paths
- Description rewritten: states destructive nature + dry-run-first
  guidance instead of 'Safe to run periodically'
- docs/mcp.md tool table updated
…doc_search scores, room ids (#1052) (#1066)

- uteke_stats: multi-line output — tiers (hot/warm/cold), pinned +
  deprecated split, recall cache hits/misses, and per-namespace
  breakdown when unscoped (was: single 'Total | Tags | DB' line)
- uteke_doc_search: per-result score, matched chunk heading + 120-char
  snippet (ranking was invisible; results were bare slug — title)
- uteke_room_memories: lines now include 8-char short id so the next
  tool call (pin/forget/graph edges) can act on the row
- uteke_room_recall description states the existing-room_id
  precondition instead of failing only at call time
- core: Store::count_pinned() + Uteke::{count_pinned, count_deprecated,
  namespace_counts} accessors (store field is private; MCP uses the
  public API surface)
…l ID-taking tools (#1048, #1049) (#1067)

MCP printed 8-char short ids in recall/list but every ID-consuming tool
required the full UUID (exact SQL match) — the basic loop recall → act
silently no-opped. resolve_id_prefix() existed in core (#794); MCP
never wired it.

- resolve_id() helper: full UUID passes through; any shorter prefix
  resolves via resolve_id_prefix(); ambiguous prefixes error loudly;
  unknown prefixes error instead of silent not-found
- Wired into uteke_forget, uteke_pin, uteke_unpin,
  uteke_graph_add_edge, uteke_graph_remove_edge
- NEW uteke_get {id} — full record by id/prefix, no truncation (fills
  the read-by-id gap; recall/search return ranked excerpts)
- NEW uteke_update {id, content?, tags?, metadata?, importance?,
  pinned?, memory_type?} — partial-update semantics matching HTTP PUT
  /memory (#659); content change re-embeds
- Tool schemas + docs/mcp.md: id params documented as UUID-or-prefix

Tests (5, scratch temp-dir stores): prefix + full + unknown resolution;
uteke_get full record via short id; pin via short id; forget short id
happy path + bogus prefix errors loudly; update partial fields leave
content untouched.
ajianaz and others added 26 commits September 9, 2026 11:28
…t raw artifacts, fix baseline labels (#1209)

* docs(benchmarks): restructure benchmarks/ into internal/longmemeval/locomo with committed raw artifacts

- benchmarks/README.md: index (what each benchmark is, status, policies, reproduce)
- benchmarks/internal/: uteke bench results — v0.17.0 re-verification (2026-09-09)
  alongside the historical v0.12-era table; removes the stale ~800-ops/s stub
- benchmarks/longmemeval/: harness scripts moved to scripts/; canonical raw
  artifacts now committed under results/ (default-500q, fts5-500q,
  contradiction-f3 metrics) so every headline number is auditable from GitHub
- RESULTS.md: explicit 470+30 decomposition (non-abstention / abstention / full-500),
  full-500 aggregates (any@5 0.982 / strict@5 0.880), corrected baseline label
  (0.854/0.885 is the vector-only run, not hybrid), v0.17.0 re-validation placeholder
- benchmarks/locomo/: planned external benchmark #2 (adapter + pilot scope)
- gitignore: allow-list committed raw results; drop tracked run logs and the
  superseded 470-question metrics snapshot
- docs/benchmarks.md: provenance line updated to committed raw artifacts

* fix(bench): vol_dir isolation in modal_fanout so re-runs with a new binary cannot resume-skip old-binary shards

- new --tag flag: shards land under {strategy}-{tag}/ on the volume
  (e.g. default-v017), leaving the original default/ artifacts untouched
- without tag, behavior is unchanged (backward compatible)
- motivated by the v0.17.0 re-validation run: the volume still holds
  completed v0.16.0 shards under default/, which the resume logic would
  have silently reused

* fix(bench): point fanout image mounts at scripts/ after the restructure

Smoke run with UTEKE_GIT_REF=v0.17.0 caught it: add_local_file still
referenced run_eval/temporal/mmr at the longmemeval/ level (FileNotFoundError
at image build). SCRIPTS = REPO_DIR/'scripts' now feeds all harness mounts in
modal_fanout.py and modal_fast50.py; dataset path (REPO_DIR/data) unchanged.

* feat(bench): LoCoMo adapter — convert to LongMemEval harness schema + fix binary resolution after restructure

- benchmarks/locomo/scripts/convert_locomo.py: maps locomo10.json to the
  harness schema (sessions as {role, content} turns, evidence D1:3 ->
  answer_session_ids). Category 5 (adversarial) and zero-evidence QA are
  excluded from the retrieval dataset and reported separately: 1536
  evaluable QA across 10 conversations. Evaluator is reused unchanged —
  one metric family across both external benchmarks.
- run_eval.py / contradiction_segment.py: repo-root resolution was
  three parents up from longmemeval/; after the scripts/ move it needs
  four. The Modal fanout smoke run caught the fanout variant of this;
  this fixes the local-run variant before it bites.
- locomo/.gitignore: converted harness files (~220MB) and exploratory
  run outputs stay out of git; canonical results get committed explicitly

* feat(bench): LMEVAL_DATA env override for fanout dataset

Enables running other harness-schema datasets through the same fan-out
(first consumer: LoCoMo 1536-QA converted set). Defaults unchanged.

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…1210) (#1211)

check_duplicate() filtered candidates by namespace only, so a stale
vector-index entry for a soft-deleted (deprecated) row could satisfy
dedup: remember() returned the deprecated id, wrote nothing, and
remember_in_room() linked rooms to the invisible ghost row.

Now every candidate above the threshold is verified against SQLite:
missing or deprecated rows are skipped (fall through to insert), and a
store read error also skips the candidate. The regression test poisons
the index with a deprecated row's vector and asserts the rewrite
creates a fresh live row.

Addresses #1210.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
… guide (#1214)

* sim: squash #1208

* docs(readme): benchmark-first restructure + self-service reproduction guide

README now follows a proof-first structure: value-prop hero strip with the
98.5% any@5 headline right under the title, quick start, then the benchmark
section (head-to-head table vs agentmemory/BM25, per-category recall strip,
strict-vs-any explanation) with a prominent 'run it yourself' hook pointing
at the new reproduction guide. FAQ anchors preserved; comparison table and
all feature/rooms/deployment content retained (deployment + rooms + MCP
moved into collapsed sections to keep the fold tight).

New benchmarks/longmemeval/REPRODUCING.md: three self-service paths (verify
committed raws in ~1 min with stdlib Python; 50-question local run; full
500Q Modal fan-out with UTEKE_GIT_REF pinning), plus a metrics-explainer
section. Adds the v0.17.0 raw 500Q output as a committed artifact
(results/default-500q-v017.jsonl, allow-listed in .gitignore) so both
published releases can be independently scored.

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…amily on a second external benchmark (#1212)

- RESULTS.md: headline (strict@5 0.716, any@5 0.840, cov@5 0.776), strategy
  comparison incl. vector-only and fts5-only ablations, per-category
  breakdown, scope notes (446 adversarial QA excluded from retrieval
  metrics; cross-benchmark comparison caution), pilot consistency check
- results/: committed raw artifacts for all three runs (default, vector,
  fts5 — 1.536 questions each) so every number is auditable from GitHub
- binary pinned to release SHA a2ec81a (UTEKE_GIT_REF), Modal x86, 10 shards

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
… unchanged (#1213)

Full 500-question re-run on the v0.17.0 binary (SHA a2ec81a, Modal x86,
10 shards, volume-isolated under default-v017/):

- strict@5 0.8800 / strict@10 0.9540: identical to v0.16.0 (+0.00pp)
- coverage@5 0.9443 (+0.09pp), any@5 0.9840 (+0.20pp)
- per-question: 238 identical, 254 reorder-only, 8 different top-10 sets;
  all 3 strict@5 changes stayed within partial credit

Confirms the retrieval scoring path is unchanged in 0.17.0; headline numbers
are now re-validated on v0.17.0. Raw artifact committed as
results/default-500q-v017.jsonl. Headline TL;DR in RESULTS.md updated to
cite the v0.17.0 run.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…ncy, 98.4% full-500 basis (#1215)

- Hero strip + badges + benchmark section + FAQ: 23ms -> ~45ms (conservative
  claim; re-tests landing at 23ms read as a pleasant surprise, not a miss)
- All recall@5 figures normalized to the full 500-question basis (98.4%,
  any@5) — the number anyone can recompute from the committed raw artifact;
  the 470-question basis stays labeled in benchmarks/ RESULTS.md
- REPRODUCING.md: published-numbers line + raw-artifact table + metric
  explainer aligned to 98.4%
- Compliance with locked content constraints verified: no LoCoMo mentions,
  no cloud domain, no pricing (Editions table already compliant: 'managed',
  price 'Coming soon')

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…dashes, claim accuracy) (#1216)

* docs(readme): fix badge anchor after 98.4 retitle (985 -> 984)

* docs(readme): humanizer/copywriting pass — zero em dashes, ban-phrase and claim-accuracy fixes

Mechanical gate (humanize-before-publish workflow):
- em dashes 20 -> 0 (prose dashes converted to colons/parens/periods; the 3
  remaining en dashes are numeric ranges: 96.7-98.2%, A-G, 100-10K)
- banned phrase 'not just' x2 removed (reworded, meaning intact)

Claim accuracy (copywriting verification pass):
- embedding model download ~188MB -> ~200MB (matches the actual on-disk
  model directory, 208M; the old figure understated the download)
- footnote 3 MRR disclaimer reworded; table placeholder cell '—' -> 'n/a'

No content, structure, or numbers changed beyond the fixes above.

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…#1217)

- docs/assets/longmemeval-recall-v017.png: 98.4% head-to-head chart
  (uteke vs MemPalace vs agentmemory), rendered from the committed raw
  artifact, footnote discloses axis-from-0 and competitor source
- placed above the by-question-category strip in the benchmark section

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…1218)

README.id.md restructured to match the benchmark-first EN README (v0.17.0 revalidation): 98.4% full-500 headline, ~45ms conservative latency claim, competitor labels anonymized to Tool A-G, new Roadmap & Editions section, Star History via hosted image. All numbers recomputed from committed raw artifacts before editing.

Also fixes stale or mislabeled claims across docs: 188MB model size to ~200MB, v0.16.0 pinned install URLs to v0.17.0, mixed strict@5 bases labeled (88.0% full-500 vs 88.3% on 470 non-abstention), 43% multi-session claim corrected to 65% per the committed dataset, perf table in docs/benchmarks.md updated to the v0.17.0 re-verification run with the 2026-08 table kept as historical, head-to-head section added to back the README footnote anchor, zero em dashes in both READMEs per the humanizer/copywriting gate.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…ith the actual server surface (#1219)

mcp.md: the tool table claimed 35 tools with 37 rows, but the server registers 46 tools. Added the 9 missing rows (namespace rename/delete, room rename/update/memory-move/list from #1181/#1202, contradictions ledger + undo and provenance from #1172), fixed the count to 46 (verified against the dispatch arms and schemas in crates/uteke-mcp/src/lib.rs, distinct names only; uteke_room_document is a dispatch alias of uteke_room_summary_document, not a separate tool), and updated uteke_recall/uteke_list/uteke_update rows for the explain flag (#1160), include_meta pagination envelope (#1188), and namespace move field (#1181).

multi-agent.md: new Managing Namespaces section covering uteke namespace move/rename/delete with the refuse/merge/deprecate strategies, the HTTP and MCP equivalents, and the with_counts active/deprecated breakdown. cli-reference.md: namespace move/rename/delete added to the quick command table.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…ngle-hop, 2=temporal, 3=multi-hop) (#1222)

convert_locomo.py carried a rotated CATEGORY_NAMES map: categories 1/2/3 were
labeled multi-hop/single-hop/temporal instead of the official upstream mapping
(single-hop/temporal/multi-hop, verified against snap-research/locomo10.json
counts 282/321/96/841 and the AMB harness docstring). 699/1536 QA carried a
wrong question_type label. Labels never enter any metric, so all overall
numbers are unchanged; the three committed jsonl artifacts are relabeled in
place (question_type only, retrieval results untouched, verified zero drift
against a fresh conversion) and the RESULTS.md per-category tables + narrative
are recomputed. Key narrative correction: the weakest category is multi-hop
(any@5 0.630, n=92), not temporal; the old 'temporal is weakest, consistent
with LongMemEval-S' reading was an artifact of the rotated labels.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Release tarballs ship libonnxruntime.so* alongside uteke binaries, but install() only moved the 3 binaries and discarded the .so/.dylib. On distros with no system lib (Arch/CachyOS) this breaks first-run remember with ONNX Runtime library not found. Mirror build_from_source() behavior. Update install.ps1 and manual install docs. Fixes #1220.

Co-authored-by: KazamiHazaki <kazamihakazaki@example.com>
The CLA gate relied on a manually POSTed commit status (context
'CLA Check'). Fork PRs always run workflows with a read-only
GITHUB_TOKEN regardless of declared permissions, so the status step
always failed with 403 on forks — every external fork PR was
permanently red even with a signed CLA (verified: PR #1221, run
34481899319, POST /statuses -> 403 while signed='true'). The bot
comment step also 403s on forks (skipped in the same run).

- Drop the commit-status step and the statuses:write permission;
  the gate now rides on this job's own Actions check run (created
  by GitHub itself, immune to the fork token restriction).
- Keep the signed->green / unsigned->red semantics via the existing
  'Fail if not signed' step, now with a ::error annotation pointing
  to the signing portal and the auto-refresh behavior.
- Keep the PR comment as a same-repo-only courtesy step
  (continue-on-error) — it can never affect the gate outcome.

Note: the ruleset's required context must switch from 'CLA Check'
to 'cla-check' (case-sensitive) in the same window as this lands;
owner action via API.

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Bumps [usearch](https://github.com/unum-cloud/USearch) from 2.26.1 to 2.26.2.
- [Release notes](https://github.com/unum-cloud/USearch/releases)
- [Commits](unum-cloud/USearch@v2.26.1...v2.26.2)

---
updated-dependencies:
- dependency-name: usearch
  dependency-version: 2.26.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [tokenizers](https://github.com/huggingface/tokenizers) from 0.23.1 to 0.23.2.
- [Release notes](https://github.com/huggingface/tokenizers/releases)
- [Changelog](https://github.com/huggingface/tokenizers/blob/main/RELEASE.md)
- [Commits](huggingface/tokenizers@v0.23.1...v0.23.2)

---
updated-dependencies:
- dependency-name: tokenizers
  dependency-version: 0.23.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps dirs from 6.0.0 to 7.0.0.

---
updated-dependencies:
- dependency-name: dirs
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Bumps [toml](https://github.com/toml-rs/toml) from 1.1.4+spec-1.1.0 to 1.1.5+spec-1.1.0.
- [Commits](toml-rs/toml@toml-v1.1.4...toml-v1.1.5)

---
updated-dependencies:
- dependency-name: toml
  dependency-version: 1.1.5+spec-1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
)

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
* Revert "benchmarks: preserve AMB (agent-memory-benchmark) run kit (#1226) (#1230)"

This reverts commit 3b93b34.

* chore: retrigger checks after branch rename

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…1232) (#1238)

Prepend a [Session date/time: ...] header to stored content so lexical
and vector recall can answer temporal questions without metadata joins.

- remember --timestamp / import --format text --timestamp (opt-in)
- RFC 3339, YYYY-MM-DD HH:MM:SS, and YYYY-MM-DD accepted; invalid values
  fail loudly before any write
- never double-prefixes already-anchored content
- server-path remember forwards the anchor
- rejected explicitly for --batch-dir (per-file anchors are ambiguous)

Measured on the internal conversation eval: temporal QA 15% -> 100%.

Closes #1232

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…1239)

* feat(ingest): --timestamp date anchors for remember and text import (#1232)

Prepend a [Session date/time: ...] header to stored content so lexical
and vector recall can answer temporal questions without metadata joins.

- remember --timestamp / import --format text --timestamp (opt-in)
- RFC 3339, YYYY-MM-DD HH:MM:SS, and YYYY-MM-DD accepted; invalid values
  fail loudly before any write
- never double-prefixes already-anchored content
- server-path remember forwards the anchor
- rejected explicitly for --batch-dir (per-file anchors are ambiguous)

Measured on the internal conversation eval: temporal QA 15% -> 100%.

Closes #1232

* test(recall): payload conformance — full payload contract, no stubs (#1233)

Two HTTP-route tests pin the recall contract: responses carry the full
SearchResult payload (memory id, full content, timestamps, score) and
empty results are structured objects, never hit-count summary strings.

Guards against the silent empty-context degradation class observed in
internal integration work (#1233).

Closes #1233

* test: harden empty-result assertion per cora review — explicit results array required (#1233)

* test: assert empty recall is a bare empty JSON array (contract per #902 shape)

* test: make payload conformance CI-safe — no-embedder store + fts5 strategy (#1233)

Uteke::open_with_backend(":memory:", None) + /remember route (storage-only
without embedder) + fts5 recall (keyword, no query embedding) so the tests
run in CI builds without the ONNX runtime lib.

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
…8.0-to-main

# Conflicts:
#	.github/workflows/cla-check.yml
#	.gitignore
#	AGENT.md
#	CHANGELOG.md
#	Cargo.lock
#	Cargo.toml
#	README.id.md
#	README.md
#	benchmarks/RESULTS.md
#	benchmarks/longmemeval/RESULTS.md
#	benchmarks/longmemeval/run_eval.py
#	crates/uteke-cli/Cargo.toml
#	crates/uteke-cli/src/commands/maintenance.rs
#	crates/uteke-cli/src/commands/server.rs
#	crates/uteke-core/src/memory/schema.rs
#	crates/uteke-core/src/memory/store.rs
#	crates/uteke-core/src/structural_export.rs
#	crates/uteke-mcp/Cargo.toml
#	crates/uteke-mcp/src/lib.rs
#	crates/uteke-server/Cargo.toml
#	crates/uteke-server/src/handlers.rs
#	docs/api-reference.md
#	docs/benchmarks.md
#	docs/install.md
#	docs/mcp.md
#	docs/roadmap.md
@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

🔍 Cora AI Code Review

⚠️ Review could not complete. LLM API error after retries: Unknown error — check workflow logs


Review powered by cora-code · BYOK · MIT

@ajianaz
ajianaz merged commit aa0fae4 into main Sep 14, 2026
18 checks passed
@ajianaz
ajianaz deleted the chore/release-v0.18.0-to-main branch September 14, 2026 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants