Repository navigation
chore: release v0.20.0 to main - #1380
Conversation
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.
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.
Embed all chunks first (no index lock, no transaction), write the document and its chunks in one SQLite transaction (upsert_document_with_chunks / update_document_with_chunks), then mirror into the vector index through index_sync. A failure while embedding now leaves the previous document, chunks and index untouched; recall is no longer blocked for the whole embedding time. doc_delete uses the shared removal policy. Refs #1341 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…content FTS5 (#1351) documents_fts is an external-content FTS5 table, so the update and delete triggers must issue the 'delete' command with the OLD values. The plain UPDATE/DELETE forms either failed with 'database disk image is malformed' (title changes via doc update/upsert) or silently left stale tokens in the index. Replace the triggers (new databases and the v12 migration) and heal existing databases on open: swap the outdated triggers and rebuild the index. Fixes #1349 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Add uteke_core::config_layers: per-key deep merge of the global and project uteke.toml, one untrusted-project-key policy (stripped before merging) and per-layer validation, generic over the schema type. The CLI (Config::load) and the server (load_uteke_toml) both use it, replacing ~290 lines of hand-listed merge_from_file, restore_sensitive_from and the server's overlay_project_config. A new config key now needs only a struct field. Server behaviour: project [recall]/[lifecycle]/... now merge per key instead of replacing the whole section; parse errors are logged instead of silent; UTEKE_TRUST_PROJECT_CONFIG=1 works for the server too. Refs #1342 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…#1356) Forbid project tags derived from generic folder names, require lowercase-hyphen names and parent/component tagging, document the namespace/room/tag axes, the immutable room namespace (#1352), replace-semantics of `update --tags`, and backup + confirmation for destructive cleanup. Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…ted (#1357) (#1358) list, recall and room_memories print only the first 8 hex chars of the id. Since ids are UUIDv7 those chars are a timestamp, so memories written within ~65s share a prefix and the printed id cannot be fed back to get/forget/pin/move. Add an opt-in `full_ids` boolean to uteke_list, uteke_recall and uteke_room_memories that prints the full UUID. Default output is unchanged. Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…#1328) (#1359) - /import: the content cap now equals MAX_PAYLOAD_SIZE and the error reports it, instead of a hardcoded 5MB that disagreed with read_body's 10MB. - /mcp: match Content-Length case-insensitively, read one byte past the cap so an unsized oversized body is a 413 rather than a truncated JSON parse error, and attach CORS headers like every other endpoint. - resolve_id: accept only hex/dash input (no LIKE wildcards reach the prefix scan), skip the scan only for a parseable UUID, and let a 36-char non-UUID fall to the prefix path so it errors like the 8-char path. Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* security(server): validate Host header to block DNS rebinding (#1326) On a loopback bind, accept only localhost / *.localhost / loopback-IP Host values (plus [server] allowed_hosts / --allowed-host); anything else is 403. A non-loopback bind (Docker service names) keeps accepting every Host unless allowed_hosts is set. A missing Host header is allowed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Merge origin/develop into security/host-header-validation Keep both test modules appended to handlers.rs; give the new mcp test ReqCtx the host_guard field. --------- Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…#1361) #1350 made document upsert/update embed-then-commit, but embed_document_chunks still held the embedder mutex across the whole chunk loop. recall/remember embed their query through the same mutex, so a long document made every other embed wait for all of its chunks. Take the lock per chunk instead. Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
) The vector index file and its .keys sidecar are published by two separate renames, so a crash between them left a mismatched pair that load only caught when the whole key map was empty. A stale map silently hid live rows (vecq) or let next_key restart too low (usearch). save() now writes a header line with the SHA-256 of the index bytes and the row count; load() rejects a sidecar that names a different index, a missing/empty sidecar for a non-empty index, and (for header-less legacy sidecars) key counts or ranges the index cannot hold. The existing corrupt-index path then discards both files and rebuilds from SQLite. Older binaries ignore the header line. Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…1322) (#1363) Every remember/forget rewrote the whole index and .keys sidecar, making bulk ingest O(N^2) and holding the index write lock for each rewrite. Per-operation paths (index_sync::upsert, remove_ids) now call persist_deferred, which saves once 64 mutations or 2s have accumulated (VectorIndex::save_if_due). Pending changes are still flushed by shutdown(), a new Uteke::flush_index, the index's Drop, and a 1s flusher thread in uteke-serve (SIGTERM does not run the shutdown path). Batch paths (import, re-embed, document chunks) keep saving explicitly. Deterministic validation errors are no longer retried, so the backoff sleep cannot run under the index lock for input that cannot succeed. Trade-off: after a hard kill the on-disk index can trail SQLite by up to the batch window; SQLite remains the source of truth and `uteke verify`/`repair` resync it. Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
vecq rows are append-only: updates and deletes leave tombstoned rows that grow the index and .keys files and make every search over-fetch k + dead from an O(N) scan. Nothing ever compacted them. VectorIndex::needs_compaction() flags a vecq index of >=128 rows with more than a quarter of them dead. Uteke::compact_index_if_needed(dry_run) rebuilds it from SQLite (original embeddings, chunks included) while holding the index write lock across the read and the build so no concurrent write is lost. It runs in the `dream` compact phase and in uteke-serve's 1s flusher thread (read-lock check only; the rebuild happens once the threshold is crossed). Co-authored-by: Aji Anaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…P add_edge and dream (#1370) - new Uteke::graph_view: unified, bounded graph (memory_edges + explicit graph edges), keyed by memory id, live memories only, node_id neighbourhood, limit + truncated; GET /graph and MCP uteke_graph use it - edges also carry source/target (the names Corin reads) - GraphStore::add_edge_for_memories / remove_edge_between: MCP uteke_graph_add_edge/remove_edge and dream contradiction edges no longer violate graph_edges FK - fix registry descriptions: /edges is not an alias of /graph; /graph documents node_id/limit Fixes #1365 Fixes #1366 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…ease configurable (#1374) - make_fast_eval.py looked for fast_eval_ids.json one directory too high - modal_fanout.py: LMEVAL_MODEL_DIR and UTEKE_RELEASE env overrides (the defaults pointed at another machine and at v0.15.0) - REPRODUCING.md: dataset is ~277 MB, document the overrides Refs #1369 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…nk or edge BFS (#1373) soft_delete_only is the default, so forget/deprecate leaves memory_edges rows behind. compute_graph_signals (strategy graph, recall_explain) and edge_bfs (related_via_edges, recall --related) now only consider live endpoints; the audit view (edges_for) and the supersession ledger still show the history. Fixes #1367 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…ex lock for edge wiring (#1371) * perf(core): remember() no longer scans the namespace or holds the index lock for edge wiring - Store::live_ids_in_namespace: one query over the candidate ids (json_each) instead of loading every id of the namespace per write; used by check_duplicate (dedup semantics unchanged) and auto_link_cosine - auto_link_cosine: fetch 4x candidates before the namespace filter so a small namespace in a big index keeps its own links; deprecated memories and chunk: keys are never link targets - remember: SQLite writes (insert, hash, timeline, wire_edges) run without the index write lock; the lock is taken only to mirror the vector; a poisoned lock becomes an honest warning instead of an error after the row was committed Refs #1368 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix: repair merge of edges.rs test modules Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…, empty index rebuild, recompute_importance (#1375) - upsert_document / delete_document keep the parent's has_children flag in step (old and new parent on re-parenting) - move_document: a legacy row with an empty path no longer matches every document (old_prefix '/%') - rename_tag onto a tag the memory already has no longer duplicates it in the JSON column - VectorIndex::build(&[]) keeps the current dimensionality instead of resetting to 768 - recompute_importance reads only the columns it needs and runs in one transaction Refs #1332 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…rade order, checksum, import bail-out, onboard TOML, config migration (#1376) - recall: flags the server has no field for (strategy, salience, recency, explain, related, depth, context, content-format, where) fall back to the local store instead of being dropped; type/enrich/pack/budget/exclude-ids are forwarded - list --entity/--category: --limit/--offset apply to the matches (paged scan), not to one pre-paginated page - doc list --tree prints descendants; doc export honours --output and the namespace and exports every document (new Uteke::doc_list_page) - upgrade stages and verifies every binary before installing any - verify-checksums selects the line by exact file name - batch import bail-out counts failures of the extraction loop only - onboard escapes the namespace (and other values) in the generated TOML - migrate_content only treats flat keys before the first [section] as legacy Refs #1332 Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(core): re-parenting doc upsert carries the whole subtree upsert_document rewrote only the document's own parent/path/depth when an existing slug got a new parent, leaving descendants under the old path (a later delete of the old parent cascaded onto the moved subtree). It now rewrites descendants with the same helper as move_document, refuses a move into the document's own subtree and a result deeper than MAX_DEPTH before writing anything. Fixes #1377 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(core): escape LIKE wildcards in document path queries Every subtree query matched paths with LIKE '<path>/%' unescaped, so '_' and '%' in a stored path acted as wildcards: moving /my_doc/ also rewrote /myxdoc/... and deleting it removed the look-alike subtree. All path LIKE queries (move, delete, count/list descendants, re-parent rewrite, depth guard) now escape the prefix and use ESCAPE '\'. Found by CodeCora on #1378. Refs #1377 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(core): cycle guard compares raw paths, not the escaped LIKE prefix The re-parenting cycle check used the escaped LIKE prefix as a string prefix, which never matched paths containing '_' or '%'. Use the raw directory form. Found by CodeCora on #1378. Refs #1377 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Prepared on chore/prepare-0.20.0; the release PR is opened from a chore/release-* branch after the mutation-test gate. Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…t; main has no unique content) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
🔍 Cora AI Code ReviewReview powered by cora-code · BYOK · MIT |
Correction to the merge-gate note above (diagnosis, not the decision)I wrote that Cora "returns an empty result for this 58-file diff" and implied size. That was wrong. Root cause (found while fixing codecoradev/homebrew-tap#5): |
The action defaults base-branch to origin/develop. For PRs into main (release PRs, whose head carries develop's tree) git diff origin/develop...HEAD is empty, so Cora reported 'empty result' and never reviewed the release diff (#1380: 0 files vs 58 against main). Same root cause as homebrew-tap#5/#6. Co-authored-by: ajianaz <ajianaz@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
What
Release v0.20.0 to
main: the content ofdevelop(29 PRs since v0.19.1, release-prep commit #1379) on top ofmain.mainis merged in with the develop side kept (git merge -s ours origin/main), the procedure from AGENTS.md for develop→main conflicts; the 32 textual conflicts come from squash-merged history, the merge-base is still v0.13.2.Why
maincarries no content thatdeveloplacks:git diff <develop v0.19.1 release commit f416076> origin/mainis empty and there are no files only onmain, so keeping the develop side loses nothing. Highlights (full list in CHANGELOG): one write policy for the vector index, atomic documents, shared config resolution,GET /graphservingmemory_edges, the security sweep and the validated review findings.Testing
develop(git diff origin/develop HEADempty), which passed CI on chore(release): v0.20.0 - version bump, changelog, docs sync #1379 (15/15, real Cora verdict, 0 code-scanning alerts).cargo test --workspacegreen; vecq-only core build green.cargo mutants -p uteke-corepre-release gate (tool not installed; proceeding on the owner's instruction). uteke-cloud#117 (CORE notice) has no reply yet.v0.20.0is created only after this PR is merged and main's HEAD is verified.🤖 Generated with Claude Code