Skip to content

docs: refresh documentation for 0.16.0 — fusion default, updated benchmarks, bundled skill bump - #1158

Merged
ajianaz merged 1 commit into
mainfrom
chore/release-docs-016-refresh
Aug 29, 2026
Merged

ajianaz merged 1 commit into
mainfrom
chore/release-docs-016-refresh

Conversation

@ajianaz

@ajianaz ajianaz commented Aug 29, 2026 •

Copy link
Copy Markdown
Collaborator

What

Carry of #1156 (merged to develop) to main via chore/release-* branch per the Source Branch policy (PRs into main must come from develop or chore/release-*; precedent: #1042 chore/carry-fix-1041-to-main).

Post-release documentation refresh for v0.16.0 (released 2026-08-29):

  • README.md + README.id.md: version claim v0.14.3 → v0.16.0; test count 530+ → 200+ (grep-verified); comparison Search column leads with Fusion (default); features add Consolidation + Author Types (0.16.0); benchmark teaser → full 500Q validation (R@5 0.946 / R@10 0.977); fixed broken docs/BENCHMARKS.md link
  • Bundled skill asset (ships in binary via include_str!): 0.10.0 → 0.16.0, strategy enum includes fusion default, lifecycle/onboard rows added
  • install.md: pinned examples v0.14.3 → v0.16.0 (all 4 asset URLs verified HTTP 200)
  • mcp.md / cli-reference.md / configuration.md / getting-started.md / index.md / architecture.md: fusion documented as default since 0.16.0
  • roadmap.md: v0.15.0 + v0.16.0 recorded as released
  • CHANGELOG.md + benchmarks.md: corrected the "raw per-question results committed" claim — that path is gitignored and was never committed; aggregates live in benchmarks/longmemeval/RESULTS.md, raw results stay on the Modal benchmark volume. No numbers changed.

Why

v0.16.0 made fusion the default recall strategy and shipped a full 500Q validation, but the public front page, the bundled skill asset (distributed inside every binary), install pins, and several doc pages still described pre-0.15 behavior. A user installing v0.16.0 today reads instructions for v0.14.3 — and the changelog pointed to raw results that were never actually committed, undermining the independent-verification promise. Meanwhile main cannot simply fast-forward to develop (it carries unreleased work), so the doc fix must land on main through the sanctioned chore/release-* carry path to be visible to users who install from main.

Testing

…hmarks, bundled skill bump (#1156)

* docs: refresh README (EN+ID) for 0.16.0 — fusion default, 500Q benchmarks, new features

* docs: align strategy/version references with 0.16.0 defaults (fusion, install pins)

* docs(roadmap): record v0.15.0 and v0.16.0 as released

* docs: correct 500Q results-availability claim (aggregates in repo, raw on volume)

* chore(skill): bump bundled uteke-memory-skill to 0.16.0 (fusion default, lifecycle, onboard)

---------

Co-authored-by: ajianaz <ajianaz@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

🔍 Cora AI Code Review

❌ Blocked — critical issues found.

🔴 Error (1)


Review powered by cora-code · BYOK · MIT

@ajianaz
ajianaz merged commit 06092c4 into main Aug 29, 2026
38 of 40 checks passed
@ajianaz
ajianaz deleted the chore/release-docs-016-refresh branch August 29, 2026 16:35
ajianaz added a commit that referenced this pull request Aug 29, 2026
* feat: add full memory detail fields to UnifiedSearchResult

UnifiedSearchResult now includes memory_type, namespace, source,
source_type, importance, pinned, access_count, last_accessed,
created_at, updated_at.

- Memory results: all fields populated from Memory struct
- Document results: all memory-specific fields are None (skip_serializing_if)
- Tests verify both memory fields present and document fields omitted
- CLI print_unified_human shows new fields (type, source, importance, pinned, access count)

Fixes the gap where recall with search_type=all/memory/doc returned
less metadata than plain recall.

* fix: remove O(n) reverse scan in get_related() + fix recall_room() missing columns

A) get_related() in graph.rs was doing load_all(None) to find reverse
   metadata relationships — O(n) full table scan. The edge_bfs() method
   already handles bidirectional lookups via UNION on source_id/target_id
   using indexed SQL. Removed the full-table scan. Forward-only metadata
   fallback kept for pre-v8 stores (O(degree), not O(n)).

B) recall_room() SQL in memory/rooms.rs was missing m.slug, m.source,
   m.source_type columns (only selected up to content_type=col16).
   row_to_memory() expects 20 columns (0-19). Fixed all 4 SQL variants.

Refs #689

