| description | Embed the open-source TinyCortex Rust crate and build a working store-and-recall loop in minutes. |
|---|
TinyCortex is the open-source Rust core of the TinyCortex memory system, published on crates.io as the tinycortex crate. It is a library, not a hosted service: you embed it in your own agent, service, or app. This page gets you from an empty project to a working store-and-recall loop, then points at where the full engine lives.
{% hint style="info" %} The hosted TinyCortex platform (managed API, turnkey "conscious recall" product, per-user cost figures) is a separate closed-alpha offering. Everything on this page is the open-source crate. {% endhint %}
TinyCortex targets Rust 2021. Add it to an existing project:
cargo add tinycortexThe end-to-end runnable surface today — InMemoryMemoryStore plus the MemoryStore trait — is async, so you also need an async runtime to drive it. The crate itself does not pull in a runtime; the examples here use Tokio (the same runtime the crate's own dev-tests use):
cargo add tokio --features macros,rt-multi-thread
cargo add anyhow # only for the `?` ergonomics in the example belowStore a memory and recall it with a keyword query, using the built-in in-process backend:
use tinycortex::memory::{InMemoryMemoryStore, MemoryInput, MemoryQuery, MemoryStore};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Volatile, BTreeMap-backed reference store. Cheaply cloneable; contents are
// lost on drop.
let store = InMemoryMemoryStore::new();
// Insert a memory into the "preferences" namespace. `insert` mints the id and
// timestamps and returns the persisted record.
let record = store
.insert(MemoryInput::new("preferences", "User prefers dark mode"))
.await?;
println!("stored {} in namespace {}", record.id, record.namespace);
// Recall by keyword. `search` returns scored hits, most relevant first.
let hits = store.search(MemoryQuery::text("dark mode")).await?;
for hit in hits {
println!("{:.3} {}", hit.score, hit.record.content);
}
Ok(())
}Run it:
cargo run| Step | Type | Notes |
|---|---|---|
MemoryInput::new(ns, content) |
MemoryInput |
Caller-supplied, untrusted, not yet persisted. Carries a namespace, raw content, and free-form metadata (empty by default). |
store.insert(input) |
-> MemoryResult<MemoryRecord> |
Trims content and rejects empty/whitespace-only input with MemoryError::EmptyContent. Mints a v4 UUID (MemoryId) and stamps created_at/updated_at. |
MemoryQuery::text(t) |
MemoryQuery |
Convenience constructor; sets text, leaves namespace and limit unconstrained. |
store.search(query) |
-> MemoryResult<Vec<SearchHit>> |
Returns SearchHit { record, score }, sorted by score then recency. |
Every storage backend satisfies the MemoryStore trait (src/memory/store/store.rs). It is Send + Sync so a single store can be shared across async tasks:
#[async_trait]
pub trait MemoryStore: Send + Sync {
async fn insert(&self, input: MemoryInput) -> MemoryResult<MemoryRecord>;
async fn get(&self, id: MemoryId) -> MemoryResult<MemoryRecord>;
async fn delete(&self, id: MemoryId) -> MemoryResult<MemoryRecord>;
async fn search(&self, query: MemoryQuery) -> MemoryResult<Vec<SearchHit>>;
}
These live in src/memory/store/types.rs and are the data contracts that flow through any store:
| Type | Role | Key fields |
|---|---|---|
MemoryInput |
Untrusted, caller-supplied item | namespace: String, content: String, metadata: Map<String, Value> |
MemoryRecord |
Persisted item with identity | id: MemoryId, namespace, content, metadata, created_at, updated_at |
MemoryQuery |
Conjunctive filter (all fields optional, None = unconstrained) |
namespace: Option<String>, text: Option<String>, limit: Option<usize> |
SearchHit |
A retrieval result | record: MemoryRecord, score: f32 (higher is more relevant; scale is store-defined) |
MemoryId |
Stable identity | type alias for uuid::Uuid (v4) |
MemoryError |
Failure modes | NotFound(MemoryId), EmptyContent |
MemoryResult<T> |
Result alias | Result<T, MemoryError> |
MemoryQuery::text is the convenience path; you can also build the struct to scope a namespace and a limit:
let query = MemoryQuery {
namespace: Some("project".to_owned()),
text: Some("durable".to_owned()),
limit: Some(10),
};
let hits = store.search(query).await?;InMemoryMemoryStore is the simplest conforming backend, intended for tests and getting started, not durable production use. Concretely:
- Volatile. Records live in an
Arc<RwLock<BTreeMap<MemoryId, MemoryRecord>>>; contents are lost on drop. The store is cheaply cloneable and shareable across tasks. - Naive keyword search.
searchlower-cases the query text, keeps records whosecontentcontains the full needle, and scores by the count of matching whitespace-delimited terms (minimum1.0). With notext, every record in scope scores1.0. - Conjunctive namespace filter. A
namespaceconstraint must match exactly;Nonesearches all namespaces. - Default limit of 20. When
MemoryQuery::limitisNone,searchreturns at most 20 hits. Results are sorted by score, ties broken bycreated_at(newer first).
What it deliberately does not do: no durability, no markdown vault, no chunking, scoring, decay, embeddings, summary trees, graph, or explainable score breakdowns. Those are the full engine.
The whole public surface sits under the memory module (src/memory/). The reference store above is one corner of it; the layered engine — content store, ingest, scoring/extraction, summary trees, retrieval, diff ledger, entities/graph, goals/tool-memory, conversations/archivist, and the async job queue — is organized into focused submodules:
| Area | Module path | Wiki page |
|---|---|---|
| Minimal store + reference backend | src/memory/store/ |
this page |
| Markdown vault, SQLite chunks, vectors, KV, entity index | storage primitives | Storage Primitives |
| Canonicalize → chunk → score → embed → tree | ingest | Ingest Pipeline |
| Value scoring + extraction | scoring | Scoring and Extraction |
| Append → seal → summarise hierarchy | src/memory/tree/ |
Summary Trees |
| Vector / keyword / graph / tree / hybrid search | retrieval | Retrieval |
| Git-backed snapshots and read-markers | diff | Diff Layer |
| Entity files + co-occurrence graph | entities | Entities and Graph |
| Goals + tool-scoped rules | goals / tool_memory | Goals and Tool Memory |
| Transcripts → summary-tree leaves | conversations / archivist | Conversations and Archivist |
| Async jobs (extract, append, seal, flush-stale, re-embed) | queue | Job Queue |
The crate root (src/lib.rs) re-exports just pub mod memory; everything public is reached through it. See the Architecture Overview for how the layers fit together and the Core Concepts for invariants like local-first markdown authority, rebuildable derived indexes, provenance, and the security taint.
Clone the canonical repo and use the standard Cargo workflow:
git clone https://github.com/tinyhumansai/tinycortex
cd tinycortex
cargo check # fast type-check without running tests
cargo test # unit tests (in src/) + integration tests (in tests/)
cargo fmt --all # format the crate
cargo doc --open # build and open the API referenceThe smoke test in tests/smoke.rs is the smallest end-to-end check — it inserts a record and asserts a keyword search finds it:
let store = InMemoryMemoryStore::new();
store
.insert(MemoryInput::new("default", "TinyCortex starts as a Rust memory core"))
.await
.expect("insert memory");
let hits = store
.search(MemoryQuery::text("Rust memory"))
.await
.expect("search memory");
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].record.namespace, "default");Heavier dependencies are bundled, so a from-source build is self-contained: rusqlite is pulled in with the bundled feature (no system SQLite needed), and git2 backs the diff ledger when the optional git-diff feature is enabled. tempfile is a dev-only dependency used by the tests. Tokio appears twice: as a dev-dependency for the test suite, and as a real optional dependency behind the crate's tokio feature, which powers the async job-queue worker loops in memory::queue::runtime. With the default (empty) feature set, none of these optional dependencies are compiled.
- Architecture Overview — how the layers fit together
- Core Concepts — invariants, provenance, and the security taint
- Storage Primitives — the markdown vault and derived indexes
- Building and Contributing — full dev workflow and conventions