This file is permanent context for AI agents working in this repository. Read it fully before you start coding.
Uteke is a local-first semantic memory engine for AI agents. Single Rust binary, fully offline, ~30ms recall. No API key, Docker, or cloud service needed.
- Repo:
codecoradev/uteke(remote GitHub), local clone - Version: 0.7.3
- License: Apache 2.0
- Main branches:
develop(default branch, all PRs go here),main(release mirror)
| Crate | Path | Purpose |
|---|---|---|
uteke-core |
crates/uteke-core/ |
Library β storage, embedding, vector search, FTS5, operations |
uteke-cli |
crates/uteke-cli/ |
CLI binary β clap commands, JSON output, server proxy |
uteke-server |
crates/uteke-server/ |
HTTP server β persistent daemon for fast agent access |
uteke-mcp |
crates/uteke-mcp/ |
MCP server β JSON-RPC for AI tool integration |
crates/uteke-core/src/
βββ lib.rs # Uteke struct β main public API
βββ operations.rs # High-level ops (remember, recall, search, forget, etc.)
βββ error.rs # Error enum, sanitized messages
βββ consolidate.rs # Memory consolidation (cosine dedup)
βββ maintenance.rs # Doctor, verify, repair
βββ import_export.rs # JSONL import/export
βββ embed/
β βββ mod.rs # Embedder struct β ONNX inference
β βββ engine.rs # Engine trait + ONNX implementation
βββ memory/
βββ mod.rs # Module re-exports
βββ store.rs # Store struct β SQLite operations
βββ vector.rs # VectorIndex β usearch HNSW + RwLock
βββ fts5.rs # FTS5 full-text search + RRF fusion
βββ schema.rs # Schema versioning + migrations
βββ crud.rs # CRUD operations (insert, get, update, delete)
βββ types.rs # Type definitions (Memory, RecallResult, RecallStrategy, etc.)
βββ tags.rs # Tag operations (json_each queries)
βββ aging.rs # Aging tier operations
βββ bulk.rs # Bulk delete operations
βββ vector.rs # Vector index management
crates/uteke-cli/src/
βββ main.rs # Entry point (logging, config, dispatch)
βββ cli.rs # Clap structs + enums (Cli, Commands, etc.)
βββ config.rs # Config file loading (uteke.toml)
βββ logging.rs # Console + daily file appender setup
βββ output.rs # Print helpers (human + JSON formatters)
βββ init.rs # Agent init (pi, claude, cursor, copilot, codex)
βββ bench.rs # Benchmark helpers
βββ assets/shell/ # Shell hook scripts (include_str! at compile time)
β βββ uteke-hook.bash
β βββ uteke-hook.zsh
β βββ uteke-hook.fish
βββ commands/
βββ mod.rs # Command dispatch
βββ remember.rs # --entity, --category, --meta flags
βββ recall.rs # --entity, --category filter, hybrid search
βββ list.rs # --entity, --category filter
βββ server.rs # HTTP proxy to uteke-serve
βββ ... # Other per-command modules
crates/uteke-server/src/
βββ main.rs # Actix-web server
| Component | Technology | Details |
|---|---|---|
| Vector Index | usearch v2.25.3 | Persistent HNSW, RwLock for concurrent reads |
| Full-Text Search | SQLite FTS5 | Virtual table, phrase + token-OR fallback |
| Hybrid Search | RRF (k=60) | Reciprocal Rank Fusion merges vector + FTS5 results |
| Storage | SQLite (rusqlite) | WAL mode, schema v5 |
| Embedding | EmbeddingGemma Q4 ONNX | 768d vectors, Mutex (ONNX tokenizer needs &mut self) |
| CLI | clap v4 | Standard Rust CLI |
| Server | actix-web | CORS enabled, ~42ms warm recall |
| MCP | JSON-RPC over stdin/stdout | 5 tools: remember, recall, list, forget, stats |
| Embedder Trait | Box<dyn Embedder> |
Pluggable: ONNX (default), future: OpenAI, Ollama |
schema_versiontable with integer counter- Current: v12 (document engine + knowledge graph + timeline + citations + hierarchy)
- Auto-migration on upgrade, zero data loss
CI runs cargo fmt --check and will fail if there are formatting issues. A single space or newline mistake is enough to fail the PR.
# ALWAYS run before commit
cargo fmtCora CLI has found real bugs in this project (BM25 score always 0, RRF normalization wrong, metadata missing in server mode). Don't wait for CI.
cora review --base origin/develop --format textIf Cora finds error-level issues, fix first before pushing.
CI runs cargo clippy -- -D warnings. All warnings are treated as errors.
cargo clippy --workspace --all-targets -- -D warningsfeature branch β PR β develop (default branch)
β
PR develop β main (saat rilis)
β
tag vX.Y.Z dari main commit
β
release workflow auto-publish
Rules:
developis the default branch. All development work goes here via PR.mainis a release mirror. It only receives updates via PR from develop.- Never push directly to main. Never PR directly to main (except developβmain).
- Before tagging: merge develop β main via PR FIRST. Then tag from main.
- Tag must point to a commit on main, not develop.
- Release workflow has
verify-mainjob that aborts if tag is not on main.
- PR required (no direct push) β enforce_admins: true
allow_force_pushes: falserequired_linear_history: trueallow_deletions: false- Required checks: Build, Check, Clippy, Format, Test
- All changes via PR β no direct push
- Required checks: Build, Check, Clippy, Format, Test, Cora Review, Cargo Audit, Trivy FS Scan
Prerequisite: All milestone issues closed, docs updated.
When creating a release branch (release/vX.Y.Z), these changes MUST be in the branch:
- Version bump β
Cargo.toml[workspace.package].versionβ new version - Inter-crate version pins β every
crates/*/Cargo.tomlthat references workspace crates must match the new version - CHANGELOG.md β move entries from
[Unreleased]to[X.Y.Z] β YYYY-MM-DD, add empty[Unreleased] - AGENT.md β update version string + any stale references (schema version, etc.)
- README.md / README.id.md β version badge if applicable
# 1. Create release branch from develop with version bump + CHANGELOG + AGENT.md
# 2. PR release/vX.Y.Z β develop, merge
# 3. PR develop β main, merge
# 4. Tag from main commit:
git checkout main
git pull origin main
git tag vX.Y.Z -m "vX.Y.Z β Description"
git push origin vX.Y.Z
# 5. Release workflow auto-builds: binaries, crates.io, Docker, GitHub ReleaseNever tag without merging develop β main first.
The verify-main CI job will abort the release if the tag is not on main.
Use .expect("clear message") or if let / match patterns. .unwrap() without context makes debugging impossible if it panics.
// β Don't
memories.remove(0).unwrap()
// β
Correct
memories.remove(0).expect("guaranteed by prior count check")
// β
Better
if let Some(memory) = memories.into_iter().next() { ... }For all file I/O that writes important data (index, config, model), use this pattern:
// Write to .tmp first, then rename (atomic on POSIX)
let tmp_path = path.with_extension("tmp");
fs::write(&tmp_path, &data)?;
fs::rename(&tmp_path, &path)?;remember(): Write to SQLite first, then vector indexforget(): Acquire index lock first, then SQLite delete- This pattern prevents inconsistency between DB and index
Every commit that adds/changes a feature or fix must include updates to:
- CHANGELOG.md β Add entry under
[Unreleased](Added/Fixed/Changed) - docs/cli-reference.md β If there's a new CLI flag or behavior change
- docs/roadmap.md β If an issue is completed
- README.md β If there are significant changes to features or quick start
- AGENT.md β If there are architecture changes, new limitations, or lessons learned
- Version badge β If releasing, update badge in README
Outdated documentation is more dangerous than no documentation.
The deploy-website.yml workflow deploys to Cloudflare Pages when main is updated.
Docs deploy from main, not develop. Make sure docs are updated before merging develop β main.
Never ignore a failing CI check with excuses like "it's an external app" or "not a required check." Every red CI check must be investigated.
Real experience: PR #274 had a CodeCora failure that was ignored. It turned out Cora Review (a separate check) found 2 critical bugs:
- RRF scores β cosine similarity β
min_scorefilter was targeting the wrong thing - Server ignores
[recall]config β CLI vs server behavior differed
If a CI check fails:
- Read the error/log β don't immediately say "it's safe"
- Check if the finding is valid β trace to the related code
- If valid β fix first, don't merge
- If false positive β document why, don't stay silent
- Don't merge while there's red β unless 100% sure it's noise
Principle: Red CI = there's a problem. Investigate first, don't assume.
Never push a v* tag without explicit approval. See section #5 for full release process.
Before tagging:
- All PRs for the version must be merged to develop
- Documentation MUST be updated first β no exceptions:
README.md+README.id.mdβ badge version, new features listCHANGELOG.mdβ new[X.Y.Z]section with all changesdocs/cli-reference.mdβ new commands, flags, API endpointsdocs/configuration.mdβ new config sections, env varsdocs/architecture.mdβ new subsystems, schema changesdocs/roadmap.mdβ milestone section with closed issuesdocs/integrations/hermes.mdβ plugin changes if applicable
- MCP tools MUST be in sync with API server endpoints β no exceptions:
- Every API endpoint must have a corresponding MCP tool
crates/uteke-mcp/src/lib.rstool list must match server routes- Run
cargo test --workspaceto verify both compile and pass
- Version bumped in
Cargo.toml+ inter-crate deps +plugin.yaml - develop β main merged via PR
cargo publish --dry-runpasses locally- Get approval from project owner
- Tag from main commit, push
The cora hook install pre-commit hook runs on every git commit. Do NOT bypass with --no-verify unless the Cora API is down. The hook catches real issues before they reach CI.
# Pre-commit hook is auto-installed at .git/hooks/pre-commit
# Verify it works:
cora review --staged --format compactCombining two ranking systems is tricky:
- Vector cosine similarity: range 0..1
- BM25 (FTS5): negative unbounded (can be -5, -10, etc.)
- Don't clamp BM25 to 0..1 directly β it destroys ranking
- Use RRF (Reciprocal Rank Fusion) β rank-based, doesn't care about original scale
- If you need to normalize to 0..1, use sigmoid:
1.0 / (1.0 + (-score).exp())
RwLockfor read-heavy workloads (vector index: recall/search far more frequent than remember/forget)Mutexwhen the operation needs&mut self(ONNX tokenizer)- Don't blindly swap Mutex β RwLock. Profile first.
The smallest bug in score calculation can break a feature entirely:
.min(1.0)vs.clamp(0.0, 1.0)β huge difference when negative values exist- Always write unit tests that verify score ranges
Server Mode = Hidden Surface Area
When adding new parameters to the CLI, don't forget to update server mode too. Bug #264: --entity, --category, --meta were added to CLI but forgotten in the server endpoint.
Checklist when adding a new CLI flag:
- Command module (
commands/remember.rs) - Server endpoint (
commands/server.rsβ proxy body) - Server handler (
uteke-server/src/main.rs) - API docs
- CLI reference docs
Every public function in lib.rs MUST have a corresponding CLI command AND HTTP endpoint. No orphan features.
| lib.rs function | CLI command | HTTP endpoint | Status |
|---|---|---|---|
doc_upsert / doc_upsert_with_parent |
uteke doc create [--parent] |
POST /doc/create |
β |
doc_get |
uteke doc get |
POST /doc/get |
β |
doc_list / doc_list_roots / doc_list_children |
uteke doc list [--tree] [--roots-only] |
POST /doc/list |
β |
doc_search (semantic/fts/hybrid) |
uteke doc search [--mode] |
POST /doc/search |
β |
doc_move |
uteke doc move [--parent] |
POST /doc/move |
β |
doc_delete |
uteke doc delete |
DELETE /doc/delete |
β |
doc_breadcrumbs |
(via --tree display) | (not exposed) | |
doc_list_descendants |
(via --tree display) | (not exposed) | |
doc_export |
uteke doc export |
(not exposed) |
Notes:
doc_breadcrumbsanddoc_list_descendantsare used internally by CLI--treebut don't need standalone API endpoints yet.doc_exportis CLI-only (bulk export, not needed for real-time API).
Entity, category, and meta are stored as JSON in the metadata column. This means:
- Filtering is done in Rust, not SQL WHERE clause
- No index on individual fields inside JSON
- For large datasets (>10K), consider separate columns
Unit tests (108) don't cover:
- Bulk insert of 100+ memories (performance regression?)
- Concurrent access via server mode
- Unicode / special characters in content
- Schema migration from old DB version
- Crash recovery (kill during write)
Run manual stress tests after significant changes.
CONTRIBUTING.md once said "2 crates" when it had been 3 since v0.0.4. Version badge was behind. Always update docs before commit β see Critical Rule #8.
v0.0.14 release failed 3 times because:
- Intra-workspace dependencies need
versionfield βuteke-clidepended onuteke-corewith onlypath, but crates.io requiresversion = "x.y.z" include_str!paths must be within the crate root β shell hooks were inscripts/shell/(repo root), outsidecrates/uteke-cli/.cargo publishonly packages files within the crate directory.- Windows build failure blocked GitHub Release β release job depended on both
build-releaseANDpublish-crates, so one platform failure blocked everything.
Fix: Run cargo publish --dry-run --allow-dirty locally before tagging. Decouple publish from release.
v0.6.4 tag was pushed without bumping workspace.package.version from 0.6.3 β 0.6.4. cargo publish rejected it: "crate uteke-core@0.6.3 already exists on crates.io index". The release workflow reported success (misleading) but no crate was actually published.
Rule: Before pushing any v* tag, verify ALL these are updated:
Cargo.toml[workspace.package].versionβ new version- Every inter-crate dependency version matches (e.g.
uteke-clidepends onuteke-core = "0.6.x") CHANGELOG.mddate set to release dateAGENT.mdversion string updated- Run
cargo publish --dry-run --allow-dirty -p <crate>to verify publish works
Deleting and re-pushing a tag triggers a new release workflow but leaves stale state on crates.io (partial publish). Always verify locally with --dry-run before pushing any tag.
Cora pre-commit hook (cora hook install) catches issues before they reach CI. Found real bugs like log directory not created before appender init, and false-positive SQL injection flags on CLI struct definitions.
ONNX model load takes ~2.5s. Wrapping embedder in Mutex<Option<EmbeddingEngine>> and lazy-loading on first embed() call makes all non-embedding commands (list, stats, tags, get, forget, namespace, aging, export, doctor, verify) start in ~20ms instead of ~3s. The key insight: not every command needs every expensive resource.
1. git checkout develop && git pull
2. git checkout -b <type>/<short-description>
3. Implementation (read related modules first)
4. cargo fmt && cargo clippy && cargo test
5. cora review --base origin/develop --format text (local review)
6. Fix all Cora findings
7. Update CHANGELOG.md (add under [Unreleased])
8. Update docs/ if there are new features/flags (see Critical Rule #8)
9. git add -A && git commit -m "type: description"
10. git push origin <branch>
11. gh pr create --base develop
12. Monitor CI (gh pr checks <number>)
13. Review PR comments (Cora, CodeRabbit)
14. Fix if there are new findings
15. gh pr merge <number> --squash --delete-branch
16. Pick next issue
feat/<new-feature>
fix/<bug-being-fixed>
docs/<what-was-updated>
refactor/<what-was-refactored>
type: description (#issue-number)
type: feat, fix, docs, refactor, test, chore
Examples:
feat: add FTS5 hybrid search with RRF (#250)
fix: BM25 score always returning 0.0
docs: update CLI reference for metadata flags
| Limitation | Status | Details |
|---|---|---|
usearch ef parameter cannot be set |
External | usearch v2.25.3 Rust bindings don't expose ef in search() |
Embedder requires Mutex |
Architectural | ONNX tokenizer internally uses &mut self |
| Metadata filtering is post-filter | Design | Entity/category/meta in JSON blob, not SQL column |
| Consolidate is O(nΒ²) | Algorithm | Pairwise cosine, slow at >1000 memories |
| FTS5-only mode score placeholder | Design | BM25 can't normalize to 0..1, actual ranking via RRF |
# Build
cargo build --workspace
# Test (295 unit tests)
cargo test --workspace
# Format + Lint
cargo fmt && cargo clippy --workspace --all-targets -- -D warnings
# Local Cora review
cora review --base origin/develop --format text
# Create PR
gh pr create --base develop --title "type: description" --body 'summary'
# Check CI
gh pr checks <number>
# Merge
gh pr merge <number> --squash --delete-branch