* feat: room↔document junction table (schema v15, #689)

New room_documents table with room_id + doc_slug composite PK.
Enables explicit room-to-document linking beyond memory wikilinks.

Store methods:
- room_add_document(room_id, doc_slug)
- room_remove_document(room_id, doc_slug)
- room_list_documents(room_id) → Vec<slug>
- document_list_rooms(doc_slug) → Vec<room_id>

Server endpoints:
- PUT /room/document/add
- DELETE /room/document/remove
- POST /room/document/list
- POST /doc/room/list

Refs #689

* fix: use public Uteke wrappers instead of private store field

- Add room_add_document, room_remove_document, room_list_documents,
  document_list_rooms to Uteke public API (rooms.rs)
- Replace uteke.store.xxx with uteke.xxx in server handlers
- Add missing ns binding in /tags GET handler
- Apply cargo fmt

* feat: memory↔document cross-entity linking via [[doc-slug]] wikilinks

When [[slug]] in memory content doesn't match any memory, Uteke now
checks if it matches a document slug. If so, creates a 'references_doc'
edge (auto-backlinked as 'referenced_by').

New API:
- POST /memory/doc-refs → document slugs referenced by a memory
- POST /doc/mem-refs → memory IDs referencing a document
- Uteke::recall_memories_for_document(slug)
- Uteke::recall_documents_for_memory(memory_id)
- Store::edge_targets() / edge_sources() — generic typed edge queries

UnifiedSearchResult enriched with:
- linked_doc_slugs (for memory results)
- linked_memory_ids (for document results)

Refs #689

* fix: use read_body + typed deserialization, add missing ns binding

- Replace read_json() (nonexistent) with read_body<T>() pattern in
  /memory/doc-refs and /doc/mem-refs handlers
- Add missing let ns = parse_query_namespace(&path) in /tags handler
- Apply cargo fmt

* fix: use serde_json::json! instead of bare json! macro

The handlers used json!() which is not in scope — must be
serde_json::json!() to match the rest of handlers.rs.

* chore: remove bmad/wds skills, update docs for v0.7.3→v0.7.4 changes

- Remove _bmad/ folder (BMAD config/templates, not uteke-related)
- Remove .agents/skills/bmad-* (48 BMAD skill folders, ~14MB)
- Remove .agents/skills/wds-* (14 WDS skill folders, ~5MB)
- Remove .agents/skills/memory + sync (WDS session skills)
- Keep .agents/skills/uteke-memory (legitimate uteke skill, bump 0.6.7→0.7.3)

Docs updates (v0.7.3→HEAD):
- architecture.md: schema v13→v15, new endpoint tables (Memory Mutation,
  Room↔Document Junction, Cross-Entity References), room_documents info
- cli-reference.md: 9 new HTTP endpoints documented
- mcp.md: tool count 27→29, add graph_add_edge + graph_remove_edge
- CHANGELOG.md: populate [Unreleased] with all changes since v0.7.3
- README.md: add Partial Updates + Cross-Entity Linking features

* docs: fix Mode C handler — replace /proc with cwd-based agent resolution

The /proc/self/cmdline approach is unreliable because shell hooks run
as child subprocesses whose cmdline is the handler script, not the
gateway process. This caused agent name resolution to fail when hooks
were invoked from asyncio contexts (e.g., session expiry watcher).

Changes:
- Replace /proc/self/cmdline with cwd-based resolution from payload
- Dynamically resolve agent per invocation (not at module load time)
- Remove hardcoded UTEKE_BIN path check (assume uteke on PATH)
- Remove cron session skip (handler-level concern, not docs example)
- Pass agent name to recall function explicitly

* docs: fix docstring — remove false HERMES_PROFILE fallback claim

Cora review: docstring claimed 'Priority: cwd > HERMES_PROFILE env > fallback'
but code only checks cwd then returns 'default'. Fix docstring to match
actual behavior.

* docs: de-emphasize deprecated Mode B across Hermes integration docs

Mode B (memory-provider) was removed for Hermes on 2026-06-29 but the
docs still presented it as an active option alongside Mode A and C.

Changes:
- Architecture diagram: label Mode B as deprecated, add Mode C
- Comparison table: strikethrough Mode B column, reorder A/C first
- Mode B section: collapse 96 lines of deprecated content into 10-line
  blockquote with migration links
- Mode comparison summary: same strikethrough treatment
- Requirements: remove Mode B entry
- Non-Hermes agents section: unchanged (already has Hermes warning)

Result: 527 → 446 lines (-81 lines). Active modes (A+C) are now the
primary focus; Mode B is preserved as historical reference only.

* feat: add `uteke onboard` — interactive onboarding wizard

Guides new users from zero to productive in one command:
- Detects install (binary on PATH, existing store)
- Asks which AI agent they use (Hermes/Claude/Cursor/Pi/OpenCode)
- Picks integration mode (tool vs memory-provider)
- Toggles features on/off (Aging, Auto-maintenance, Graph rerank,
  Salience/Recency boost, Server mode)
- Writes uteke.toml config with selections
- Runs uteke init with selected agent automatically
- Prints a full feature showcase (all commands grouped by category)

Non-interactive mode: uteke onboard --yes --agent hermes --namespace default

Docs updated: getting-started.md, cli-reference.md, AGENT.md, CHANGELOG.md
New dep: which crate for PATH detection

* docs: add onboarding section to README.md and README.id.md

Add a prominent "🚀 Onboarding" section right after install, before
"Why Uteke?". Includes an agent-readable callout so when a user drops
the GitHub link to an AI agent (Hermes, Claude, etc.), the agent reads
the README and knows to run `uteke onboard.

* fix(docs): repair dangling Mode B anchor link

Rename of the Mode B heading (-> '~~Mode B~~ — memory-provider (deprecated)')
orphaned the cross-reference at line 245. Update the link target to the new
anchor #mode-b--memory-provider-deprecated.

Verified via github-slugger: 0 broken internal links.

* fix(docs): clean fragile '575577' anchor on Memory-Provider section

The heading 'Memory-Provider for Other Agents (#575/#577)' produced a
confusing concatenated anchor #memory-provider-for-other-agents-575577.
Drop the issue refs from the heading (move into the body) so the anchor
becomes the clean #memory-provider-for-other-agents. Fixes CodeCora alert.

Verified via github-slugger: 0 broken links; no other file references the
old anchor.

* fix(server): match Error::Validation instead of string in /memory/importance

The /memory/importance handler classified validation errors via
e.to_string().contains("importance must be"), coupling the 400/500
status to the exact wording of the validation message in store.rs.
If that wording changes, a 400 would silently degrade to a 500.

Match the typed Error::Validation(_) variant instead, mirroring the
pattern already used by PUT /memory.

Closes #697

* fix(core): validate room_id + doc_slug exist in room_add_document

The room_documents junction table has an FK on room_id (→ rooms) but
none on doc_slug, so POST /room/document/add accepted any doc_slug and
created dangling room→document links. A bogus room_id also surfaced as
a raw FK violation → 500.

Validate both room_id and doc_slug existence in room_add_document,
returning Error::Validation (→ 400) for unknown values. Update the
PUT /room/document/add handler to map Validation → 400 accordingly.

Closes #698

* fix: address PR #696 review feedback

🔴 Must Fix:
- AGENT.md: correct schema version v13 → v15 (room↔document junction, #689)
- onboard.rs: remove misleading get_stats() placeholder, drop count claim
- onboard.rs: write_config() now backs up existing uteke.toml to .bak and
  prompts for confirmation before overwriting (prevents credential loss)

🟡 Should Fix:
- prompt_integration_mode(): print message for custom agents (no longer silent)
- validate_agent(): now warns on unrecognized agents instead of being a no-op
- Banner boxes: replace misaligned Unicode box drawing with consistent ASCII
  print_banner() helper (fixes off-by-1 alignment, ambiguous em-dash width)
- --json flag: intentionally ignored in onboard to avoid mixing ASCII banners
  with parseable JSON output

🟢 Nice to Have:
- Install URL: main → develop (matches repo default branch convention)
- Add 7 unit tests for pure functions (get_toggle, validate_agent, print_banner,
  write_config TOML structure)

All tests pass: cargo fmt --check ✅, cargo clippy -D warnings ✅, cargo test (50 passed) ✅

Co-authored-by: ajianaz <reviewer>

* test: room operations test suite (#689 PR5)

Add comprehensive test coverage for room operations across two files:

memory/rooms.rs (33 tests, Store-level):
- CRUD: create, get, list (all + namespace filter), delete, duplicate error, cascade
- room_memories: link, idempotent link, updated_at propagation, recall with limit,
  author filter, empty room, limit=0 (all), author metadata enrichment
- get_room_memory_ids: with/without author filter
- room_stats: with memories (counts, participants, last_activity) + empty + nonexistent
- room_summary: empty room, with memories (clusters, tags, time_range), decisions,
  pinned highlights, nonexistent
- room_document: empty room, with memories (pinned section, type sections), nonexistent
- room_documents junction: add+list, idempotent add, remove, reverse lookup (document_list_rooms)

rooms.rs (14 tests, Uteke-level):
- CRUD roundtrip, namespace filter, delete
- room_stats (empty + nonexistent), room_summary (empty)
- room_document (empty + nonexistent)
- room_documents junction: add+list+remove, idempotent, reverse lookup
- remember_in_room + recall_room (4 tests, #[ignore] — requires ONNX embedder)

* fix: add missing Store import in rooms test module

* fix: resolve CI failures — unused imports + document validation in room tests

- Replace glob import  with explicit imports (Room, Document)
  to fix clippy unused-import error in both test modules
- Add  helper for creating valid documents
- Pre-create documents via  before attaching to rooms
  in 4 junction-table tests (room_add_document validates slug existence)

* style: rustfmt fixes

* fix: remove unused Room import + move junction tests to Store-level

- Remove unused super::Room import (clippy error)
- Move room↔document junction tests from rooms.rs to memory/rooms.rs
  because Uteke::doc_upsert requires ONNX embedder, but Store-level
  upsert_document works without it. Coverage is maintained.

* feat(theme): adopt @codecora/theme — Catppuccin Mocha, new base /uteke/docs/

- Replace custom theme with @codecora/theme (single-source Catppuccin tokens)
- Migrate config.ts to createConfig() helper
- Update base path: /docs/uteke/ → /uteke/docs/ (new routing scheme)
- Remove custom.css (now handled by shared theme)
- Accent: green

* feat: add enrich parameter to recall_unified for cross-entity linking (#689)

Add `enrich: bool` parameter to recall_unified() that populates
linked_doc_slugs and linked_memory_ids on UnifiedSearchResult using
the existing EDGE_REFERENCES_DOC infrastructure.

- Add enrich_memory_doc_links() and enrich_doc_memory_links() helpers
  that look up cross-entity edges and populate the appropriate fields
- Wire enrichment into recall_unified dispatcher (Memory/Document/All)
- Add enrich field to RecallRequest in HTTP API (default: false)
- Pass enrich through from server handler to recall_unified
- Update CLI and MCP callers with enrich=false for backward compat
- Add 3 integration tests (all #[ignore] -- require ONNX embedder)

Files modified:
- crates/uteke-core/src/lib.rs: signature, helpers, tests
- crates/uteke-server/src/types.rs: RecallRequest.enrich field
- crates/uteke-server/src/handlers.rs: pass enrich to recall_unified
- crates/uteke-cli/src/commands/recall.rs: backward compat
- crates/uteke-mcp/src/lib.rs: backward compat

* feat: add RoomSummary.referenced_documents and room_summary_with_docs (#689 PR3)

- Add referenced_documents field (Option<Vec<String>>) to RoomSummary struct
  with serde(default, skip_serializing_if) for backward compatibility
- Set referenced_documents: None at both RoomSummary construction sites
  in room_summary() for backward compat
- Add Store::room_summary_with_docs() that enriches a summary with
  document slugs from the room_documents junction table
- Add Uteke::room_summary_with_docs() high-level wrapper in rooms.rs
- Add 3 tests: with documents, empty room (None), nonexistent room (None)

* feat(cli): add --enrich flag for cross-entity recall links (#689 PR4)

Wire up the enrich parameter added by PR702 so users can opt into
cross-entity link resolution via uteke recall --enrich.

- Add --enrich bool flag to Recall subcommand in cli.rs
- Thread enrich from CLI dispatch → run_recall → recall_unified()
- Display linked_doc_slugs (📄 Docs) on memory results in human output
- Display linked_memory_ids count (🔗 Memories) on document results
- MCP recall left at enrich=false for now (separate enhancement)

* fix(theme): pin to codecora-theme with config.mjs (Node 24 compat)

* test: cross-entity integration tests (#689 PR6)

Store-level tests (no embedder needed, run in CI):
- cross_entity_doc_edge_round_trip: memory↔document edge CRUD
- cross_entity_doc_edge_with_backlink: backlink auto-generation
- cross_entity_doc_edge_idempotent: INSERT OR IGNORE dedup
- cross_entity_resolve_nonexistent_doc_slug_returns_none
- cross_entity_edge_targets_empty_for_no_edges

Room↔Document junction tests:
- room_doc_enrichment_full_flow: full link→list→summary→reverse
- room_doc_remove_unlink: unlink clears both directions
- room_doc_nonexistent_doc_rejected: Validation error
- room_doc_nonexistent_room_rejected: Validation error
- room_doc_shared_across_multiple_rooms: shared doc

E2E tests (#[ignore] = requires ONNX embedder):
- e2e_wikilink_creates_doc_edge_and_enriches: full wikilink→edge→enrich
- e2e_room_doc_memory_cross_entity: three-entity cross-linking

Closes #689

* style: rustfmt fixes for integration tests

* fix(theme): update codecora-theme pin (JSON import assertion fix)

* fix(docs): add ignoreDeadLinks to prevent broken link check failure

* fix(theme): pin codecora-theme to latest main (forward opts fix)

* docs: restructure — split getting-started, add install/comparison/feature pages

- Extract install.md from getting-started (curl, cargo, binary, docker)
- Extract rooms.md, time-travel.md, smart-decay.md, relationship-graph.md
- Extract benchmarks.md (from BENCHMARKS.md), add shell-hooks.md
- Add comparison.md with full feature comparison table
- Slim down index.md (ringkas 5-row comparison, link to /comparison)
- Update config.ts sidebar (halaman terpisah, bukan anchor links)
- Remove old BENCHMARKS.md (merged into benchmarks.md)

Install routes updated in CF Worker:
  /{product}/install -> 302 -> raw GitHub install.sh
  /install (legacy) -> 302 -> uteke install.sh

* feat: enable salience/recency boosts by default (opt-out) (#721)

Change SalienceRecencyConfig::default() weights from 0.0 to 0.1.

- Both salience and recency boosts are now ON by default (weight 0.1)
- CLI: absent flag = default 0.1, --salience/--recency = config 0.15,
  --no-salience/--no-recency = 0.0 (opt-out)
- Server/API: inherits the 0.1 default automatically
- Boost is additive and small enough to not dominate embedding similarity
- Adopted from Hermes holographic memory analysis

Breaking: none — the boost is additive and tiny (max +0.1).
Backward compatible: users can disable via --no-salience --no-recency.

* feat: Jaccard token similarity as post-RRF reranking signal (#719)

Add Jaccard token overlap as an orthogonal recall signal (#719):
- New jaccard.rs module with tokenize() and jaccard_similarity()
- Post-RRF additive boost when jaccard_weight > 0.0
- CLI config [recall].jaccard_weight (default 0.0 = off)
- Setter API: Uteke::set_jaccard_weight()
- Tag tokens included in content token set for richer overlap

Jaccard catches different cases than BM25 (IDF-weighted) and vector
cosine (semantic). When enabled (recommended 0.10-0.15), it boosts
results with high token overlap regardless of term rarity or
embedding distance.

Closes #719

* fix(docs): deprecate hermes-memory-provider for Hermes + add HTTP transport

- README: add deprecation notice for Hermes (removed 2026-06-29),
  clarify template is for pi/claude/cursor agents only
- README: add transport mode section (subprocess vs HTTP)
- plugin.yaml: bump to v1.1.0, add deprecation comment
- __init__.py.tmpl: add dual transport support (subprocess + HTTP),
  add UTEKE_SERVER_URL/UTEKE_TOKEN config, refactor recall/remember
  into separate _subprocess/_http methods, extraction guard for missing binary
- docs/integrations/hermes.md: link to extension template source in Mode B section

* feat: trust scoring with feedback API (#718)

Add helpful/unhelpful feedback mechanism to adjust memory importance:

Core (uteke-core):
- Uteke::feedback_helpful() → importance += 0.05
- Uteke::feedback_unhelpful() → importance -= 0.10
- Asymmetric: penalty > reward (adopted from Hermes trust scoring)
- Clamped to [0.0, 1.0], returns new importance value

CLI:
- uteke feedback <id> helpful|unhelpful
- JSON and human-readable output
- FeedbackAction enum (Helpful, Unhelpful)

Server API:
- POST /memory/feedback { id, feedback }
- Validates UUID, returns { id, feedback, delta, importance }

Closes #718

* style: fix cargo fmt formatting (#722)

* style: fix cargo fmt formatting (#723)

* fix: use raw SQL in feedback_adjust instead of non-existent store.get() (#725)

* fix: import FeedbackAction in commands/mod.rs (#725)

* fix: avoid redundant reference in feedback command (#725)

* feat: auto-contradiction scan as Dream pipeline phase (#720)

Add Phase 4 (Contradict) to the Dream maintenance pipeline.

Algorithm:
- Load top 200 most recently updated memories (O(n²) bounded)
- For each pair: check tag overlap (Jaccard ≥ 0.3) + embedding
  cosine similarity (≤ 0.6 threshold)
- High tag overlap + low content similarity = contradiction
- Creates graph_edges with relation 'contradicts' (older → newer)

Pipeline order: Lint → Backlinks → Dedup → **Contradict** → Orphans → Compact → Verify

Supports dry-run mode (reports without creating edges).
CLI: uteke dream --phases contradict

Closes #720

* fix: use GraphStore::new for add_edge in contradict phase (#726)

* style: fix cargo fmt formatting for contradict phase (#726)

* fix: prefix unused ns variable with underscore in load_recent_memories (#726)

* docs: update CHANGELOG, CLI reference, and architecture for v0.7.4 features

- CHANGELOG: 3 Added entries (trust scoring, jaccard, contradiction)
  2 Changed entries (salience default-on, dream 7 phases)
- cli-reference: updated recall flags (--salience/--recency default-on,
  --jaccard), dream phases (contradict), new feedback command + HTTP endpoint
- architecture: salience opt-out, dream pipeline 7 phases

* chore: release v0.8.0 — version bump, CHANGELOG, and docs update

- Bump workspace version 0.7.3 → 0.8.0
- Update all sub-crate internal deps to 0.8.0
- Finalize CHANGELOG: add missing PR entries, new Deprecated and Dependencies sections
- Rename [Unreleased] → [0.8.0] with 2026-07-17 date
- Update docs version refs: cli-reference, install, architecture (v0.7.4 → v0.8.0)

* refactor(api): rename POST /room/document → POST /room/summary (#735)

BREAKING: The room summary document endpoint is renamed from POST /room/document
to POST /room/summary to eliminate confusion with the room↔document junction
routes (/room/document/add, /room/document/list, /room/document/remove).

Changes:
- Server: POST /room/summary is the new primary endpoint
- Server: POST /room/document retained as deprecated alias with warn! log
- Core: room_document() → room_summary_document() across all crates
- CLI: 'uteke room document' command unchanged (help text updated)
- MCP: uteke_room_document tool updated to use new function
- Docs: cli-reference, docker, mcp, rooms updated

Closes #735

* fix: rename to POST /room/summary-document — avoid collision with /room/summary

Clippy caught unreachable pattern: POST /room/summary already used by
room_summary() (tag clustering). Renamed to POST /room/summary-document
to eliminate ambiguity with both junction routes AND the clustering endpoint.

Refs #735

* feat(api): URL prefix versioning /api/v1 and /api/v2 (#737)

Adds API versioning middleware to uteke-server.

Changes:
- ApiVersion enum (V1, V2) with path prefix parser
- /api/v1/* routes return v1 format (flat recall results for backward compat)
- /api/v2/* routes return current v2 format (wrapped UnifiedSearchResult)
- Unversioned routes (/recall, /remember, etc.) continue to work (→ latest)
- Health endpoint now includes api_versions and api_latest fields
- to_v1_flat() adapter converts UnifiedSearchResult → v1 flat format

v1 vs v2 differences:
- v1 recall: [{id, content, score, namespace, tags, ...}] (flat)
- v2 recall: [{memory: {id, content, ...}, score}] (wrapped)

Routes affected:
- /recall → /api/v1/recall (flat) or /api/v2/recall (wrapped)
- /remember, /memory, /forget, /list, /search, /stats, etc.
- /room/*, /doc/* — all sub-routes versioned
- /health — unversioned, includes version info

* feat(config): configurable dream pipeline thresholds (#731)

Replaces hardcoded dream pipeline constants with configurable values.

Changes:
- DreamConfig struct (uteke-core + uteke-cli config)
  - contradict_similarity_threshold: 0.6 (cosine > this → NOT contradiction)
  - contradict_tag_jaccard_min: 0.4 (up from 0.3, reduces false positives)
  - contradict_max_memories: 200 (O(n²) scan limit)
  - dedup_threshold: 0.92 (cosine > this → merge candidate)
  - orphan_importance_threshold: 0.15 (safer than old 0.3)
- MaintenanceConfig defaults changed to safer values:
  - auto_aging_enabled: true → false (opt-in)
  - auto_aging_interval_hours: 6 → 24 (daily)
  - auto_dream_interval_days: 3 → 7 (weekly)
- DreamConfig wired into CLI main.rs and server main.rs via set_dream_config()
- dream.rs reads from self.dream_config instead of const values
- DEFAULT_ORPHAN_THRESHOLD updated 0.3 → 0.15
- Server reads [dream] section from uteke.toml

Config example:
  [dream]
  contradict_similarity_threshold = 0.6
  contradict_tag_jaccard_min = 0.4
  contradict_max_memories = 200
  dedup_threshold = 0.92
  orphan_importance_threshold = 0.15

Closes #731

* fix: resolve type mismatch and format issues (#731)

- consolidate() expects f32 not f64 — remove incorrect cast
- Server dream config: unwrap_or_default() for Option<usize> returns 0
  (wrong), use unwrap_or(DreamConfig::default().*) instead
- Format: restructure server dream config block for rustfmt compliance

* fix(format): collapse consolidate call to single line per rustfmt

* fix(onboard): resolve clippy errors in test code

- Remove unused 'toggles' vec in test_write_config_generates_valid_toml
- Replace useless format! with raw string literal
- Remove stray closing parenthesis

* fix(core): robust embedding model download with timeout, retry, streaming (#740)

- Add connect timeout (30s) and read timeout (300s) to prevent
  infinite hangs on slow/unstable connections
- Retry up to 3 attempts on transient failures with cleanup
- Stream download to disk via 64KB chunks instead of buffering
  entire 187MB model_data file in RAM
- Add progress indicator (file size + percentage milestones)
- Add human-readable byte formatting (KB/MB/GB)
- Verify content-length matches downloaded bytes

* fix: use checked_div for clippy compliance (#744)

* docs: add SECURITY.md and PR template

- SECURITY.md: supported versions, vulnerability reporting process, SLA
- PULL_REQUEST_TEMPLATE.md: type checklist, CI verification, conventional commits

* fix: use GitHub Private Vulnerability Reporting instead of public issue

Cora caught that instructing reporters to open a public issue with
[SECURITY] prefix contradicts the 'do not open a public issue' policy.
GitHub issues are public by default — only Private Vulnerability Reporting
provides proper confidentiality.

* fix(core): prevent os error 33 on Windows by reading usearch index from locked file handle

* style(core): apply cargo fmt to fix CI check

* fix(core): filter deprecated memories in recall() (#748)

recall() vector search path was missing a deprecated filter, causing
deprecated memories to appear in search results (61.7% in reported case).

FTS5 path already filters at SQL level (m.deprecated = 0).
recall_at_time() already filters in post-filter.

Adding the filter in recall() fixes all downstream paths:
- Vector strategy
- Hybrid RRF (calls recall())
- Graph rerank (calls recall_rrf -> recall())

* chore: release v0.9.0 — version bump, CHANGELOG

v0.9.0 features:
- uteke onboard interactive wizard
- API URL versioning /api/v1, /api/v2
- Configurable dream pipeline thresholds
- SECURITY.md + PR template

v0.9.0 fixes:
- Deprecated memories in vector search (#748)
- Windows OS error 33 (#747)
- Embedding download robustness (#740)

* fix: bump intra-workspace dependency versions to 0.9.0

uteke-cli, uteke-mcp, and uteke-server had hardcoded uteke-core/mcp
version = "0.8.0" instead of using workspace version. Updated all
to 0.9.0 to match workspace.package.version.

* docs: add contributors section to v0.9.0 changelog

Thank @webhop123 for Windows OS error 33 fix (#747).

* fix(api): ensure leading slash on versioned API path after strip_prefix

ApiVersion::from_path() stripped '/api/v1/' prefix producing 'recall'
instead of '/recall', causing all /api/v1/* and /api/v2/* routes to
return 404. The fix ensures the stripped path always starts with '/'.

Also adds @gnoviawan to v0.9.0 CHANGELOG contributors.

* fix(api): ensure leading slash on versioned API path after strip_prefix

ApiVersion::from_path() stripped '/api/v1/' prefix producing 'recall'
instead of '/recall', causing all /api/v1/* and /api/v2/* routes to
return 404. The fix strips '/api/v1' (no trailing slash), keeping the
leading '/' in the remainder for correct route pattern matching.

Also adds @gnoviawan to v0.9.0 CHANGELOG contributors.

* chore: release v0.9.1 — patch fix for API versioning routes

- Fix ApiVersion::from_path() strip_prefix producing path without leading /
- Add @gnoviawan to CHANGELOG contributors
- Bump workspace + intra-workspace deps to 0.9.1

* chore: upgrade Rust edition 2021 → 2024 (#755)

- edition 2021 → 2024 (requires Rust 1.85+)
- rust-version 1.75 → 1.85 (edition 2024 MSRV)

Audit: no unsafe extern blocks, no gen keyword, no raw pointer derives.
Codebase uses explicit dyn, no edition-breaking patterns found.

Closes #755

* fix: edition 2024 — remove explicit ref bindings + apply rustfmt

Edition 2024 introduces implicitly-borrowing patterns, making
`Some(ref x)` an error. Also changes rustfmt import grouping.

- doc.rs:348: Some(ref json_str) → Some(json_str)
- room.rs:95: Some(ref q) → Some(q)
- cargo fmt --all (38 files, import reorder)

* fix: wrap set_var/remove_var in unsafe blocks (edition 2024)

Edition 2024 makes std::env::set_var and std::env::remove_var unsafe.
All test code using these functions now wraps calls in unsafe {} blocks.

- uteke-core/src/lib.rs: 5 set_var + 10 remove_var
- uteke-cli/src/config.rs: 14 set_var + 19 remove_var

* fix: clippy edition 2024 lints — repeat_n + is_none_or

- graph_rerank.rs: repeat(?).take(n) → repeat_n(?, n)
- lib.rs: .map_or(true, |_| true) → .is_none_or(|_| true)

* fix: clippy let_binding_from_block in slug_from_path (edition 2024)

Remove unnecessary let binding — return the expression directly.

* fix: expose room_remember via HTTP + forget returns 404 (#762)

Bug A: POST /remember ignored 'room' field because remember_in_room()
was never exposed via HTTP. Added POST /room/remember endpoint that
calls uteke.remember_in_room() — stores memory AND links to room.

Bug B: DELETE /forget returned 200 even when memory ID doesn't exist.
Now checks existence first and returns proper 404.

Tags field: verified code path is correct — deserialization and storage
both handle tags properly. The production 500 was an infrastructure
issue (embedder), not a code bug.

* chore: release v0.10.0 — edition 2024 + room/remember endpoint

- Rust edition 2021 → 2024, min Rust 1.85
- POST /room/remember HTTP endpoint
- DELETE /forget returns 404 for non-existent IDs
- README/docs Rust version badges updated

* feat: rewrite hermes-memory-provider as pre_llm_call plugin hook

* fix(test): update memory_provider tests for pre_llm_call plugin

* fix(plugin): remove stale @staticmethod, use context manager for file read

- @staticmethod on module-level function causes TypeError at runtime
- open() without context manager leaks file handle
Both found by CodeCora review.

* fix: uteke-tool template dynamic namespace and room ns params

* fix: skip DB open for upgrade command (#772), fix doc/delete query parsing (#776)

Fix #772: uteke upgrade hangs when database is locked
- Add Commands::Upgrade to early-exit block in main.rs (alongside
  Completions, Init, Onboard, Bench). Upgrade only needs network +
  filesystem — no store access required.

Fix #776: DELETE /doc/delete fails to parse query params
- doc_delete handler passed full URL to parse_query_param(), which
  only splits on '&' and '=' without stripping the path portion.
- Use req.url().query() instead, which returns just the query string.
  Matches the pattern already used in parse_query_namespace().

* fix CI: make upgrade module pub(crate), use split pattern for query string

- commands/mod.rs: mod upgrade → pub(crate) mod upgrade (was private,
  main.rs couldn't access it)
- handlers.rs: req.url().query() → req.url().split('?').nth(1) because
  tiny_http Request::url() returns &str, not url::Url

* feat: migrate default data dir ~/.uteke → ~/.codecora/uteke with auto-migration (#773)

- uteke_home() now defaults to ~/.codecora/uteke
- Auto-migrates legacy ~/.uteke on first run (rename + cross-device fallback)
- CLI global_config_path() uses uteke_core::uteke_home() instead of hardcoded path
- Updated all docs, CLI help text, and extension templates
- Test input strings for set_namespace_in_toml intentionally kept (simulates old user config)
- CHANGELOG.md historical entries untouched

* style: apply cargo fmt to lib.rs

* fix: remove redundant &format! borrow in Error::generic calls (clippy)

* fix: address review — Windows paths in INSTALL.md, sandbox test to temp dir

* fix: prevent env var test pollution between parallel test threads

Both uteke_home tests now save and restore HOME/UTEKE_HOME env vars
to prevent race conditions when Rust runs tests in parallel.

* style: use if-let instead of match for single-pattern clippy

* release: bump v0.10.1

* fix: isolate test_uteke_home_with_env from parallel env var pollution

Race condition: test_uteke_home_default_no_migration sets HOME and
removes UTEKE_HOME while test_uteke_home_with_env sets UTEKE_HOME.
When running in parallel, remove_var from one thread can clobber
set_var from another, causing the assertion to see the default path
instead of the UTEKE_HOME override.

Fix: also set HOME to a unique temp dir in the env test, matching
the pattern already used by the default-path test.

* docs: fix hermes integration examples and add API notes

* chore: bump quinn-proto 0.11.14 → 0.11.15 (GHSA-4w2j-m93h-cj5j)

Fixes Trivy HIGH vulnerability in transitive dependency chain:
reqwest → quinn → quinn-proto

Also syncs Cargo.lock with workspace version 0.10.1.

* chore(deps): bump which from 7.0.3 to 8.0.5

Bumps [which](https://github.com/harryfei/which-rs) from 7.0.3 to 8.0.5.
- [Release notes](https://github.com/harryfei/which-rs/releases)
- [Changelog](https://github.com/harryfei/which-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/harryfei/which-rs/compare/7.0.3...8.0.5)

---
updated-dependencies:
- dependency-name: which
  dependency-version: 8.0.5
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: filter deprecated memories from room operations (#784, #785, #786) (#790)

* fix: filter deprecated memories from room operations (#784, #785, #786)

#784 — room_stats, recall_room, get_room_memory_ids now filter
deprecated=0 via INNER JOIN. Previously counted deprecated memories
causing 76% stat inflation (620 vs 148). Added 3 test cases.

#785 — POST /room/recall query is now optional. When query is
None/empty, falls back to chronological recall_room instead of
returning 400 error.

#786 — Added docs/api-reference.md with full HTTP API reference
covering 30+ endpoints. Added to VitePress sidebar.

Closes #784, #785, #786

* style: cargo fmt

---------

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

* fix: runtime ORT dispatch for non-AVX2 CPUs + validation error mapping (#709, #789) (#792)

* feat(onnx): runtime ORT library dispatch for non-AVX2 CPUs (#709)

Switch from ort's download-binaries (which bundles AVX2-only lib) to
load-dynamic, enabling runtime selection between AVX2 and SSE4.2 ORT
shared libraries.

Changes:
- Cargo.toml: ort features = [load-dynamic, ndarray, api-18]
  (api-18 works around pykeio/ort#547 vitis EP compile error)
- New embed/ort_init.rs: CPU feature detection + library resolution
  (ORF_LIB_PATH env > ./libonnxruntime.so > ./ort-legacy/libonnxruntime.so)
- engine.rs: call init_ort_environment() before OnnxEmbedder::new()
- Non-ORT backends (custom, ollama, openai) are unaffected

CI packaging and legacy ORT sidecar build will follow in next commits.

* ci(release): add ORT sidecar packaging + legacy SSE4.2 build

Step 5 & 6 — CI pipeline and release packaging for load-dynamic ORT.

Release workflow changes:
- Add build-ort-legacy job: builds ORT 1.24.4 from source with
  onnxruntime_USE_AVX=OFF (SSE4.2 only) for SIGILL fix (#709)
- Add package-legacy job: assembles legacy Linux x64 bundle with
  ort-legacy/ sidecar alongside standard ORT libraries
- All platforms now download prebuilt ORT shared libs from GitHub
  releases (load-dynamic requires runtime .so/.dylib/.dll)
- Copy symlinks + actual .so files for proper dlopen resolution
- Docker build: arch-specific ort-amd64/ort-arm64 subfolders to
  avoid cross-arch .so contamination in multi-platform builds

Dockerfile changes:
- Copy ORT shared libs to /usr/local/lib/ with ldconfig
- Select arch-specific ORT libs via TARGETARCH
- Copies libonnxruntime_providers_shared.so too

ort_init.rs changes:
- Add system lib path fallback (/usr/local/lib, /usr/lib, /lib)
  for Docker containers where binary and .so are in different dirs
- Update module doc with full 5-step resolution order

Refs: #709

* fix: return 400 for Validation errors in /room/remember (#789)

* ci(release): add Windows legacy SSE4.2 ORT build (#709)

- Add build-ort-legacy-windows job: builds ORT from source on windows-latest
  with AVX/AVX2/AVX512 explicitly disabled (SSE4.2 only)
- Add package-legacy-windows job: combines standard bundle + legacy DLLs
- Update release job to depend on package-legacy-windows
- Add Windows legacy row to release notes table
- Guard Unix-only system lib paths with #[cfg(unix)] in ort_init.rs

* docs: update CHANGELOG for #709 #785 #789

* style: apply cargo fmt to engine.rs

---------

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

* chore(deps): bump uuid from 1.23.5 to 1.24.0

Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.23.5 to 1.24.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.5...v1.24.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: auto-generated API reference docs via schemars (#786) (#795)

Add crates/docgen — a standalone binary that reads the route registry
and type schemas to generate docs/api-reference.md automatically.

Architecture:
- api_registry.rs: single source of truth for endpoint metadata
  (method, path, description, request/response types, related issues)
- types.rs: #[derive(JsonSchema)] behind docgen feature flag
- docgen binary: reads registry + generates JSON schemas via schemars,
  renders to structured markdown with field tables
- CI: docs-check job fails if api-reference.md is stale

The docgen feature flag ensures zero runtime overhead — schemars is
only compiled when generating docs, not in production builds.

Closes #786

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

* chore(deps): bump thiserror from 2.0.18 to 2.0.19

Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.18 to 2.0.19.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.18...2.0.19)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-version: 2.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump serde_json from 1.0.150 to 1.0.151

Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.150 to 1.0.151.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.150...v1.0.151)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.151
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: accept short ID prefix in DELETE /forget (#794) (#796)

list and room_recall display only 8-char UUID prefixes, but forget
required a full UUID — making it impossible to delete memories from
CLI output.

- Add resolve_id_prefix() to crud + operations (LIKE query, returns
  full ID for exact match, Err for ambiguous)
- Handler tries full UUID first, then resolves prefix on failure
- Ambiguous prefix (>1 match) returns 400 with count
- Still returns 404 if prefix doesn't match any memory

Closes #794

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

* chore: v0.10.2 pre-release — version bump + docs sync (#797)

- Bump workspace version 0.10.1 → 0.10.2
- CHANGELOG: promote [Unreleased] → [0.10.2], add entries for
  #784 (deprecated memory filter), #785 (optional room/recall query),
  #794 (short ID prefix in forget), auto-generated API docs (#786),
  quinn-proto security patch
- README.md: v0.7.3 → v0.10.2, test count 206 → 431
- README.id.md: v0.7.2 → v0.10.2, test count 206 → 431,
  ~/.uteke → ~/.codecora/uteke
- INSTALL.md: version examples 0.0.2/0.0.7 → 0.10.2
- Regenerate docs/api-reference.md (version bump)

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

* chore(deps): bump clap from 4.6.1 to 4.6.4

Bumps [clap](https://github.com/clap-rs/clap) from 4.6.1 to 4.6.4.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.4)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Release v0.10.2 (#798)

* docs: fix hermes integration examples and add API notes

* chore: bump quinn-proto 0.11.14 → 0.11.15 (GHSA-4w2j-m93h-cj5j)

Fixes Trivy HIGH vulnerability in transitive dependency chain:
reqwest → quinn → quinn-proto

Also syncs Cargo.lock with workspace version 0.10.1.

* fix: filter deprecated memories from room operations (#784, #785, #786) (#790)

* fix: filter deprecated memories from room operations (#784, #785, #786)

#784 — room_stats, recall_room, get_room_memory_ids now filter
deprecated=0 via INNER JOIN. Previously counted deprecated memories
causing 76% stat inflation (620 vs 148). Added 3 test cases.

#785 — POST /room/recall query is now optional. When query is
None/empty, falls back to chronological recall_room instead of
returning 400 error.

#786 — Added docs/api-reference.md with full HTTP API reference
covering 30+ endpoints. Added to VitePress sidebar.

Closes #784, #785, #786

* style: cargo fmt

---------

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

* fix: runtime ORT dispatch for non-AVX2 CPUs + validation error mapping (#709, #789) (#792)

* feat(onnx): runtime ORT library dispatch for non-AVX2 CPUs (#709)

Switch from ort's download-binaries (which bundles AVX2-only lib) to
load-dynamic, enabling runtime selection between AVX2 and SSE4.2 ORT
shared libraries.

Changes:
- Cargo.toml: ort features = [load-dynamic, ndarray, api-18]
  (api-18 works around pykeio/ort#547 vitis EP compile error)
- New embed/ort_init.rs: CPU feature detection + library resolution
  (ORF_LIB_PATH env > ./libonnxruntime.so > ./ort-legacy/libonnxruntime.so)
- engine.rs: call init_ort_environment() before OnnxEmbedder::new()
- Non-ORT backends (custom, ollama, openai) are unaffected

CI packaging and legacy ORT sidecar build will follow in next commits.

* ci(release): add ORT sidecar packaging + legacy SSE4.2 build

Step 5 & 6 — CI pipeline and release packaging for load-dynamic ORT.

Release workflow changes:
- Add build-ort-legacy job: builds ORT 1.24.4 from source with
  onnxruntime_USE_AVX=OFF (SSE4.2 only) for SIGILL fix (#709)
- Add package-legacy job: assembles legacy Linux x64 bundle with
  ort-legacy/ sidecar alongside standard ORT libraries
- All platforms now download prebuilt ORT shared libs from GitHub
  releases (load-dynamic requires runtime .so/.dylib/.dll)
- Copy symlinks + actual .so files for proper dlopen resolution
- Docker build: arch-specific ort-amd64/ort-arm64 subfolders to
  avoid cross-arch .so contamination in multi-platform builds

Dockerfile changes:
- Copy ORT shared libs to /usr/local/lib/ with ldconfig
- Select arch-specific ORT libs via TARGETARCH
- Copies libonnxruntime_providers_shared.so too

ort_init.rs changes:
- Add system lib path fallback (/usr/local/lib, /usr/lib, /lib)
  for Docker containers where binary and .so are in different dirs
- Update module doc with full 5-step resolution order

Refs: #709

* fix: return 400 for Validation errors in /room/remember (#789)

* ci(release): add Windows legacy SSE4.2 ORT build (#709)

- Add build-ort-legacy-windows job: builds ORT from source on windows-latest
  with AVX/AVX2/AVX512 explicitly disabled (SSE4.2 only)
- Add package-legacy-windows job: combines standard bundle + legacy DLLs
- Update release job to depend on package-legacy-windows
- Add Windows legacy row to release notes table
- Guard Unix-only system lib paths with #[cfg(unix)] in ort_init.rs

* docs: update CHANGELOG for #709 #785 #789

* style: apply cargo fmt to engine.rs

---------

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

* feat: auto-generated API reference docs via schemars (#786) (#795)

Add crates/docgen — a standalone binary that reads the route registry
and type schemas to generate docs/api-reference.md automatically.

Architecture:
- api_registry.rs: single source of truth for endpoint metadata
  (method, path, description, request/response types, related issues)
- types.rs: #[derive(JsonSchema)] behind docgen feature flag
- docgen binary: reads registry + generates JSON schemas via schemars,
  renders to structured markdown with field tables
- CI: docs-check job fails if api-reference.md is stale

The docgen feature flag ensures zero runtime overhead — schemars is
only compiled when generating docs, not in production builds.

Closes #786

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

* fix: accept short ID prefix in DELETE /forget (#794) (#796)

list and room_recall display only 8-char UUID prefixes, but forget
required a full UUID — making it impossible to delete memories from
CLI output.

- Add resolve_id_prefix() to crud + operations (LIKE query, returns
  full ID for exact match, Err for ambiguous)
- Handler tries full UUID first, then resolves prefix on failure
- Ambiguous prefix (>1 match) returns 400 with count
- Still returns 404 if prefix doesn't match any memory

Closes #794

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

* chore: v0.10.2 pre-release — version bump + docs sync (#797)

- Bump workspace version 0.10.1 → 0.10.2
- CHANGELOG: promote [Unreleased] → [0.10.2], add entries for
  #784 (deprecated memory filter), #785 (optional room/recall query),
  #794 (short ID prefix in forget), auto-generated API docs (#786),
  quinn-proto security patch
- README.md: v0.7.3 → v0.10.2, test count 206 → 431
- README.id.md: v0.7.2 → v0.10.2, test count 206 → 431,
  ~/.uteke → ~/.codecora/uteke
- INSTALL.md: version examples 0.0.2/0.0.7 → 0.10.2
- Regenerate docs/api-reference.md (version bump)

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

---------

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

* fix(ci): Windows legacy ORT C4875 suppress + Docker artifact download (#799)

- Add /wd4875 to Windows legacy ORT CMake to suppress C4875
  (non-string literal [[gsl::suppress]] deprecated) treated as error
- Remove merge-multiple from Docker artifact download — v7 auto-extracts
  which loses the .tar.gz wrapper the script expects

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

* fix(ci): Windows legacy ORT C4875 suppress + Docker artifact download (#801)

- Add /wd4875 to Windows ORT cmake (C4875 gsl::suppress deprecated)
- Remove merge-multiple from Docker artifact download (v7 auto-extracts)

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

* fix(ci): Windows ORT CMP0091 + Docker/Windows skip-decompress (#802)

* docs: fix hermes integration examples and add API notes

* chore: bump quinn-proto 0.11.14 → 0.11.15 (GHSA-4w2j-m93h-cj5j)

Fixes Trivy HIGH vulnerability in transitive dependency chain:
reqwest → quinn → quinn-proto

Also syncs Cargo.lock with workspace version 0.10.1.

* fix: filter deprecated memories from room operations (#784, #785, #786) (#790)

* fix: filter deprecated memories from room operations (#784, #785, #786)

#784 — room_stats, recall_room, get_room_memory_ids now filter
deprecated=0 via INNER JOIN. Previously counted deprecated memories
causing 76% stat inflation (620 vs 148). Added 3 test cases.

#785 — POST /room/recall query is now optional. When query is
None/empty, falls back to chronological recall_room instead of
returning 400 error.

#786 — Added docs/api-reference.md with full HTTP API reference
covering 30+ endpoints. Added to VitePress sidebar.

Closes #784, #785, #786

* style: cargo fmt

---------

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

* fix: runtime ORT dispatch for non-AVX2 CPUs + validation error mapping (#709, #789) (#792)

* feat(onnx): runtime ORT library dispatch for non-AVX2 CPUs (#709)

Switch from ort's download-binaries (which bundles AVX2-only lib) to
load-dynamic, enabling runtime selection between AVX2 and SSE4.2 ORT
shared libraries.

Changes:
- Cargo.toml: ort features = [load-dynamic, ndarray, api-18]
  (api-18 works around pykeio/ort#547 vitis EP compile error)
- New embed/ort_init.rs: CPU feature detection + library resolution
  (ORF_LIB_PATH env > ./libonnxruntime.so > ./ort-legacy/libonnxruntime.so)
- engine.rs: call init_ort_environment() before OnnxEmbedder::new()
- Non-ORT backends (custom, ollama, openai) are unaffected

CI packaging and legacy ORT sidecar build will follow in next commits.

* ci(release): add ORT sidecar packaging + legacy SSE4.2 build

Step 5 & 6 — CI pipeline and release packaging for load-dynamic ORT.

Release workflow changes:
- Add build-ort-legacy job: builds ORT 1.24.4 from source with
  onnxruntime_USE_AVX=OFF (SSE4.2 only) for SIGILL fix (#709)
- Add package-legacy job: assembles legacy Linux x64 bundle with
  ort-legacy/ sidecar alongside standard ORT libraries
- All platforms now download prebuilt ORT shared libs from GitHub
  releases (load-dynamic requires runtime .so/.dylib/.dll)
- Copy symlinks + actual .so files for proper dlopen resolution
- Docker build: arch-specific ort-amd64/ort-arm64 subfolders to
  avoid cross-arch .so contamination in multi-platform builds

Dockerfile changes:
- Copy ORT shared libs to /usr/local/lib/ with ldconfig
- Select arch-specific ORT libs via TARGETARCH
- Copies libonnxruntime_providers_shared.so too

ort_init.rs changes:
- Add system lib path fallback (/usr/local/lib, /usr/lib, /lib)
  for Docker containers where binary and .so are in different dirs
- Update module doc with full 5-step resolution order

Refs: #709

* fix: return 400 for Validation errors in /room/remember (#789)

* ci(release): add Windows legacy SSE4.2 ORT build (#709)

- Add build-ort-legacy-windows job: builds ORT from source on windows-latest
  with AVX/AVX2/AVX512 explicitly disabled (SSE4.2 only)
- Add package-legacy-windows job: combines standard bundle + legacy DLLs
- Update release job to depend on package-legacy-windows
- Add Windows legacy row to release notes table
- Guard Unix-only system lib paths with #[cfg(unix)] in ort_init.rs

* docs: update CHANGELOG for #709 #785 #789

* style: apply cargo fmt to engine.rs

---------

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

* feat: auto-generated API reference docs via schemars (#786) (#795)

Add crates/docgen — a standalone binary that reads the route registry
and type schemas to generate docs/api-reference.md automatically.

Architecture:
- api_registry.rs: single source of truth for endpoint metadata
  (method, path, description, request/response types, related issues)
- types.rs: #[derive(JsonSchema)] behind docgen feature flag
- docgen binary: reads registry + generates JSON schemas via schemars,
  renders to structured markdown with field tables
- CI: docs-check job fails if api-reference.md is stale

The docgen feature flag ensures zero runtime overhead — schemars is
only compiled when generating docs, not in production builds.

Closes #786

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

* fix: accept short ID prefix in DELETE /forget (#794) (#796)

list and room_recall display only 8-char UUID prefixes, but forget
required a full UUID — making it impossible to delete memories from
CLI output.

- Add resolve_id_prefix() to crud + operations (LIKE query, returns
  full ID for exact match, Err for ambiguous)
- Handler tries full UUID first, then resolves prefix on failure
- Ambiguous prefix (>1 match) returns 400 with count
- Still returns 404 if prefix doesn't match any memory

Closes #794

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

* chore: v0.10.2 pre-release — version bump + docs sync (#797)

- Bump workspace version 0.10.1 → 0.10.2
- CHANGELOG: promote [Unreleased] → [0.10.2], add entries for
  #784 (deprecated memory filter), #785 (optional room/recall query),
  #794 (short ID prefix in forget), auto-generated API docs (#786),
  quinn-proto security patch
- README.md: v0.7.3 → v0.10.2, test count 206 → 431
- README.id.md: v0.7.2 → v0.10.2, test count 206 → 431,
  ~/.uteke → ~/.codecora/uteke
- INSTALL.md: version examples 0.0.2/0.0.7 → 0.10.2
- Regenerate docs/api-reference.md (version bump)

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

* fix(ci): Windows legacy ORT C4875 suppress + Docker artifact download (#799)

- Add /wd4875 to Windows legacy ORT CMake to suppress C4875
  (non-string literal [[gsl::suppress]] deprecated) treated as error
- Remove merge-multiple from Docker artifact download — v7 auto-extracts
  which loses the .tar.gz wrapper the script expects

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

* fix(ci): Windows ORT CMP0091 policy + Docker/Windows skip-decompress

- Add CMAKE_POLICY_DEFAULT_CMP0091=NEW to ORT build so CMAKE_MSVC_RUNTIME_LIBRARY
  propagates to FetchContent deps (protobuf). Fixes LNK2038 RuntimeLibrary mismatch.
- Add skip-decompress: true to Docker download-artifact to preserve .tar.gz files
- Add skip-decompress: true to Windows legacy packaging to preserve .zip artifact

---------

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

* fix(ci): remove Windows legacy ORT build + upgrade download-artifact to v8 (#803)

- Remove build-ort-legacy-windows job (ORT v1.24.4 removed
  --enable_msvc_static_runtime, internal protobuf hardcodes /MT
  causing unfixable LNK2038)
- Remove package-legacy-windows job (depends on removed ORT build)
- Upgrade download-artifact v4→v8 in Docker job with skip-decompress:
  true (v4 doesn't support skip-decompress, was silently ignored)
- Update release notes: remove Windows legacy bundle reference
- Linux legacy bundle preserved (no CRT mismatch on Linux)

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

* fix(ci): simplify Docker artifact extraction - v4 default auto-extract (#804)

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

* fix(ci): create binaries dir inside release-artifacts after cd (#806)

The mkdir -p binaries ran before cd release-artifacts, creating the
directory in the workspace root. Subsequent mv commands wrote to
release-artifacts/binaries/ instead, leaving the Docker context
binaries/ empty. Fix: cd first, then mkdir.

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

* fix(ci): keep binaries/ at workspace root for Docker context (#808)

Dockerfile does 'COPY binaries/ ./' expecting binaries/ at the build
context root (workspace root). The previous fix (#806) moved 'cd
release-artifacts' before 'mkdir binaries', which placed binaries/
inside release-artifacts/ — invisible to Docker build context.

Root cause: download-artifact@v4 downloads into release-artifacts/,
but Docker context is workspace root (.). Fix: don't cd. Create
binaries/ at workspace root, find artifacts via 'find release-artifacts'.

Fixes the 'COPY binaries/ not found' Docker buildx error.

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

* fix(ort): force /MD runtime for Windows legacy ORT build

ONNX Runtime v1.24.4 build from source on Windows fails with LNK2038
RuntimeLibrary mismatch: protobuf (MT_StaticRelease) vs onnxruntime
(MD_DynamicRelease). Add -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL
to force dynamic CRT across all dependencies.

Bump cache key v1→v2 to invalidate cached build without the flag.

* fix(ort): force /MD + /NODEFAULTLIB for Windows ORT build

CMAKE_MSVC_RUNTIME_LIBRARY alone insufficient — protobuf submodule
ignores it and compiles with /MT. Add explicit /MD to CMAKE_CXX_FLAGS
and CMAKE_C_FLAGS, plus /NODEFAULTLIB:LIBCMT;LIBCPMT to linker to
prevent static CRT symbols from being linked.

Bump cache key v2→v3.

* ci: drop Windows legacy ORT build and package jobs

Windows ORT build from source fails with CRT mismatch (LNK2038) —
protobuf submodule hardcodes /MT regardless of CMAKE_MSVC_RUNTIME_LIBRARY.

Removes:
- build-ort-legacy-windows job
- package-legacy-windows job
- package-legacy-windows from release needs
- Windows legacy bundle from release notes

99%+ Windows CPUs (Intel Haswell 2013+, AMD Excavator 2015+) support
AVX2. Pre-built AVX2 release already works. Linux legacy bundle
retained for server-grade old hardware (Celeron J4125/N4020).

* ci: filter release artifacts to avoid Docker buildx metadata

Download step used merge-multiple:true without pattern, downloading
ALL artifacts including Docker buildx metadata (.dockerbuild zip).
This corrupt artifact caused "download failed after 5 retries".

Fix: add pattern: uteke-* to only download release binary artifacts.

* ci: fix release notes template — heredoc with proper YAML escaping

* chore(deps): bump actions/cache from 4 to 6

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: ORT init cascade — AVX2 fallthrough, error message accuracy, embedder init cache

Fixes #820, #821, #822

#820 — ort_init.rs: AVX2 path no longer early-returns Err when standard
library is not found in exe_dir. Falls through to system paths
(/usr/local/lib, /usr/lib, /lib) which is the common Docker layout.
Added !avx2 guard on legacy path to prevent AVX2 CPUs from accidentally
loading SSE4.2-only library. Descriptive error at the end lists all
searched locations.

#821 — engine.rs: Replaced misleading 'CPU may lack required SIMD support'
error message with actionable 'Set ORT_LIB_PATH to the library file,
or use the standard release bundle.' The old message sent users down
the wrong debugging path (checking CPU capabilities instead of library
placement).

#822 — lib.rs: Added embedder_init_error: Mutex<Option<String>> field
that caches the first embedder initialization failure. Subsequent calls
to ensure_embedder() return immediately with the cached error instead
of retrying the expensive ORT/model init on every request. This prevents
log spam and CPU waste in Docker environments where the ORT library is
persistently missing. Uses String storage because Error type is not Clone.

Tests: 379 passed, 0 failed.

* fix: TTL-based error cache for transient network failures (#822 review)

- Change embedder_init_error from Mutex<Option<String>> to
  Mutex<Option<(String, Instant, bool)>> with permanent/transient flag
- ONNX failures (missing lib) cached permanently — no point retrying
- OpenAI/Ollama failures use 60s TTL — auto-retry after outage passes
- Address Cora code review: prevent permanent disable on transient errors
- Fix rustfmt string continuation in non-onnx error message

Fixes CodeCora review feedback on #822.

* release: v0.10.3 — ORT init fixes, dep bumps

* revert: undo PR #837 merge (develop → main)

Reverts all changes merged via PR #837 (develop → main).
Main restored to pre-merge state (8a12376).

Reason: PR was merged with --admin bypass on failing CI
(Branch Naming check failed for develop branch).
Must fix pr-checks.yml before re-merging.

* chore(release): v0.11.0 — code + docs changes from develop

Re-applies all v0.11.0 content that was reverted by PR #838.
Includes: version bump, changelog, code fixes (doc_move, recall score,
governance docs), issue templates, API reference.

* chore: merge develop to main for v0.14.0 release (#1020)

* fix(docs): standardize recall latency to ~45ms

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

* chore: harden .gitignore + restructure repo for public safety

- 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.

* feat: hybrid as default recall strategy + import fixes (#1005, #1014) (#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>

* perf: Phase 2 — skip update check in batch mode, pre-truncate embedding 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>

* feat: Phase 3 — lifecycle/deprecated, memory tools guide, source provenance (#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 …
ajianaz added a commit that referenced this pull request Oct 15, 2026
* fix(cli): uteke-cli crates.io publish failure + release verify step (#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>

* chore: bump version to 0.14.2 (#1031)

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

* ci(release): reject stale RELEASE_NOTES.md (#1033)

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>

* fix: HTTP & MCP recall strategy — default hybrid, loud validation (#1034, #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>

* chore: bump version to 0.14.3 (#1039)

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

* fix(ci): release notes heredoc executed installer on runner — use template 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>

* docs: sync stale version refs, roadmap v0.13–v0.14, merge comparison 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 222d06740a9988745ae47569d9cbba461cd2a492.

* docs(agent): Critical Rule #14 — explicit approval before execution (#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.

* test: use ORT_LIB_NAME in find_ort_in_dir exact-match test (#1054) (#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.

* chore: switch ID generation new_v4 → now_v7 (#1058) (#1060)

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.

* fix: soft-forgotten memories leak into list, search, and doctor counts (#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.

* fix: search_content(None) coerced to default namespace — cross-namespace 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.

* fix: recall cache hit skips salience/recency boosts — cold/warm score 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.

* fix: /export drops namespace attribution + deprecated-row delta undocumented (#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.

* fix(mcp): uteke_dream destructive defaults — dry-run first, scope or 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

* feat(mcp): enrich tool outputs agents need — stats tiers/namespaces, 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)

* feat(mcp): uteke_get + uteke_update tools; short-ID resolution for all 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.

* feat: structural export — full-store round-trip (rooms, graph, edges, documents, timeline) (#1068)

* feat: structural export — full-store round-trip (memories, rooms, graph, edges, documents, chunks, timeline) (#1057)

uteke export only dumped the memories table; rooms, knowledge graph,
memory edges, documents (+chunks) and timeline evaporated on
machine-to-machine migration and cloud→OSS exit.

Format (structural-v1): single NDJSON stream — manifest first line
{"uteke_export":{format_version, section counts}} + tagged rows
({"type":"room"|"graph_node"|...}). Every line a JSON object —
filterable with standard ndjson tooling; no zip/tar dependency.

- export_full(): all 10 sections, ids verbatim, deprecated memories
  excluded (policy documented); embeddings dropped (portable)
- import_full(): SINGLE TRANSACTION (atomic — failure rolls back the
  whole restore), two-pass dependency order (memories before FK
  junctions, documents before chunks, nodes before edges),
  INSERT OR IGNORE (idempotent re-import), FTS rebuilt after restore,
  unknown tags ignored (forward-compatible)
- restored memories get NULL embeddings — load_all() already filters
  NULL from index builds (#992) and repair --reembed targets exactly
  these rows; result reports needs_reembed count
- CLI: uteke export --full; uteke import auto-detects the manifest
  line and routes to the structural path (legacy JSONL unchanged)
- cli-reference documented (previously the export format was entirely
  undocumented)

Round-trip test: 2 memories + room + link + memory edge + document +
chunk → export → fresh store → import → all sections restored with
original ids (room points at restored memory id), second import is a
no-op.

* fix: chunk created_at missing from structural dump SELECT (code-scanning #684)

The document_chunks SELECT dropped created_at when the dump block was
restructured; import bound unwrap_or("") so every restored chunk got a
silently-empty timestamp. Column restored on both sides; round-trip
test now asserts chunk created_at verbatim.

* fix: preserve exported source_type on structural import (code-scanning #686)

Dump wrote source_type verbatim but import hardcoded 'import' —
restored memories lost 'user'/'document' provenance. Bind the exported
value; 'import' remains the fallback for rows lacking it.

* feat: supersession workflow — mark stale decisions superseded, flagged at recall (#1069)

* feat: supersession workflow — mark stale decisions superseded, flagged at recall (#1053)

Long-lived rooms accumulate revisited decisions that stay
indistinguishable from current truth. The graph infra existed (typed
memory_edges since v8) but was never a recall-layer concern.

- EDGE_SUPERSEDED_BY type; Uteke::supersede(old,new,reason): wires the
  edge pair (old→new superseded_by, new→old supersedes) AND
  soft-deprecates the old memory in ONE transaction — partial failure
  rolls back entirely; recall cache invalidated for the namespace
- Uteke::supersession_of(id): resolves the current-replacement pointer
- MCP uteke_supersede {old_id,new_id,reason?} — accepts UUID or
  unambiguous prefix (resolve_id), self/unknown ids rejected loudly
- MCP uteke_recall output flags stale hits: '⚠ superseded by <id> —
  verify before acting' (covers legacy/hard-restored deprecated rows
  that still surface)
- docs/mcp.md updated

Tests: edge pair + deprecation + hide-from-list + self/unknown
rejection (core); short-id supersede via MCP executor.

* test: nanos-precision scratch dirs — WAL busy race under parallel workspace runs

* fix: re-supersession stacks edges + nondeterministic pointer lookup (code-scanning #687)

Superseding the same memory twice left both superseded_by pairs
(INSERT OR IGNORE) and supersession_of() picked LIMIT 1 with no ORDER
BY. Now: prior pair deleted in-transaction before the new pair;
lookup orders by created_at DESC deterministically. Test pins
re-supersession replacing the pointer with no stacking.

* ci: add source-branch-check — PRs into main must come from develop (#1072)

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

* chore: bump version to 0.15.0 (#1073)

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

* ci: source-branch-check allows chore/release-* release branches (#1075)

* ci: source-branch-check allows chore/release-* release branches

* ci: guard source-branch check on BASE_REF=main explicitly

---------

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

* ci: bump actions/download-artifact v4 -> v8 (Node 24) (#1080)

Fixes #1079. Node.js 20 is deprecated on GitHub-hosted runners;
download-artifact v8 targets Node 24. upload-artifact already at v7.

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

* fix(core): structural import silently dropped room_documents + harden roundtrip test (#1078 batch 1) (#1081)

- import: route room_document rows to a pending pass restored AFTER
  documents (FK doc_slug order was wrong; INSERT OR IGNORE hid the drop)
- export/import: include room_documents.added_at (NOT NULL, no default;
  missing column made every insert a silent no-op)
- remove dead match arms document/document_chunk superseded by pass-2
- test: seed all 10 sections, assert manifest + imported counts, chunk
  created_at and pinned flag round-trip verbatim, idempotent re-import
- mutants: 36/36 caught (was 19 missed)

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

* fix(ci): skip CLA check for bots (dependabot, renovate, github-actions) (#1077)

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

* feat(core): add author_type (human|agent) to memory records (#1084)

* feat(core): add author_type (human|agent) to memory records (#1083)

Schema v16: ALTER TABLE with DEFAULT 'agent' + index, backfills existing
rows. Memory struct + validate_author_type. set_author_type() on Store and
Uteke facade (invalid values -> Validation error). Server /remember accepts
optional author_type, 400 on invalid. All memory SELECTs now include
source/source_type/author_type (previously dropped on read). Version
asserts in tests use CURRENT_SCHEMA_VERSION instead of hardcoded numbers.

* fix(server): validate author_type before write + regenerate API docs (#1083)

Cora review finding: /remember inserted the memory before validating
author_type, so an invalid value left a persisted record while the client
got a 400 (partial-write). Validation now runs before any insert.
Regenerates docs/api-reference.md (API Docs Fresh check).

---------

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

* fix(server): wire 'at' time-travel param into /room/recall (#1085)

* fix(server): wire 'at' time-travel param into /room/recall (#1082)

RoomRecallRequest now accepts 'at' (RFC3339). Invalid timestamps
return 400 instead of being silently dropped. Both recall paths
(chronological fallback + semantic) apply point-in-time filtering
using the same temporal rules as core recall_at_time: created_at <= at,
valid_from <= at, valid_until > at, not deprecated.

* fix(server): over-fetch before temporal filter in room recall chronological path

Cora MAJOR on #1085: SQL LIMIT truncated candidates before the
point-in-time post-filter, returning fewer results than requested.
Fetch 3x (min limit+10) when 'at' is set, then truncate after filtering.

---------

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

* docs(server): document deprecation time-travel limitation (#1086) (#1087)

Cora review on #1085 flagged that deprecated memories are excluded
from time-travel regardless of when they were deprecated. The schema
lacks a deprecation timestamp, so this cannot be fixed without a
schema migration (tracked in #1086). Document the limitation at the
predicate until then.

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

* feat(core): room semantic segmentation (LLM-free, #1088) (#1090)

* feat(core): add room semantic segmentation PoC (LLM-free, #1088)

Segment a room's memories by embedding-distance boundaries (LycheeMemory
V2, arXiv:2608.12990). Boundaries: adjacent cosine < threshold or max
segment size; short runs merge into the previous segment. Segments are
the batching unit for future segment-level LLM consolidation — one LLM
call per segment instead of per memory.

* fix(core): propagate store errors in room_segments instead of silent drops

* perf(core): replace per-memory fetch with single-query recall_room in room_segments

* fix(core): merge short runs into actual preceding segment in room_segments

Previously the merge target was derived from the raw segment index
(merged[start] - 1). When the preceding run had itself been merged
earlier in the pass, this produced orphan indices, leaving tiny
segments behind and defeating min_size for chains of short runs.
Now targets merged[start - 1]. Adds regression test.

* test(core): use orthogonal embeddings in chain-merge regression test

---------

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

* feat(core): expose semantic segments in room_summary (#1088) (#1091)

Uteke::room_summary now enriches the store's tag-based summary with
embedding-distance segments when room memories have embeddings. Segments
are the batching units for future segment-level LLM consolidation
(LycheeMemory V2 granularity). No-embedding rooms keep segments=None
(serde-optional, backward compatible). room_segments split into a
public fetch + room_segments_inner to avoid double queries.

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

* feat(core): segment-level consolidation planner, measure-only (#1088) (#1092)

Adds room_consolidation_plan(): builds per-segment LLM batching plans
from the semantic segmentation (LycheeMemory V2) with call/token cost
estimates — zero LLM calls. Validated on clone prod DB: large rooms
show 67-91% call reduction vs naive per-memory consolidation.

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

* feat(core): provenance trust policy for consolidation (#1089) (#1093)

Adds provenance module: TrustTier classification from author/source types,
non-amplification rule (consolidated record capped at weakest source tier),
and a hedging guard rejecting confidence-upgrading rewrites (EN + ID markers).
Policy layer for the LLM consolidation executor; validated via unit tests.

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

* feat(core): LLM consolidation executor reusing extraction setup (#1088) (#1094)

* feat(core): LLM consolidation executor reusing extraction setup (#1088)

Adds consolidation_exec: executes a segment-based ConsolidationPlan via the
same OpenAI-compatible endpoint as the import/extraction pipeline (shared
ExtractionConfig - one setup, many uses). Each batch becomes one LLM call
with a consolidation prompt that preserves hedging. Outputs pass the #1089
provenance gate before write; sources are soft-deprecated, never deleted.
Budget-capped via max_llm_calls.

* fix(core): isolate per-batch LLM failures in consolidation executor

A failed LLM call no longer aborts execute_plan mid-run: the error is
recorded in ConsolidationExecution.batch_errors and remaining batches
still process. Failed batches leave their sources untouched.

* fix(core): embed consolidated records and isolate store failures per batch

- Compute embedding via ConsolidationStore::embed_content before insert so
  consolidated records are visible to vector recall (was embedding: vec![]).
- insert/deprecate failures abort only the affected batch and are recorded
  in batch_errors; the run continues (duplicate content from a partial
  write is recoverable by dedup, lost batches are not).

* fix(core): budget guard counts LLM requests made, not successes

Failed calls still consume an API request against max_llm_calls, so the
guard now uses a dedicated llm_calls_made counter incremented before the
call regardless of outcome. Also rewords an error message that tripped a
false-positive SQL-injection pattern (no SQL involved).

* fix(core): LLM client timeout + whitelist LLM-provided memory_type

- reqwest blocking client gets a 120s timeout so a slow consolidation
  endpoint cannot hang the run (parity with update_check).
- LLM-provided fact_type is validated against the allowed set
  (fact/decision/preference/procedure/context); anything else falls back
  to "fact" so arbitrary model output can never set an unknown type.

---------

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

* feat: wire consolidation pipeline into core, CLI, and HTTP (#1088) (#1095)

* feat: wire consolidation pipeline into core, CLI, and HTTP (#1088)

- Implement ConsolidationStore for Uteke (SQLite store + lazy embedder)
- Add embed_text/add_to_index helpers on Uteke for the write path
- CLI: uteke room consolidate <room> [--apply] [--max-calls N] (dry-run default)
- HTTP: POST /room/consolidate (dry-run default; apply gated on extraction
  LLM config with 503; hard budget cap)
- Register endpoint in API registry; document CLI + API in docs/
- Tests: trait dispatch, plan batching, deprecate+insert write paths

* fix: make insert_memory rollback-safe (review #1095)

Cora review flagged non-atomic insert: if add_to_index or
link_memory_to_room failed, the row stayed persisted but un-searchable
and/or unlinked. Now both failure paths compensate by removing the
index entry and deleting the row before returning the error. Added
remove_from_index helper for the compensating action.

---------

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

* chore(docs): regenerate api-reference via docgen (#1095 follow-up) (#1096)

The manual consolidate endpoint entry was placed in the wrong spot and
used hand-written content; docgen now renders it from the API registry
(including the #1088 related-tag).

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

* feat(core): vecq pure-Rust vector backend behind feature flag (#1099)

* feat(core): add vecq pure-Rust vector backend behind feature flag

Add an alternative vector index backend built on vecq-core (training-free
4-bit vector quantization, pure Rust). This removes the C++ toolchain
requirement for mobile/FFI builds and unblocks uteke-mobile#39.

- Make 'usearch' an optional feature (still in default)
- Add 'vecq' feature, mutually exclusive with 'usearch' (compile_error guard)
- VectorIndex public API unchanged; internals dispatch via cfg
- vecq tombstones: removed rows filtered via key map (no incremental delete)
- vecq search over-fetches k + dead-row count to compensate tombstones
- New CI job 'vecq-check' runs vecq-backend unit tests on every PR
- Unit tests pass on both backends (usearch: 7, vecq: 8 incl. tombstone test)

Closes #1098

* fix(core): derive vecq row key from physical row count

Address cora review findings on the vecq backend:

- CRITICAL: derive the vecq row key from `index.len()` (physical append-only
  row count) instead of the key-mapping sidecar's `next_key`. If the sidecar
  were stale relative to the index file after a crash, insert would map keys
  to the wrong physical rows and search would return wrong memories.
- MAJOR: over-fetch by the exact dead-row count (index.len() - live keys)
  instead of next_key-derived estimate, so search returns up to k live
  results even with tombstones.

* fix(core): handle phantom sidecar keys on vecq insert

Address cora PR review on #1099: if a crash happens between the `.keys`
sidecar write and the index file write, the reloaded sidecar can hold
keys >= index.len() (phantom keys with no physical row). Insert now
derives the vecq key from the physical row count and explicitly removes
any phantom key at that position (warn + drop its id mapping) before
claiming it — the phantom's row never existed, so nothing live can be
shadowed.

* fix(core): key allocation and usearch add in one cfg block

The previous refactor split key allocation and index.add() into separate
cfg blocks, and the add() path derived the key via next_key - 1 — wrong
whenever insert is called on a fresh index whose next_key counter is
still 0 (CI failure in test_forget_then_reinsert: duplicate key 0).
Allocation, map updates, capacity growth, and add() now live in a single
usearch block so the key variable is used directly.

* fix(core): validate vecq embedding dims before mutating key maps

Address cora PR review on #1099: the dimension check ran after the key
maps were updated, so a dimension-mismatch error left key_to_id holding
a key whose row was never added — corrupting len() accounting and
aliasing the next insert's key. The check now runs at the top of
insert(), before any state mutation.

---------

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

* fix(core): track deprecated_at for correct time-travel recall (#1086) (#1100)

* fix(core): track deprecated_at for correct time-travel recall (#1086)

- Schema v17: add deprecated_at column, backfill legacy deprecated rows
- Set deprecated_at on all deprecate paths, clear on restore
- recall_at_time: memory deprecated AFTER the point-in-time now appears
  (previously any deprecated flag excluded it); deprecated BEFORE is excluded
- memory_exists_at handler: same temporal predicate
- recall_inner(include_deprecated) so temporal path sees deprecated rows
- Regression test (no ONNX dependency)

* fix(server): align NULL deprecated_at semantics with core (#1086); drop stray embed_cache.db

* test(server): align at_time tests with deprecated_at semantics (#1086)

---------

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

* chore: remove embed_cache.db artifact and ignore it (#1101)

* chore: remove embed_cache.db artifact and ignore it

* test(core): make #1086 regression test embedder-free (pure predicate)

* refactor(core): extract shared memory_existed_at predicate for recall_at_time (#1086)

---------

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

* feat: per-pair dedup control via POST /consolidate/pair (#1076) (#1102)

* feat: per-pair dedup control via POST /consolidate/pair (#1076)

Add caller-chosen survivor consolidation: keep id_keep untouched,
deprecate (or hard-delete) id_remove. Reuses the same soft-delete +
reason + index cleanup path as bulk /consolidate, overriding the
hard-coded keep-newer rule when the reviewer picks the richer entry.

* fix(core): ephemeral index & embed cache for in-memory stores

rusqlite reports an empty string (not ":memory:") as the path of an
in-memory database, so store.path() leaked through the ephemeral guard:
every ':memory:' open wrote ./uteke_index.usearch and ./embed_cache.db
into the CWD. Concurrent tests then raced on one shared index file
(sidecar keys vs usearch keys) producing non-deterministic
'Duplicate keys not allowed' flakes.

Treat both '' and ':memory:' as ephemeral: no index file, no embed
cache file. Verified: 3x full workspace suite green with zero stray
files emitted.

---------

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

* fix(ci): keep v prefix in release notes download table + pinned install (#1043) (#1103)

* fix(ci): keep v prefix in release notes download table + pinned install (#1043)

The release job substitutes __VER__ in release-notes-template.md with
${VER#v} — the tag WITHOUT its v prefix — while the archived assets are
named with it (`<artifact>-v<semver>.tar.gz`). Copy-pasting a filename
or the pinned-install line from the notes therefore 404s.

- release.yml: substitute __VER__ with the full tag (v kept)
- template: drop the literal v from the 5 download-table rows (the tag
  now supplies it) so rendered names stay exactly as before
- ci.yml: new Release Notes Template job — renders the template with a
  placeholder semver and asserts the names equal the workflow's
  `<artifact>-v<ver>.<ext>` pattern, and that the pinned-install line
  carries the v-prefixed tag. Catches this class of regression before
  a tag is cut, not after.

Verified locally: rendered names diff clean against the actual
v0.15.0 release assets; the new CI step passes verbatim.

Reimplements the intent of #1046 (closed: wrong base branch, author
unresponsive) — thanks @VIVAAN-DHAWAN for the original report.

Closes #1043

* ci: restructure release-notes check to single-line printf

CodeCora flagged the multi-line quoted string in the new check: YAML
block scalars happen to strip the leading indentation today, but the
pattern is fragile — any re-indent silently changes the expected value.
Replace with printf + line continuations (indentation after a
continuation backslash is ignored by sh), same expected set.

Verified locally: extracted step verbatim from the YAML, passes.

---------

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

* feat(server): add --version/-V flag to uteke-serve (#1044) (#1104)

* feat(server): add --version/-V flag to uteke-serve (#1044)

uteke-serve had no way to report its version while the main CLI does.
Add a --version / -V arm to the arg parser printing
`uteke-serve <CARGO_PKG_VERSION>` (mirroring the CLI's format), list it
in --help, and cover it with integration tests via CARGO_BIN_EXE
(long flag, short flag, and unknown-flag still rejected with a usage
hint).

Reimplements the intent of #1045 (closed: wrong base branch, author
unresponsive) — thanks @VIVAAN-DHAWAN for the original patch.

Closes #1044

* test(core): merge racing ORT_LIB_PATH env tests into one sequential test

resolve_ort_lib_prefers_env_var and resolve_ort_lib_errors_on_missing_
env_var_path both mutated the process-global ORT_LIB_PATH in parallel
#[test]s. When the invalid-path test overwrote the var between the
valid test's set_var and resolve, resolve_ort_lib() returned Err and
the is_ok() assert failed — a timing-dependent CI flake (seen on this
branch's first CI run; passed locally, failed in CI).

Merged both cases into a single #[test] that runs them back-to-back,
eliminating the shared-state race. 5x targeted + 3x full workspace
suite green.

---------

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

* fix(cli): honor UTEKE_HOME as store path override (#1105) (#1107)

The CLI resolved its store from --store or uteke.toml only, ignoring
UTEKE_HOME — so an isolated-looking invocation silently opened the
default (often production) database. During v0.16.0 pre-release QA this
caused accidental v16+v17 schema migrations on a live DB.

Resolution order is now --store > UTEKE_HOME > uteke.toml > default,
via a shared resolve_store_path() used by both main and repair --rebuild
(the latter resolves index paths independently and is the most
destructive command to mis-target).

Verified empirically: UTEKE_HOME=<empty dir> uteke doctor now reports
DB=0 in that dir instead of the default store's 8045 memories.

Closes #1105

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

* fix: echo author_type in remember responses + CLI --author-type flag (#1106) (#1108)

POST /remember and uteke remember --json stored author_type correctly
but never echoed it, so clients couldn't confirm what was recorded.
The CLI didn't even expose the field (#1084 shipped HTTP-only).

- HTTP /remember response now includes author_type (default mirrors
  the schema default 'agent', #1083)
- CLI: new --author-type flag (human|agent), validated BEFORE any
  write so invalid values fail loudly without leaving a stored row;
  echoed in --json on both remember paths (contradiction + plain)
- run_via_server path forwards the flag in the HTTP body

Verified empirically in isolated stores: explicit human -> echo human
+ DB human; default -> echo agent + DB agent; invalid 'robot' -> loud
error / HTTP 400, zero rows written.

Closes #1106

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

* fix(core): repair() no longer evicts document chunk vectors from index (#1113)

* fix(core): repair() no longer evicts document chunk vectors from index

repair() rebuilt the vector index from load_all() which returns memories
only. Document chunk vectors ('chunk:<id>' keys) were silently dropped
from the index on every repair, degrading semantic doc search until the
document was re-upserted. doctor() reported consistency OK afterwards,
masking the eviction.

Add Store::load_all_chunk_embeddings() and include chunk vectors in the
rebuild (embeddings are already persisted in document_chunks, so no
re-embedding is needed). Regression test verifies semantic doc search
still finds chunks after repair.

Fixes #1110

* test: mark repair-chunks regression as requiring ONNX embedder

The test opens a real Uteke instance which eagerly initializes the ONNX
embedder; CI runners lack the ORT native library, so the test panicked
at open(). Marked #[ignore] with the same convention as the five
existing embedder-dependent tests; it still runs locally with the
release bundle via --ignored.

---------

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

* fix(build): vecq binaries now buildable via forwarded backend features (#1115)

uteke-cli, uteke-server, and uteke-mcp pulled uteke-core with default
features, so requesting vecq always unified with the default usearch
backend and hit the mutual-exclusion compile_error guard. The feature
was library-testable but unshippable as a binary, while CI stayed green
because the vecq-check job only ran 'cargo test --lib vector'.

Each downstream crate now declares 'usearch'/'vecq' features that
forward to uteke-core (default = usearch), and uteke-server forwards
its backend choice through its uteke-mcp dependency as well. The
resulting build matrix:

  cargo build -p uteke-cli            # usearch (default)
  cargo build -p uteke-cli --no-default-features --features vecq

CI vecq-check now also builds all three binaries with the vecq feature
so packaging breaks fail the build, not just library tests.

Fixes #1109

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

* fix(core): backend-aware index filename, doctor label, forget --confirm help (#1116)

* fix(core): backend-aware index filename, doctor label, forget --confirm help

The vector index filename was hardcoded to 'uteke_index.usearch' in
Uteke::open, the doctor check, and the CLI bench/repair commands, even
when the binary was built with the vecq backend. A vecq build wrote
VECQ-format bytes into a '.usearch'-named file, and opening a store
with the other backend could parse-fail and leave the index file
deleted after a re-save (observed during #1112 E2E).

The filename now derives from INDEX_EXT, so each backend owns its own
file: 'uteke_index.vecq' for vecq builds, 'uteke_index.usearch' for
usearch builds. Cross-backend opens simply miss the file and start
fresh instead of clobbering the other format.

Also:
- doctor check label is now 'vecq index'/'usearch index' per backend
- 'uteke forget --confirm' help text explains it is the non-interactive
  equivalent of the y/N prompt (docs item 4 of #1112)

Fixes #1112

* test: mark index-filename regression as requiring ONNX embedder

Uteke::open eagerly initializes the ONNX embedder; CI runners lack the
ORT native library, so the test panicked at open(). Same convention as
the existing embedder-dependent tests — runs locally via --ignored.

---------

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

* fix(core): verify/doctor count doc chunks on DB side, no false MISMATCH (#1114)

* fix(core): verify/doctor count doc chunks on DB side, no false MISMATCH

The vector index holds memories AND document chunk vectors
('chunk:<id>' keys), but verify()/doctor() compared index.len()
against a memories-only DB count. Any store with documents
reported MISMATCH and recommended repair — which (before #1110
fix) would evict the chunk vectors.

VerifyReport gains a chunk_count field (serde default, backward
compatible). Doctor and CLI verify output now show chunks
explicitly.

Fixes #1111

* test: mark verify-chunks regression as requiring ONNX embedder

Same as the repair-chunks regression: Uteke::open eagerly initializes
the ONNX embedder which CI runners lack. Runs locally via --ignored.

---------

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

* fix(cli): repair report compares against memories + chunks (#1117)

Follow-up to #1110/#1111: the CLI repair summary flagged a false
'Index count still differs from DB' warning for stores containing
documents, because it compared index_after (memories + chunk vectors)
against db_count (memories only). RepairReport now carries chunk_count
(serde default, backward-compatible) and the CLI compares against
db_count + chunk_count.

Caught during post-merge E2E retest: repair on a doc-only store kept
the chunk vector (search still found it) but printed a misleading
warning.

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

* test(longmemeval): fast-eval foundation — datasets + Modal harness + 500Q metrics (#1124)

* bench(longmemeval): 500Q hybrid run results + reranker + fast-eval harness (#1118)

- Final metrics 500Q hybrid: recall@5=0.909, recall@10=0.964, ndcg@5=0.875
- reranker.py: cross-encoder ONNX (ms-marco-MiniLM-L-6) + --rerank flag di run_eval
- modal_fast50/pull_fast: fast-eval infra (Volume terpisah uteke-lmeval-fast)
- dataset eval cepat: fast50/fast10/temporal15/multisession15 (stratified)
- fix: uteke binary fallback (.local/bin) utk host ini

* bench(longmemeval): remove reranker from foundation PR (#1118 cancelled)

The foundation PR keeps only reusable infra: 500Q metrics, fast-eval
datasets (via deterministic generator), Modal harness, compare tooling.
The cross-encoder reranker is dropped per the single-model decision;
temporal boost (#1119) lands as its own PR on top.

* chore: retrigger CI (CodeCora review failed with LLM API error)

---------

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

* test(longmemeval): temporal date-window boost (#1119) (#1126)

* bench(longmemeval): temporal date-window boost (#1119)

- temporal.py: temporal expression parser -> (lo,hi) window; patterns:
  last-N, N-days/weeks-ago (digits+words), last week/month, between X-Y,
  before/after month, in-month, this month/year
- boost_ranking(): additive RRF boost for in-window sessions
- run_eval.py: --temporal flag (default OFF) + --temporal-boost (0.0022)
- modal_fast50.py: bake temporal.py into image; LMEVAL_TEMPORAL=1 passthrough
- test_temporal.py: 21 unit tests (parser + boost math), all passing

* fix(longmemeval): propagate temporal flag to Modal workers + isolate volume cache

Two bugs in the A/B harness found before burning Modal credits:
- LMEVAL_TEMPORAL was read at module level inside the container, so the
  env var never reached the worker: a --temporal run would silently
  execute the baseline. The flag now travels through the shard spec.
- Shard cache keyed only by strategy: a temporal shard would resume
  from the stale 470Q baseline volume entry. Volume paths are now
  variant-keyed (hybrid vs hybrid_temporal).

* test(longmemeval): remove tautological assertions flagged by review

---------

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

* test(longmemeval): MMR diversity rerank flag + replay recording (#1120) (#1127)

* bench(longmemeval): MMR diversity rerank (#1120)

* fix(longmemeval): normalise MMR relevance scale + record session order in jsonl

* feat(longmemeval): mmr_lambda support in Modal harness for A/B evaluation

* fix(longmemeval): append mmr suffix to volume variant (preserve temporal key)

---------

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

* fix(longmemeval): dataset-tagged volume paths + qid-set cache validation (#1129) (#1130)

* fix(longmemeval): dataset-tagged volume paths + qid-set cache validation (#1129)

Volume cache keyed only by (variant, shard_idx) let a fast50 shard satisfy
a multisession15 run's length check and be returned verbatim - silent
cross-dataset stale hits. Now the dataset sha1[:8] is part of the volume
path, and a completed-shard cache hit additionally requires the qid set
to match exactly. list_volume globs across tag subdirs.

* fix(longmemeval): drop truncated tail lines from volume shard cache (#1130)

Review finding: a partially-written tail line (killed container) raised
JSONDecodeError and aborted the shard. Skip malformed lines instead; the
qid-set equality check downstream still guards completeness.

---------

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

* fix(core): make sha2 non-optional — no-default-features profile compiles again (#1131) (#1133)

* feat(bench): add RRF fusion mode (--fusion) to run_eval.py

Vector and hybrid strategies fail on DISJOINT question sets in fast50
offline simulation (7 questions vector-only wins, 5 hybrid wins, overlap
only in failures). Reciprocal Rank Fusion of both rankings captures
both: simulated R@5 0.9267 -> 0.9700, R@10 0.9700 -> 1.0000 on all 50
questions (weight plateau [1.3, 1.6], k=60).

Implementation: --fusion runs two recalls (primary strategy x1.5 +
complementary vector<->hybrid x1), RRF-fuses deduped session rankings
before temporal/MMR post-processing. Default OFF; no behavior change
without the flag.

Smoke test (hardest question gpt4_8279ba03, baseline R@5=0.00):
--strategy vector --fusion -> R@5=1.00, matches simulation exactly.

* feat(bench): --fusion flag passthrough in modal_fast50

run_shard spec gains 'fusion' key -> --fusion flag to run_eval.py, and
variant key gains '_fusion' suffix so fused shards never share volume
cache entries with plain strategy shards (#1129 pattern).

* fix(core): make sha2 non-optional — no-default-features profile compiles again (#1131)

The embedding cache (CachingEmbedder/EmbeddingCache) hashes keys with
SHA256 and is backend-agnostic — it wraps OpenAI/Ollama embedders too,
not just ONNX. But sha2 was optional behind the onnx feature while
embed/cache.rs imported it unconditionally, so every
--no-default-features build failed with E0432.

sha2 is tiny and pure-Rust; making it non-optional is the honest
dependency statement. Verified profiles: default (onnx+usearch),
onnx+vecq, vecq-only. CI vecq-check gains a no-onnx check step so the
profile stays green.

---------

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

* feat(core): Fusion as default recall strategy + benchmark tooling (v0.16.0) (#1135)

* docs(bench): tune fusion weight to vec×1.7 based on x86 rankings

x86 re-simulation using ACTUAL Modal rankings (results_modal_vector +
results_modal_hybrid_fresh_post1130) shows:
- vec×1.5: R@5=0.9600 (4 fails: gpt4_8279ba03, 60472f9c, 862b... etc)
- vec×1.7: R@5=0.9800 (3 fails, gpt4_8279ba03 fixed at rank≤5)
- plateau [1.7, 1.9] all → 0.9800

wv=1.7 chosen (mid-plateau, not edge). The default remains 1.5 in
run_eval.py; production runs pass --fusion-primary-weight 1.7.

* perf(bench): fusion default weight 1.5 -> 1.7 (x86 actual rankings)

Re-simulation on ACTUAL x86 Modal rankings (results_modal_vector +
results_modal_hybrid_fresh_post1130) instead of ARM-local rankings:
- wv=1.5: R@5=0.9600 (4 fails, incl. gpt4_8279ba03 at 0.0)
- wv=1.7: R@5=0.9800 (gpt4_8279boss rank pushed <=5)
- plateau [1.7, 1.9] -> 0.9800; 1.7 = mid-plateau

Also fixes stale help text (default: 1.7).

* fix(bench): fusion weight in volume cache key + --fusion-weight flag

The wv=1.7 confirm run silently returned the cached wv=1.5 shards:
variant key 'vector_fusion' was shared by both weights. Weight is now
part of the variant key (vector_fusion1.7 vs vector_fusion1.5), and
--fusion-weight passes the value through to run_eval so the cache key
matches what actually ran (#1129 pattern applied to fusion).

* feat(core): RecallStrategy::Fusion variant (parse/serde only)

Adds the Fusion strategy to the public enum with exact-match parsing
and kebab-case serde, plus roundtrip tests. Dispatch returns a loud
Validation error until the arm is wired in the follow-up commit —
never a silent fallback to another strategy.

Benchmark evidence (#1123): vector and hybrid fail on disjoint
question sets; weighted RRF fusion (vector x1.7 + hybrid x1, k=60)
reaches R@5 0.98 vs 0.9267 hybrid on LongMemEval fast50.

* refactor(core): extract compute_recall below the cache layer

The strategy match inside recall_hybrid moves verbatim into a private
compute_recall method. The dispatcher (cache check, cache put,
salience/recency boosts, truncation) is unchanged — pure refactor,
zero behavior change, full workspace tests green.

Fusion (#1123) will call compute_recall twice (Vector + Hybrid) and
fuse the rankings, bypassing the per-sub-call cache so the dispatcher
applies boosts exactly once.

* feat(core): Fusion strategy arm — vector×1.7 + hybrid×1 RRF (#1123)

Wires RecallStrategy::Fusion into compute_recall: runs the Vector and
Hybrid sub-strategies at boost_window depth and fuses their rankings
with the benchmark-tuned weights (vector x1.7, hybrid x1.0, k=60).
Sub-calls bypass the cache/boost layer; the dispatcher applies
salience/recency boosts exactly once and caches the fused set —
mirroring the harness semantics that measured fast50 R@5 0.98.

Includes rrf_fuse_weighted helper (unit-tested: overlap wins + dedup,
weight-driven ordering, empty inputs) and end-to-end integration
tests: on-topic memory ranks first, cold/warm cache parity, empty
store returns empty (no silent fallback, no error).

* feat(core)!: Fusion is the default recall strategy (#1123)

Moves #[default] from Hybrid to Fusion: every recall that does not
specify a strategy now uses the benchmark-proven weighted RRF fusion
(vector x1.7 + hybrid x1, k=60; fast50 R@5 0.98 vs 0.9267 hybrid).

Behavior change ONLY for implicit-default callers; explicit
strategy=default_strategy config is untouched. 0.x semver: minor
version bump (0.16.0) carries this change.

* feat(cli): default_strategy fusion + strategy list updated

- default_strategy: hybrid -> fusion (0.16.0, #1123)
- UTEKE_RECALL_STRATEGY env var accepts 'fusion'
- generated-config template and --help text list fusion
- default_recall_config test asserts fusion

Explicit [recall].default_strategy in user configs still wins via the
overlay merge — only the implicit default changes.

* feat(server): fusion default + strategy list in 400 message

Implicit fallback when a request omits strategy and uteke.toml has no
default_strategy: Hybrid -> Fusion (#1123, 0.16.0), matching the core
enum default and CLI default_strategy. The 400 message for invalid
strategy values now lists all five strategies. Request-level strategy
and config default_strategy semantics unchanged.

* feat(mcp): fusion default + schema enum

MCP recall tool schema gains 'fusion' (default since 0.16.0, #1123);
implicit fallback when the client omits strategy: Hybrid -> Fusion,
matching core/CLI/HTTP defaults. Invalid-strategy error lists all five
strategies. Explicit client strategy values are unchanged.

* feat(bench): 'fusion' as first-class strategy in harness

run_eval.py --strategy fusion exercises the IN-CORE default path
(uteke 0.16.0+ weighted RRF), distinct from the harness-level --fusion
flag which post-processes two CLI recalls. Enables the zero-config
validation run: --strategy fusion on a default-config binary.

* docs: fusion default + 0.16.0 changelog + version bump

- CHANGELOG 0.16.0: fusion strategy added, default hybrid -> fusion
  (implicit-default callers only; explicit config untouched)
- docs/configuration.md: default_strategy fusion + env var table
- docs/benchmarks.md: fusion pipeline + fast50 numbers
- benchmarks/longmemeval/RESULTS.md: fusion run row, disjoint-failset
  explanation, tuning evidence (plateau 1.7-1.9 on actual x86 rankings)
- workspace + crates 0.15.0 -> 0.16.0

* feat(bench): --strategy default sentinel for zero-config validation

'default' omits --strategy in the recall command so the binary's own
implicit default (0.16.0: fusion) is exercised — validates that a
fresh install with no config gets benchmark-grade recall out of the
box (#1123).

* docs(bench): zero-config validation results (local ARM, 0.16.0)

- Parity proven: implicit default == explicit fusion (identical top-10)
- Divergence from hybrid confirmed (default is not the old behavior)
- fast10 zero-config: R@5 0.9667, R@10 1.0, NDCG@5 0.9643
- 1 partial miss @5 (60472f9c) matches known hard question from
  the Modal x86 fusion run (#1123)

* test(longmemeval): 3-way RRF simulation — fts5 arm adds nothing (no-go)

- collect fts5-only rankings 500Q via modal fanout (fix: bake temporal.py
  + mmr.py into image)
- 3-way grid best = 2-way shipped (0 flipped questions at any weight)
- fts5-only 500Q baseline R@5=0.8211 (matches harness 0.820)
- ranking-side optimization exhausted; next headroom is insert granularity

* fix(ci): ignore ONNX-dependent fusion tests in CI + regen api docs

- fusion_recall_finds_on_topic_memory / fusion_recall_empty_store_returns_empty
  require the ONNX runtime lib which is unavailable on CI runners (same
  convention as the other embedder tests)
- docs/api-reference.md was stale (3 insertions) — regenerated via docgen

---------

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

* feat(bench): UTEKE_GIT_REF — build validation binary from exact SHA in Modal image (#1137)

- env UTEKE_GIT_REF=<sha>: image builds uteke-cli from the git tarball
  (repo public) instead of the tagged release; ORT 1.24.4 x86_64 shared
  lib reused from the official release tarball (ort load-dynamic)
- default behaviour unchanged (release binary) — backwards compatible
- purpose: pure-default 500Q validation needs 0.16.0 fusion-default
  binary before v0.16.0 is tagged

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

* fix(bench): install build-essential for git-ref image build (linker cc missing) (#1138)

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

* docs(bench): pure-default 500Q validation — fusion 0.946 R@5 (+9.2 vs 0.15.0 hybrid) (#1139)

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

* docs: fusion weights = internal hasil tuning, bukan bagian kontrak publik (#1140)

* docs(bench): pure-default 500Q validation — fusion 0.946 R@5 (+9.2 vs 0.15.0 hybrid)

* docs: describe fusion weights as benchmark-tuned internals

Remove explicit weight values from public-facing docs (RESULTS.md,
CHANGELOG, docs/benchmarks.md). The weights are implementation details
visible in the core crate; publishing the exact tuning recipe in prose
adds no user value.

---------

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

* docs(bench): LongMemEval-S 500Q results page — dual-metric + comparison chart (#1141)

* docs(bench): pure-default 500Q validation — fusion 0.946 R@5 (+9.2 vs 0.15.0 hybrid)

* docs: describe fusion weights as benchmark-tuned internals

Remove explicit weight values from public-facing docs (RESULTS.md,
CHANGELOG, docs/benchmarks.md). The weights are implementation details
visible in the core crate; publishing the exact tuning recipe in prose
adds no user value.

* docs(bench): LongMemEval-S 500Q results page — dual-metric (recall_any 98.2 / strict recall_all) + comparison chart

* docs(bench): add trademark disclaimer + internal-benchmark qualifier to chart footnote

* docs(bmad): PRD draft — benchmark-as-trust strategy from Round 10 discussion (4/4 agents)

* docs: move BMAD PRD out of repo — discussion artifact, stored in uteke doc instead

---------

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

* chore(release): CHANGELOG 0.16.0 — full-release validation numbers + public benchmark page (#1142)

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

* fix(core): repair --reembed can now repair NULL-embedding rows (#1146) (#1147)

reembed_missing() scanned via load_all(), whose 'embedding IS NOT NULL'
guard (#992, kept for index.build() safety) filtered out exactly the rows
needing repair — NULL-embedding write-path crash artifacts were invisible
to reembed and doctor reported an unresolvable MISMATCH.

Add load_missing_embeddings(): dedicated SQL scan
(WHERE embedding IS NULL OR length(embedding) = 0 AND deprecated = 0)
used as the reembed source. load_all() is unchanged.

Regression test (ignored, requires ONNX): NULL + empty-blob rows → scan
sees both → reembed repairs 2/2 → verify() consistent → reembed no-op.

Found on a production store with 36 NULL rows (2026-07-10..2026-08-13);
workaround was manual NULL→X'' SQL. With this fix the same store reaches
'All checks passed' via official commands alone.

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

* fix(ci): CLA check skips bot PRs with exact allowlist (#1132) (#1148)

Dependabot PRs are authored by 'dependabot[bot]', not 'app/dependabot',
so the exemption condition never matched: the CLA job ran, failed, and
left a 'please sign the CLA' comment on every dependabot PR (demonstrated
in vecq#2/#3, fixed there in vecq#14).

Use the exact allowlist from vecq#14, keeping the legacy app/ forms as
fallbacks. Exact-match only — no '[bot]' substring match, which would
exempt arbitrary third-party GitHub Apps from the CLA.

Note: other repos vendored this workflow (tole, skill-suites) — follow-up
PRs there once this lands.

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

* fix(cli): repair --reembed report reflects final index state (#1149) (#1150)

The Repair Report block printed the stale rebuild-time count: with NULL/
empty-embedding rows present, repair() legitimately rebuilds the index
without them, so index_after=0 and a misleading 'Index count still
differs' warning appeared right above a successful 2/2 re-embed summary.

Reorder run_repair: run reembed_missing() BEFORE printing, refresh
index_after from verify() so it counts newly appended vectors, and always
print the repair report even when reembed fails. Plain repair (no
--reembed) still warns honestly when rows remain unrepaired.

Verified E2E on an isolated store (1 NULL + 1 empty-blob row):
- repair --reembed: 'Index after: 2 vectors' + 'Index rebuilt
  successfully', no false warning
- plain repair: index_after=0 + warning kept (rows genuinely unrepaired)
- full suites: uteke-core 524 passed, uteke-cli 52 passed

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

* chore(ci): bump actions/github-script from v7 to v9 (#1151)

v7 runs on deprecated Node 20 (warnings visible in recent cla-check
runs). v9 is the current major. No script API changes needed — the
action's inputs and github.rest surface used here are unchanged.

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

* test(cli): resolve_namespace precedence — flag > env > config > default (#1078 P0) (#1152)

Six-case sequential test covering the full precedence chain. Mutation
proof: all 4 mutants of resolve_namespace caught (body-replace x2,
delete-! on empty-env guard, != -> == on config fallback), run with
cargo-mutants 27.1.0 scoped to main.rs.

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

* test(cli,core): env parsing guards + validate_input_with_limits boundary matrix (#1078 P0) (#1153)

- config.rs: 8 tests covering score range guards (out-of-range ignored),
  strategy validation (valid accepted / invalid keeps fusion default),
  graph weights (valid + out-of-range ignored), embed fallback env
  overrides (all fields + is_configured matrix: none/partial/full)
- lib.rs: validate_input_with_limits boundary matrix — empty content,
  at/over content limit, limit=0 disables check, tag count at/over,
  empty tag rejected, tag length at/over, limit=0 disables tag check

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

* test(core): ensure_embedder branch coverage — unknown/custom/openai-missing-key (#1078 P0) (#1154)

- unknown backend: rejected eagerly at open() with clear message
- custom backend without embedder: immediate Validation error
- openai backend without API key: deterministic init error, 2nd call
  hits the #822 transient cache (asserts 'previously failed (cached)')

All deterministic — no network calls.

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

* test(cli): cover TOML helpers — migrate_content, global_config_path, set_namespace_in_toml (#1078 P0) (#1155)

Targets the cargo-mutants survivors from the config.rs run (48% score):
- migrate_content: store_path/namespace → [store], model/max_seq_length →
  [embedding], unknown keys + section headers pass through
- global_config_path: honors UTEKE_HOME env override
- set_namespace_in_toml: rewrites only [store].namespace (not [other]),
  inserts directly after [store] header, appends section when missing,
  replaces existing value in place

78 mutants → 40 missed before this batch; these tests kill the
migrate_content arm-deletes, the != / && section-boundary mutants, and
the insert-position mutants.

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

* docs: refresh documentation for 0.16.0 — fusion default, updated benchmarks, bundled skill bump (#1156)

* docs: refresh README (EN+ID) for 0.16.0 — fusion default, 500Q benchmarks, new features

* docs: align strategy/version references with 0.16.0 defaults (fusion, install pins)

* docs(roadmap): record v0.15.0 and v0.16.0 as released

* docs: correct 500Q results-availability claim (aggregates in repo, raw on volume)

* chore(skill): bump bundled uteke-memory-skill to 0.16.0 (fusion default, lifecycle, onboard)

---------

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

* chore: sync main back into develop after #1158 docs carry (#1159)

* feat: add full memory detail fields to UnifiedSearchResult

UnifiedSearchResult now includes memory_type, namespace, source,
source_type, importance, pinned, access_count, last_accessed,
created_at, updated_at.

- Memory results: all fields populated from Memory struct
- Document results: all memory-specific fields are None (skip_serializing_if)
- Tests verify both memory fields present and document fields omitted
- CLI print_unified_human shows new fields (type, source, importance, pinned, access count)

Fixes the gap where recall with search_type=all/memory/doc returned
less metadata than plain recall.

* fix: remove O(n) reverse scan in get_related() + fix recall_room() missing columns

A) get_related() in graph.rs was doing load_all(None) to find reverse
   metadata relationships — O(n) full table scan. The edge_bfs() method
   already handles bidirectional lookups via UNION on source_id/target_id
   using indexed SQL. Removed the full-table scan. Forward-only metadata
   fallback kept for pre-v8 stores (O(degree), not O(n)).

B) recall_room() SQL in memory/rooms.rs was missing m.slug, m.source,
   m.source_type columns (only selected up to content_type=col16).
   row_to_memory() expects 20 columns (0-19). Fixed all 4 SQL variants.

Refs #689

* feat: room↔document junction table (schema v15, #689)

New room_documents table with room_id + doc_slug composite PK.
Enables explicit room-to-document linking beyond memory wikilinks.

Store methods:
- room_add_document(room_id, doc_slug)
- room_remove_document(room_id, doc_slug)
- room_list_documents(room_id) → Vec<slug>
- document_list_rooms(doc_slug) → Vec<room_id>

Server endpoints:
- PUT /room/document/add
- DELETE /room/document/remove
- POST /room/document/list
- POST /doc/room/list

Refs #689

* fix: use public Uteke wrappers instead of private store field

- Add room_add_document, room_remove_document, room_list_documents,
  document_list_rooms to Uteke public API (rooms.rs)
- Replace uteke.store.xxx with uteke.xxx in server handlers
- Add missing ns binding in /tags GET handler
- Apply cargo fmt

* feat: memory↔document cross-entity linking via [[doc-slug]] wi…
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.

1 participant