Pipelines Overview¶
uniko keeps your agent responsive while its knowledge compounds in the background. Writing a
Message returns in milliseconds; the expensive intelligent work — deriving Facts, promoting
Procedures, naming Topics — runs on its own cadence and never blocks the turn the agent is in the
middle of. Every pipeline runs in-process: there is no separate service to deploy and no queue to
operate.
uniko splits that work into three movements, each with a different cost profile and a different sense of urgency:
- Atomic ingest — synchronous, single-transaction storage of a
Message, its entities, and its structural edges. Returns in milliseconds. - Async post-ingest — observation extraction and embedding spawned off the ingest path, plus a worker-driven consolidation cycle (and a cortex sweep: procedure promotion, topic detection, decay, session maintenance) triggered by a threshold or a timer.
- Three-phase recall — a coverage-gated cascade that reads the compiled knowledge first and only broadens to raw content when it has to.
flowchart TB
msg([Message / Artifact])
subgraph sync["Atomic ingest — synchronous, one transaction"]
direction LR
P1["P1 Ingest<br/>store node + edges<br/>< 10ms"]
P2["P2 NER<br/>entities + MENTIONS<br/>< 100ms"]
P1 --> P2
end
subgraph spawned["Spawned off the ingest path — async"]
direction LR
P3["P3 Observations<br/>extract statements<br/>< 5s"]
P7a["P7a Auto-embed<br/>Message embedding"]
end
subgraph worker["Consolidation worker — threshold OR timer"]
direction TB
P4["P4 Consolidation<br/>Observations → Facts<br/>reinforce / invalidate / drift"]
sweep["Cortex sweep (gated)<br/>P5 Procedure promotion<br/>P6 Topic detection<br/>F50 decay · session maintenance"]
P4 --> sweep
end
subgraph recall["Three-phase recall — coverage-gated cascade"]
direction TB
R1["Phase 1 Compact<br/>Facts · Procedures · Topics"]
R2["Phase 2 Expand<br/>Episodes · Observations · Sessions"]
R3["Phase 3 Broaden<br/>Chunks · Messages · graph"]
R1 -->|coverage gate| R2 -->|coverage gate| R3
end
msg --> P1
P2 -->|"ingest call returns"| P3
P2 --> P7a
P3 -->|ObservationsReady| P4
recall -.reads compiled knowledge.-> worker
uniko is an embedded Rust library
Every pipeline runs in-process inside your application. The orchestration
layer is PipelineSystem in
uniko-memory. There is no separate service to deploy — workers are Tokio
tasks spawned at construction time and torn down on shutdown.
What runs when¶
The data flow spans eight numbered pipeline stages. What matters operationally is who drives each one — the calling thread, a spawned task, or a background worker reacting to a trigger.
| Stage | Driver | Timing |
|---|---|---|
P1 Ingest — store Message/Artifact, chunk, create edges |
Synchronous (caller's task) | < 10ms (message), < 100ms (artifact) |
P2 NER — extract Entity nodes + MENTIONS edges |
Synchronous (caller's task) | < 100ms |
| P3 Observations — extract factual statements | Spawned async task | < 5s |
P7a Auto-embed — embed the Message |
Spawned async task | continuous |
P4 Consolidation — derive Facts, reinforce, invalidate, detect drift |
Consolidation worker | background, threshold/timer |
P5 Procedure promotion — recurring sequences → Procedures |
Consolidation worker (cortex sweep) | background, post-consolidation |
P6 Topic detection — entity co-occurrence → Topics |
Consolidation worker (cortex sweep) | background, post-consolidation |
The ingest call returns after P2 completes. P3 and P7a are spawned as independent tasks — observation extraction and embedding finish in the background and notify the consolidation worker that new Observations exist. That split is what keeps your agent responsive: the caller gets a fast, durable write-confirmation while the expensive extraction work compounds knowledge off the hot path.
Compile once, query forever
Consolidation compiles raw Messages and Observations into Facts and Procedures once. The recall cascade queries that compiled knowledge first, so extraction is paid once at write time and amortized across every read — never re-derived per query.
Atomic ingest¶
P1 and P2 run as ordered steps inside the IngestWorker, in a single async
task per item, with no queue between them. P1 creates the Message (or
Artifact) node and its structural edges; P2 attaches the extracted entities.
Because there is no queue between the steps, a message is either fully stored
with its entities or not — there is no half-ingested state visible to a query.
Each item flows through a chain of Steps. Steps declare their own error policy
via StepErrorPolicy, so a failure is contained to a single item and a single
step:
Skip— log and continue to the next step (e.g. NER failing still leaves a stored Message).DeadLetter— persist aDeadLetternode for later retry, then continue.Abort— stop the remaining steps for this one item only.
Concurrency is bounded by a Semaphore (default 8), and the worker pulls from a
bounded channel (capacity 200), so backpressure propagates to the caller rather
than growing an unbounded queue.
→ Read more in Ingest.
Async post-ingest: consolidation and the cortex sweep¶
When P3 finishes extracting Observations, it sends an ObservationsReady
notification to the ConsolidationWorker. The worker keeps a per-agent
counter and fires a consolidation cycle on a threshold-OR-timer rule:
- 20 new Observations accumulate for an agent (
consolidation_threshold), or - the periodic timer ticks (
consolidation_interval_secs, default 900s / 15 min) with any pending Observations,
whichever comes first. A ForceConsolidate task triggers a cycle immediately.
A consolidation cycle (P4) derives Facts from unprocessed Observations,
reinforces or invalidates existing Facts, detects drift, and records a
ConsolidationCycle audit node. After a successful cycle, the worker runs
the cortex sweep when both gates allow, governed by two independent throttles:
- a per-agent cycle counter (
cortex_cycle_every_n_consolidations, default 4), and - a per-sweep wall-clock minimum (
cortex_min_interval_secs, default 600s).
When both gates allow, the sweep runs P5 procedure promotion (per agent), P6
topic detection (global), F50 memory decay (prune age-decayed Episodes), and
session maintenance (auto-close inactive Sessions and summarise them).
Background reasoning is isolated
Procedure promotion, topic detection, decay, and session maintenance run after consolidation succeeds. A failure in any of them is logged and isolated — it never breaks consolidation or the write path. Background reasoning is enrichment, not a critical path.
→ Read more in Consolidation.
Three-phase recall¶
recall() searches across every memory layer but tries to do the least work
that still answers the query. It runs a coverage-gated cascade:
Searches the compiled tiers: Fact, Procedure, Topic. This is the
cheapest path. If coverage clears the Phase 1 gate (default 0.75), the
cascade can stop here.
Episodic recall: Episode, Observation, Session, plus fulltext over
Message and Observation content, with MMR deduplication. Gated at a
lower coverage threshold (default 0.65).
Raw content and graph traversal: Chunk and Message search plus
entity-link traversal. Phase 3 always completes — there is no early exit.
Recall sharpens as you ingest
Until consolidation has produced Facts, Procedures, and Topics, queries route through
Phase 3 over raw content. As you ingest, the system shifts toward compiled-knowledge
retrieval: phase1_only_pct rises and recall gets cheaper and sharper the more the
knowledge base learns. This learned-index effect is structural and intentional.
The coverage score blends facet coverage, mean result score, and tier diversity:
A drift override forces Phase 2+ execution even when Phase 1 coverage is sufficient, whenever the query references an Entity that consolidation has flagged unstable (F39), via the F58 drift override — so questions about unstable entities always re-check recent episodic evidence.
→ Read more in Recall.
Orchestration: the PipelineSystem¶
PipelineSystem owns the bounded channels, the LLM circuit breaker, the
dead-letter queue, and the worker join handles. It is created once and spawns
both workers immediately:
use std::sync::Arc;
use uniko_pipes::PipelineConfig;
use uniko_store::{KnowledgeBase, config::UnikoConfig};
use uniko_memory::pipeline::PipelineSystem;
let kb = Arc::new(KnowledgeBase::in_memory(UnikoConfig::default()).await?);
let ps = PipelineSystem::new(PipelineConfig::default(), kb, vec![]);
// ... submit ingest / consolidation tasks ...
ps.shutdown().await?;
Both workers run a tokio::select! loop with biased priority ordering:
shutdown first, then queued tasks. The consolidation worker adds a periodic
timer arm at the lowest priority; the ingest worker has no timer arm (only
shutdown + rx.recv()). This is what guarantees an interactive recall() is
never starved behind a consolidation cycle. Submitting a task is non-blocking:
Health is observable per worker, including the circuit-breaker state:
let health = ps.health();
println!("ingest queue depth: {}", health.ingest.queue_depth);
println!("llm circuit: {:?}", health.llm_circuit);
Offline operation
NER, observation extraction, consolidation, and recall all function without an LLM. When the LLM provider fails, the circuit breaker opens and LLM-dependent steps degrade to local fallbacks (rule-based NER, rule-based observation extraction); the breaker probes and recovers automatically.
Dive deeper¶
Ingest¶
Atomic single-transaction storage — P1 store, P2 NER, spawned P3/P7a, step error policies, and the IngestWorker.
Consolidation¶
Threshold-or-timer cycles that compile Observations into Facts, plus the gated cortex sweep: procedures, topics, decay, sessions.