API Reference¶
uniko is a Rust library, so its API is the set of crates you link and the methods you call — there
is no service to wire up. The public surface is the Uniko facade: one owning handle from which
you mint agents, open sessions, observe turns, and read memory back. Every signature below is
reproduced from the source.
One crate matters for callers:
uniko-api— the public facade. It contains no logic; itstoolsmodule re-exports the curated surface (Uniko,Agent,Session, the view/result types). Depend on this crate and you get the whole reachable surface. The same types are re-exported fromuniko-memory, which is where they're defined.
The lower-level engine (KnowledgeBase, PipelineSystem, the recall/answer_query free
functions) is still available for embedded/advanced use — see Engine internals.
Note
Parameter structs use ..Default::default() heavily — most fields are optional and documented
inline in the crate's rustdoc. Only the load-bearing shape is shown here.
Uniko — the instance¶
Uniko owns the store, the model runtime, and the ingest pipeline. Build one, mint agents from it,
and shutdown when done.
| Method | Signature | Description |
|---|---|---|
open |
async fn open(path: impl AsRef<Path>) -> Result<Self, UnikoError> |
Persistent, zero-config defaults. |
in_memory |
async fn in_memory() -> Result<Self, UnikoError> |
Ephemeral, zero-config defaults. |
builder |
fn builder() -> UnikoBuilder |
Configure capabilities, then build(). |
agent |
fn agent(&self, agent_id: impl Into<String>) -> Agent |
Mint an agent identity (cheap). |
config |
fn config(&self) -> &UnikoConfig |
The resolved configuration. |
purge |
async fn purge(&self) -> Result<DeletionReport, UnikoError> |
Wipe all data (dev/test). |
shutdown |
async fn shutdown(self) -> Result<(), UnikoError> |
Drain workers and close. Needs sole ownership — drop agents/sessions first. |
UnikoBuilder (chainable): .path(p) / .in_memory() (location) · .embedding(EmbeddingConfig)
· .llm(LlmSpec) (enables answer()) · .raw_config(UnikoConfig) (full override) ·
.streaming(bool) (enables submit/flush) · .scope_to_agent() / .scope(RecallScope)
(read visibility) · .extractor(Arc<dyn ModalityExtractor>) (pluggable modality) · .build().
LlmSpec constructors: openai(alias, model_id, base_url: Option<&str>) (reads
OPENAI_API_KEY) · openai_with_key_env(alias, model_id, base_url, key_env) ·
mistralrs(alias, model_id) (local model).
Agent¶
An Agent binds an identity to the store. Reads can be scoped to what that agent may see.
let agent = memory.agent("assistant");
let bundle = agent.recall("what does the user prefer?").await?;
let answer = agent.answer("when is the deadline?").await?; // needs .llm(...)
| Method | Signature | Description |
|---|---|---|
recall |
async fn recall(&self, query: &str) -> Result<ContextBundle, UnikoError> |
Ranked, prompt-ready context. |
recall_in |
async fn recall_in(&self, query: &str, scope: Scope) -> Result<ContextBundle, UnikoError> |
Recall confined to a session/participant/time scope. |
answer |
async fn answer(&self, question: &str) -> Result<Answer, UnikoError> |
Recall + the configured LLM. |
answer_in |
async fn answer_in(&self, question: &str, scope: Scope) -> Result<Answer, UnikoError> |
Scoped answer. |
query |
async fn query(&self, cypher: &str) -> Result<Vec<Record>, UnikoError> |
Read-only Cypher against the graph. |
query_in |
async fn query_in(&self, cypher: &str, scope: &Scope) -> Result<Vec<Record>, UnikoError> |
Binds the scope's allow-set as $allow. |
session |
fn session(&self, session_id: impl Into<String>) -> Session |
Open a conversation thread. |
data |
fn data(&self) -> Data<'_> |
Addressed retrieval — see Data. |
goals |
fn goals(&self) -> Goals<'_> |
Goal/task lifecycle — see Goals. |
delete_session |
async fn delete_session(&self, session_id: &str) -> Result<DeletionReport, UnikoError> |
Delete a whole conversation. |
forget_participant |
async fn forget_participant(&self, participant_id: &str) -> Result<DeletionReport, UnikoError> |
GDPR erasure of a person's data. |
agent_id |
fn agent_id(&self) -> &str |
The bound participant id. |
Logic surface: define_rule(name, source) → NodeId · run_rule(name, &cols, params) →
Vec<Record> · assume(block) -> AssumeBuilder (what-if, rolled back) ·
abduce(program, params) -> AbductionResult. See Reasoning with Locy.
Session & Turn¶
A Session is a conversation thread. observe is durable (commits before returning); the streaming
verbs need .streaming(true).
let mut session = agent.session("chat-42");
let result = session
.observe(Turn::new("alice", "here's the spec").attach(IngestSource::path("spec.pdf")))
.await?;
session.ingest(IngestSource::path("handbook.md")).await?; // standalone document
| Method | Signature | Description |
|---|---|---|
observe |
async fn observe(&mut self, turn: Turn) -> Result<ObserveResult, UnikoError> |
Ingest a conversational turn (+ attachments); read-after-write. |
submit |
async fn submit(&self, turn: Turn) -> Result<(), UnikoError> |
Streaming fire-and-forget turn. |
submit_source |
async fn submit_source(&self, source: IngestSource) -> Result<(), UnikoError> |
Streaming blob. |
flush |
async fn flush(&self) -> Result<(), UnikoError> |
Barrier: drain the streaming pipeline. |
ingest |
async fn ingest(&self, source: IngestSource) -> Result<IngestOutcome, UnikoError> |
Load a standalone document. |
summarize |
async fn summarize(&self) -> Result<Option<NodeId>, UnikoError> |
Build/refresh the session Summary. |
forget_turn |
async fn forget_turn(&self, message_id: &str) -> Result<DeletionReport, UnikoError> |
Soft-redact (hidden from recall, lineage kept). |
delete_turn |
async fn delete_turn(&self, message_id: &str) -> Result<DeletionReport, UnikoError> |
Hard-delete + cascade. |
delete_document |
async fn delete_document(&self, artifact_id: &str) -> Result<DeletionReport, UnikoError> |
Delete an ingested document. |
Turn (builder): Turn::new(sender, content) · .id(message_id) (idempotency) ·
.content_type(s) · .at(DateTime<Utc>) · .addressed_to(Vec<String>) ·
.metadata(k, serde_json::Value) · .attach(IngestSource) · .attachments(iter).
ObserveResult { message: AtomicIngestResult, attachments: Vec<IngestOutcome> } — each
attachment's IngestOutcome::Artifact(_).artifact_id is the handle for later retrieval.
Recall results¶
recall/recall_in return a ContextBundle of ranked items, each carrying what it is (kind)
and where it came from (sources).
for item in &bundle.items {
println!("[{:?}] {}", item.kind, item.content);
for src in &item.sources { /* RecallSource::Message | Attachment | Document */ }
}
| Type | Shape | Description |
|---|---|---|
ContextBundle |
{ items: Vec<RecallItem>, total_tokens, coverage, phase1_only, phase2_only } |
Ranked result set + accounting. |
RecallItem |
{ node_id: NodeId, kind: RecallKind, score: f64, content: String, sources: Vec<RecallSource> } |
One recalled node. primary_source() returns the first. |
RecallKind |
Chunk · Observation · Fact · Procedure · Topic · Episode · Message · Other |
What it is. is_derived() ⇒ true for Fact/Procedure/Topic/Observation; tier() ⇒ its RecallTier. |
RecallSource |
Message { message_id, chunk_id? } · Attachment { message_id, artifact_id, chunk_id? } · Document { artifact_id, chunk_id? } |
Lineage — 1 for a chunk, many for a Fact. |
RecallTier |
Semantic · Procedural · Episodic · KnowledgeBase · Provenance |
Scoring weight band. |
Scoping (Scope) — chainable, passed to the _in variants: .sessions([...]) ·
.participants([...]) · .since(DateTime) · .until(DateTime) · .categories([...]) ·
.sources([...]) · .as_viewer(Viewer).
Dimensions holds the resolved filters; ViewerScope is Unrestricted | As(Viewer).
Typed provenance. Turn::category(..) / Turn::source(..) and
IngestSource::with_category(..) / .with_source(..) record a record class and a
stable logical source without putting either in searchable prose. A :Source node
carries the logical identity (FROM_SOURCE from the Message/Artifact); category and
source_id are denormalised onto Message, Artifact, Chunk, Observation and Fact so
.categories(..) / .sources(..) filter with a property predicate. Every
RecallItem reports its own category and source_id, with None meaning "no
category" / "no traceable single source" rather than "filtered out".
Revisions and retirement. IngestSource::with_revision(..) / Turn::revision(..)
declare which revision of a source the bytes are. A new revision stamps
superseded_at on the previous one and records SUPERSEDES from new to old;
Agent::retire_source(..) stamps Source.retired_at (restore_source reverses it).
Ordinary recall excludes superseded revisions and retired sources, so replaced or
retired evidence cannot ground a current answer; Scope::include_superseded() reaches
it, which is how a past result stays attributable to the revision behind it. Nothing is
deleted, and retirement lives on the source rather than the content — retiring one
source never affects another that merely shares identical bytes.
Re-ingesting the same revision with identical bytes is a no-op; the same revision with
changed bytes raises IdConflict, since a revision id is a promise about the content.
Revisions do not change existing results on their own
The exclusion set is resolved before candidate generation, and when nothing has been retired or superseded it is empty and no predicate is emitted. Results shrink only once something has actually been retired or replaced.
Filters apply before ranking
.categories(..) and .sources(..) are resolved into the allow-set that
candidate generation itself uses, so the result limit and the coverage score
describe the permitted evidence — not candidates that ranked well and were
discarded afterwards. A filter with no eligible matches returns an empty result;
it is never padded out with other categories. Episode carries no provenance, so
a provenance filter excludes it, matching the existing rule that a node which
cannot anchor a dimension goes dark under a filter on it.
Warning
Unscoped reads default to ViewerScope::Unrestricted — recall does not filter
Fact/Observation visibility unless you scope to a viewer (recall_in(.., Scope::default().as_viewer(v))
or build with .scope_to_agent()).
Answer¶
agent.answer(...) returns an Answer — the generated text plus the grounding context, citable.
let ans = agent.answer("when is the deadline?").await?;
println!("{}", ans.text);
for src in ans.citations() { /* deduped grounding sources */ }
| Field / method | Type | Description |
|---|---|---|
text |
String |
The synthesized answer. |
context |
ContextBundle |
The recall bundle that grounded it (items carry kind + sources). |
model / input_tokens / output_tokens |
Option<…> |
Generation telemetry. |
recorded_episode |
Option<NodeId> |
Set when query-episode recording was opted in and succeeded. |
citations() |
fn citations(&self) -> Vec<&RecallSource> |
Deduped grounding sources, in bundle order. |
Note
citations() reports the grounding context (what the model was shown), not model-attributed
citations — the model isn't asked which items it used.
Data — retrieval¶
agent.data() dereferences the ids that recall hands back.
let view = agent.data().artifact("spec.pdf").await?; // Option<ArtifactView>
let bytes = agent.data().artifact_bytes("spec.pdf").await?; // Option<Vec<u8>>
let msg = agent.data().message("m-12").await?; // Option<MessageView>
| Method | Signature | Description |
|---|---|---|
message |
async fn message(&self, message_id: &str) -> Result<Option<MessageView>, UnikoError> |
A turn: sender, content, session, recipients, attachments. |
artifact |
async fn artifact(&self, artifact_id: &str) -> Result<Option<ArtifactView>, UnikoError> |
Metadata + reassembled text + the message it was attached to. |
artifact_bytes |
async fn artifact_bytes(&self, artifact_id: &str) -> Result<Option<Vec<u8>>, UnikoError> |
The original blob. |
MessageView { message_id, sender_id, content, timestamp, session_id, addressed_to: Vec<String>,
attachments: Vec<String> } · ArtifactView { artifact_id, kind, mime: Option<String>,
path: Option<String>, text: String, attached_to_message: Option<String> }.
Goals & tasks¶
agent.goals() is the goal/task lifecycle surface — create, transition, read sliced by phase, and
expand a goal's working context.
let g = agent.goals().create(CreateGoalParams { title: "Ship the release".into(), ..Default::default() }).await?;
agent.goals().complete("goal-1", Some(serde_json::json!({ "shipped": true }))).await?;
let active = agent.goals().active().await?; // Vec<GoalView>
let ctx = agent.goals().context("goal-1").await?; // Option<GoalContext>
| Group | Methods |
|---|---|
| Read | all() · in_phase(GoalPhase) · active() · planned() · completed() · get(id) · tasks() · tasks_in(TaskPhase) · tasks_of(goal_id) · context(goal_id) |
Transition (→ Result<bool>, false = unknown id) |
start(id) · complete(id, Option<Json>) · abandon(id) · set_status(id, &str) · start_task · complete_task · block_task · set_task_status |
| Create | create(CreateGoalParams) -> NodeId · create_task(CreateTaskParams) -> NodeId |
GoalView { goal_id, title, description?, status, phase: GoalPhase, created_at?, deadline?,
completed_at?, metrics: Option<Json> } · TaskView { task_id, title, description?, status,
phase: TaskPhase, priority?, goal_id?, created_at?, completed_at? } · GoalContext
{ goal: GoalView, tasks: Vec<TaskView>, sessions, recent_messages, facts, entities }.
GoalPhase Planned · Active · Completed · Abandoned · TaskPhase Planned · Active ·
Completed · Blocked — derived from the free-form status (+ completed_at); the raw status stays
exposed.
Participant must exist
create/create_task require the agent's Participant to exist — observe() creates it on
first sight. A pure-planning agent should observe at least once, or seed the participant.
Ingest sources¶
A single blob type drives both Turn::attach and Session::ingest.
| Constructor | Description |
|---|---|
IngestSource::text(s) / ::bytes(b) / ::path(p) |
Build from text, bytes, or a file. |
.with_mime(m) / .with_id(id) / .with_path(p) |
Override MIME, set the artifact id, set a display path. |
IngestOutcome is Artifact(ArtifactIngestResult) · Pdf(PdfIngestResult); the
result types carry artifact_id + node ids. Unsupported modalities return Err(UnikoError::Unsupported(...)). MIME is resolved explicit → magic-bytes → extension →
text, then routed (text/markdown/code/json → artifact, PDF → tiered extractor, image/audio/video →
a registered ModalityExtractor).
Recording primitives (low-level)¶
Beyond agent.goals(), the subjective-state free functions are available at the uniko_memory
crate root for fine-grained recording. Each takes (&kb, agent_id, params) (except assert_fact /
invalidate_fact, which take no agent_id) and the Participant must already exist.
| Function | Returns | Description |
|---|---|---|
create_goal / create_task |
NodeId |
The primitives behind agent.goals().create*. |
assert_fact |
FactUpsert |
Upsert a (subject, predicate, object) Fact. |
invalidate_fact |
() |
Close a Fact's bitemporal validity interval. |
add_observation |
NodeId |
An explicit Observation anchored to a Message. |
record_episode |
NodeId |
An Episode (action + outcome + state). |
record_action |
RecordActionResult |
An Action node; large output overflows to an Artifact. |
See Agent Tools for the parameter structs and edge wiring.
Access-control policy¶
Facts/Observations carry a visibility (public / private:{id} / team:{id} / org:{id}).
Scoped reads filter automatically; you can also apply it directly.
| Symbol | Signature | Description |
|---|---|---|
Viewer::new |
async fn new(kb, participant_id) -> Result<Self, UnikoError> |
Resolve team/org memberships from the graph. |
Viewer::from_parts |
fn from_parts(participant_id, teams, orgs) -> Self |
Build from already-resolved memberships. |
visibility_admits |
fn visibility_admits(visibility: Option<&str>, viewer: &Viewer) -> bool |
Whether a tag admits the viewer. Unknown schemes fail closed. |
Errors & ids¶
| Re-export | Use |
|---|---|
UnikoError |
The crate-wide error type every method returns. |
NodeId |
Internal node id (i64) returned by create/ingest. |
DeletionReport |
What a delete/forget changed. |
Value, Record |
Graph value type and a query result row (query/run_rule). |
FinalizeReport |
What Session::finalize built or refreshed. |
temporal_epoch_millis |
Epoch milliseconds for a datetime-shaped temporal Value — the supported way to read a BTIC/timestamp column out of a query row. |
Engine internals¶
Uniko wraps the low-level engine, which remains public for embedded/advanced use. These are the
mechanisms the facade exists to hide — reach for them only when you need to.
| Symbol | Where | Wrapped by |
|---|---|---|
KnowledgeBase (uniko_store) |
the graph + model runtime handle | Uniko |
PipelineSystem (uniko_memory) |
ingest/consolidation workers, channels, circuit breaker | Uniko (streaming) |
recall(&kb, q, &RecallConfig) |
the 3-phase cascade free function | Agent::recall |
answer_query(&kb, q, &cfg, generator, record) |
recall + a generator closure (returns GeneratedAnswer) → Answer |
Agent::answer |
ingest_message_atomic(&kb, &msg, &mut ctx) |
the single-transaction ingest path | Session::observe |
See Pipelines for the engine's ingest/consolidation/recall internals.
See also¶
- Recall & Retrieval —
recall/answer, provenance, anddata(). - Agent Tools — goals/tasks and the recording primitives.
- Architecture — how the layers fit together.
- Schema — the node and edge catalog.