Quick Start¶
In one Rust program you will take an empty database to an answered question: observe a short conversation, then ask "What pet does Alice have?" and get the answer back from compiled knowledge. The recall path never calls an LLM — the only model call here is the one that phrases the final answer. This is the same flow the benchmark harness runs.
The path is three moves:
- Build a
Unikoinstance — the single owning handle to your memory. - Open a
Sessionandobservea fewTurns — a tiny conversation. - Ask
agent.answer(...)— it recalls the relevant context and synthesizes an answer.
uniko is a Rust library
There is no server to launch and no command to run. You add the crates to your
own binary and call the methods below. Everything here is async; the examples
assume a Tokio runtime.
The shape of the thing¶
A Uniko instance owns everything — the underlying uni-db graph, the
model runtime (embeddings, reranker, generation), and the ingest pipeline. From it you mint an
Agent (an identity that can be scoped), and from the agent a Session (a conversation thread).
Writes go through session.observe(...); reads — recall, answer, query — go through the agent.
flowchart LR
T[Turn] -->|observe| S[Session]
S --> KB[(Uniko memory)]
Q[Question] -->|answer| A[Agent]
A -->|recall + LLM| ANS[Answer]
What success looks like¶
When you run this, observing each turn extracts entities and observations and commits before returning — so the next read sees it immediately (read-after-write). The query prints a one-line answer — "Alice has a rescue greyhound named Biscuit" — the count of recalled items that grounded it, and the sources each item traces back to.
A complete example¶
use uniko_memory::{LlmSpec, Turn, Uniko};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1. Build an instance. `in_memory()` is ephemeral; use `.path("./data/kb")`
// to persist. `.llm(...)` enables `answer()` — recall itself needs no LLM.
let memory = Uniko::builder()
.in_memory()
.llm(LlmSpec::openai("llm/default", "gpt-4o-mini", None))
.build()
.await?;
// 2. An agent is an identity; a session is a conversation thread.
let agent = memory.agent("assistant");
let mut session = agent.session("session-1");
// 3. Observe a tiny conversation. `observe` commits before returning, so a
// following read sees it. Session/Participant nodes are created on first sight.
session
.observe(Turn::new("alice", "I just adopted a rescue greyhound named Biscuit."))
.await?;
session
.observe(Turn::new("bob", "Nice! How is Biscuit settling in?"))
.await?;
session
.observe(Turn::new("alice", "Great — she already sleeps on the sofa every afternoon."))
.await?;
// 4. Ask. `answer` runs the recall cascade, hands the ranked context to the
// configured LLM, and returns the answer plus the context that grounded it.
let answer = agent.answer("What pet does Alice have?").await?;
println!("answer: {}", answer.text);
println!("grounded on {} items", answer.context.items.len());
for source in answer.citations() {
println!(" source: {source:?}");
}
// 5. Drain workers and close the store cleanly.
drop(session);
memory.shutdown().await?;
Ok(())
}
Step by step¶
Build the instance¶
let memory = Uniko::builder()
.in_memory()
.llm(LlmSpec::openai("llm/default", "gpt-4o-mini", None))
.build()
.await?;
Uniko::builder() configures capabilities, then build() warms the models so the first query is
fast. in_memory() is ephemeral; swap in .path("./data/kb") for a durable, file-backed store —
same builder, same handle. For the zero-config defaults (the benchmark-validated stack), just
Uniko::in_memory().await? or Uniko::open("./data/kb").await?.
.llm(...) registers a generation model so agent.answer(...) can synthesize — recall and
retrieval need no LLM. LlmSpec::openai(alias, model_id, base_url) reads OPENAI_API_KEY from the
environment; there's also LlmSpec::openai_with_key_env(...) and LlmSpec::mistralrs(...) for a
local model.
Configuration lives on the builder
Embedders, NLP, and the reranker are set with .embedding(EmbeddingConfig::...) and friends, or
drop in a fully-tuned UnikoConfig with .raw_config(cfg). See
Configuration.
Observe the conversation¶
let agent = memory.agent("assistant");
let mut session = agent.session("session-1");
session.observe(Turn::new("alice", "I adopted a rescue greyhound named Biscuit.")).await?;
observe runs the full ingest pipeline — chunking, entity extraction, observation extraction — and
commits before returning, so the next recall/answer sees the turn. The Session and
Participant are created on first sight from the ids you pass; you don't pre-register them.
A Turn is a builder: Turn::new(sender, content) plus optional .id(...) (idempotency),
.at(timestamp), .addressed_to(vec![...]), .metadata(k, v), and .attach(IngestSource::...) to
ride a document along with the message. To load a standalone document (no conversation), use
session.ingest(IngestSource::path("handbook.md")).await?.
Throughput vs. read-after-write
observe is durable and synchronous. For high-volume ingest where you don't need the result
immediately, build with .streaming(true) and use session.submit(turn) (fire-and-forget) then
session.flush().await?.
Ask a question¶
agent.answer(...) returns an Answer: answer.text is the synthesized
reply, answer.context is the ranked recall bundle that grounded it (items, coverage,
total_tokens), and answer.citations() lists the messages/attachments the answer drew on. The
model and token usage ride along (answer.model, answer.output_tokens).
Want just the context, no LLM? Call agent.recall("the deadline").await? — it returns the same
ContextBundle for you to format into your own prompt. Each RecallItem carries its kind
(Chunk/Fact/Observation/…) and sources (the turns/documents it came from). See
Recall & Retrieval.
Shut down¶
shutdown consumes the instance and drains the workers within the configured timeout. Drop any
outstanding Agent/Session handles first so it has sole ownership.
Where to go next¶
Recall & Retrieval¶
recall/answer, item kind + sources provenance, and dereferencing them with agent.data().
Agent Tools¶
Goals and tasks (agent.goals()), plus the episode/action/fact recording primitives.