pub struct Client { /* private fields */ }Expand description
High-level facade composing the embedder, store, and vector index.
Constructed via Client::builder. Cheap to clone — internally backed by
Arc so multiple call sites can share one configured Memoir instance.
Implementations§
Source§impl Client
impl Client
Sourcepub async fn new(
database_url: String,
qdrant: String,
schema: Option<String>,
system_prompt: Option<String>,
collection: Option<String>,
extraction_llm: Option<LlmConfig>,
contradiction_llm: Option<LlmConfig>,
categorize_model: Option<NliConfig>,
) -> Result<Client, ClientError>
pub async fn new( database_url: String, qdrant: String, schema: Option<String>, system_prompt: Option<String>, collection: Option<String>, extraction_llm: Option<LlmConfig>, contradiction_llm: Option<LlmConfig>, categorize_model: Option<NliConfig>, ) -> Result<Client, ClientError>
Builds a Client from Postgres and Qdrant connection strings.
memoir-core owns its own connection pool. The pool’s search_path is
pinned to the configured schema so memoir-core’s tables and
migration ledger never collide with the consumer’s other Postgres
state. The consumer never sees a sea_orm::DatabaseConnection — this is a
deliberate boundary so the library can manage its own connection
lifecycle (search_path, pool sizing, future read-replica routing)
without each consumer reinventing the same plumbing.
LLM providers are configured per LlmRole via the extraction_llm
and contradiction_llm setters. A role left unconfigured produces a
registry with no entry for that role; downstream call sites
(e.g. the extraction worker stage) skip gracefully when their
preferred role is absent.
§Examples
use memoir_core::client::Client;
use memoir_core::llm::LlmConfig;
let client = Client::builder()
.database_url("postgres://postgres:postgres@localhost:54321/my_app")
.qdrant("http://localhost:6334")
.schema("memoir")
.extraction_llm(LlmConfig::ollama("http://localhost:11434", "llama3.2"))
.build()
.await?;§Errors
Returns ClientError::Database when the pool cannot connect,
ClientError::Embedding if the embedder fails to initialize,
ClientError::Vector if ensure_collection fails on Qdrant, and
ClientError::Llm if a configured provider can’t be constructed
(e.g. malformed URL or api-key rejected by rig).
Sourcepub fn builder() -> ClientBuilder
pub fn builder() -> ClientBuilder
Builds a Client from Postgres and Qdrant connection strings.
memoir-core owns its own connection pool. The pool’s search_path is
pinned to the configured schema so memoir-core’s tables and
migration ledger never collide with the consumer’s other Postgres
state. The consumer never sees a sea_orm::DatabaseConnection — this is a
deliberate boundary so the library can manage its own connection
lifecycle (search_path, pool sizing, future read-replica routing)
without each consumer reinventing the same plumbing.
LLM providers are configured per LlmRole via the extraction_llm
and contradiction_llm setters. A role left unconfigured produces a
registry with no entry for that role; downstream call sites
(e.g. the extraction worker stage) skip gracefully when their
preferred role is absent.
§Examples
use memoir_core::client::Client;
use memoir_core::llm::LlmConfig;
let client = Client::builder()
.database_url("postgres://postgres:postgres@localhost:54321/my_app")
.qdrant("http://localhost:6334")
.schema("memoir")
.extraction_llm(LlmConfig::ollama("http://localhost:11434", "llama3.2"))
.build()
.await?;§Errors
Returns ClientError::Database when the pool cannot connect,
ClientError::Embedding if the embedder fails to initialize,
ClientError::Vector if ensure_collection fails on Qdrant, and
ClientError::Llm if a configured provider can’t be constructed
(e.g. malformed URL or api-key rejected by rig).
Source§impl Client
impl Client
Sourcepub async fn migrate(&self) -> Result<(), ClientError>
pub async fn migrate(&self) -> Result<(), ClientError>
Applies memoir-core’s migrations in the configured Postgres schema.
Idempotent — safe to call on every startup. Creates the schema if missing and applies all pending migrations.
§Errors
Returns ClientError::Migration if the schema name is invalid or
if any migration fails to apply.
Sourcepub fn system_prompt(&self) -> Option<&str>
pub fn system_prompt(&self) -> Option<&str>
Returns the configured system-prompt section, if any.
Sourcepub fn collection_name(&self) -> &str
pub fn collection_name(&self) -> &str
Returns the Qdrant collection name configured for vector storage.
Sourcepub fn embedder(&self) -> &OnnxEmbedding
pub fn embedder(&self) -> &OnnxEmbedding
Returns the embedding model used by this client.
Sourcepub fn store(&self) -> &PostgresStore
pub fn store(&self) -> &PostgresStore
Returns the source-of-truth store used by this client.
Sourcepub fn index(&self) -> &QdrantIndex
pub fn index(&self) -> &QdrantIndex
Returns the vector index used by this client.
Sourcepub fn llms(&self) -> &LlmRegistry
pub fn llms(&self) -> &LlmRegistry
Returns the registry of LLM providers configured on this client.
Sourcepub fn remember(
&self,
prompt: impl Into<String>,
scope: Scope,
) -> RememberBuilder<'_>
pub fn remember( &self, prompt: impl Into<String>, scope: Scope, ) -> RememberBuilder<'_>
Writes prompt as an episodic memory under scope.
Returns a per-call builder; await it to persist the row and enqueue
its embed (and, if extraction is configured, extract) job. The
returned Memory reflects the source-of-truth row; its vector
index entry is pending until the worker drains the embed job.
Use Client::search to retrieve memories — remember is
write-only.
Attach optional JSON metadata via RememberBuilder::metadata;
without it the column defaults to {}. Attach a parsed event-time
via RememberBuilder::event_at when the content references a
specific moment (e.g. “the deployment happened Friday”); without it,
the memory has no event-time and is excluded from event-time range
filters at search time.
§Examples
let written = client
.remember("hello", scope)
.metadata(serde_json::json!({ "source": "chat" }))
.await?;
println!("wrote pid={}", written.pid);§Errors
Returns ClientError::Store wrapping a database failure when the
row cannot be inserted, and ClientError::Jobs when the embed or
extract job cannot be enqueued.
Sourcepub fn edit(&self, pid: impl Into<String>) -> EditBuilder<'_>
pub fn edit(&self, pid: impl Into<String>) -> EditBuilder<'_>
Mutates an existing memory in place — a correction, not a supersession.
Returns a per-call builder; await it to apply the patch. Use this when
the original row was wrong and the caller is overwriting it (typo,
misheard utterance, wrong parsed date). When the original was true at
the time but new information now obsoletes it, use the contradiction
path that calls MemoryStore::supersede instead — edit discards the
old text, supersede preserves it.
created_at is preserved; updated_at bumps automatically via the
database trigger. Content changes flip the row’s vector-index state
back to pending and enqueue a re-embed job, so the row drops out of
search hits until the worker drains the queue — same lifecycle as a
fresh remember(). See EditBuilder for the builder methods.
§Errors
Returns ClientError::Store wrapping
crate::store::StoreError::NotFound when no memory matches pid,
crate::store::StoreError::UnsupportedEdit when the target row’s
kind does not support in-place edits (today: every non-Episodic
kind), ClientError::ReservedMetadataKey when metadata contains a
reserved payload key, and ClientError::Jobs when the re-embed
job cannot be enqueued.
Sourcepub fn search(
&self,
query: impl Into<String>,
scope: Scope,
) -> SearchBuilder<'_>
pub fn search( &self, query: impl Into<String>, scope: Scope, ) -> SearchBuilder<'_>
Searches indexed memories in scope by vector similarity to query.
Returns a per-call builder; await it to embed the query, run the
vector search, and assemble the matching Memory rows. The kind
toggles on the builder filter retrieval. See SearchBuilder for
builder methods.
Only memories whose vector index entry has reached indexed are
eligible. Rows still in pending (recently written via
Client::remember, not yet drained by the worker) are filtered
out — they can still be inspected by pid via Client::recall.
§Examples
let memories = client.search("hello", scope).limit(5).await?;
for m in memories.list() {
println!("{}", m.content);
}§Errors
Returns ClientError::Embedding if the query cannot be embedded,
ClientError::Vector if the vector index search fails, and
ClientError::Store wrapping a database failure when the matched
pids cannot be hydrated to full rows.
Sourcepub fn timeline(&self, scope: Scope) -> TimelineBuilder<'_>
pub fn timeline(&self, scope: Scope) -> TimelineBuilder<'_>
Returns memories in scope ordered chronologically — the event log.
Postgres-only read; no embedding, no Qdrant. Includes superseded rows
by default (this is the audit view). Default order is newest-first,
default limit is crate::store::DEFAULT_TIMELINE_LIMIT. See
TimelineBuilder for the builder methods.
§Errors
Returns ClientError::Store wrapping
crate::store::StoreError::InvalidScope when a Scope target has
empty fields, or wrapping
crate::store::StoreError::Database for database failures.
Sourcepub fn query(&self, query: impl Into<String>, scope: Scope) -> QueryBuilder<'_>
pub fn query(&self, query: impl Into<String>, scope: Scope) -> QueryBuilder<'_>
Retrieves memories in scope ranked by hybrid cosine-and-recency, as
a prompt-shaped MemoryContext.
Mirrors Client::search’s candidate-retrieval primitives but
re-ranks the top-K candidates by combining vector similarity with
recency before returning. Default strategy is
RankingStrategy::default_hybrid; override via
QueryBuilder::ranking. The default strategy’s parameter values
are explicitly allowed to drift pre-1.0 — callers depending on a
specific ranking must pass an explicit RankingStrategy::Hybrid { .. }.
Returns a MemoryContext suitable for dropping into a system
prompt via Display. See MemoryContext for the rendering
shape and the staleness caveat for cached output.
§Errors
Returns ClientError::Embedding if the query cannot be embedded,
ClientError::Vector if the vector index search fails, and
ClientError::Store wrapping a database failure when the matched
pids cannot be hydrated to full rows.
Sourcepub fn recall_as_of(
&self,
scope: Scope,
as_of: impl Into<DateTime<FixedOffset>>,
) -> RecallAsOfBuilder<'_>
pub fn recall_as_of( &self, scope: Scope, as_of: impl Into<DateTime<FixedOffset>>, ) -> RecallAsOfBuilder<'_>
Returns memoir’s state of knowledge in scope as of as_of.
A memory is included when it was written on or before as_of and was
not yet superseded then. Pure Postgres read; no Qdrant, no embedder.
Newest-first by created_at, default limit
crate::store::DEFAULT_TIMELINE_LIMIT. See RecallAsOfBuilder
for the builder methods.
§Errors
Returns ClientError::Store wrapping
crate::store::StoreError::InvalidScope when a Scope target has
empty fields, or wrapping
crate::store::StoreError::Database for database failures.
Sourcepub async fn recall(&self, pid: &str) -> Result<Memory, ClientError>
pub async fn recall(&self, pid: &str) -> Result<Memory, ClientError>
Looks up a single memory by its public id, at any lifecycle state.
Returns the memory regardless of whether its vector index entry is
pending, indexed, or failed — callers using this for direct
lookups see the source-of-truth row from Postgres.
No scope check is performed: any caller holding a pid can retrieve
the corresponding memory. The library treats its caller as the trust
boundary; service-mode callers (epic 0007) gate access via their own
auth layer.
§Examples
let memory = client.recall("AbCdEfGhIjKlMnOpQrStU").await?;
println!("{}", memory.content);§Errors
Returns ClientError::Store wrapping crate::store::StoreError::NotFound
when no memory matches pid, and ClientError::Store wrapping
crate::store::StoreError::Database for database failures.
Sourcepub async fn forget(
&self,
target: ForgetTarget,
) -> Result<Vec<String>, ClientError>
pub async fn forget( &self, target: ForgetTarget, ) -> Result<Vec<String>, ClientError>
Deletes one memory by pid, or every memory matching a scope tuple.
The Postgres delete is authoritative — returned pids reflect what was
actually removed from the source of truth. The Qdrant delete is
best-effort: on failure the source-of-truth row is already gone and
the orphaned vector becomes the reconciliation sweep’s problem
(ticket 0012). Failure does not propagate; it emits
memoir.forget.index_delete_failed at WARN.
Returns the pids that were deleted. An empty vector means the target matched no rows — not an error.
§Examples
let deleted = client.forget(ForgetTarget::Pid("abc123".to_string())).await?;
let cleared = client.forget(ForgetTarget::Scope(scope)).await?;§Errors
Returns ClientError::Store wrapping
crate::store::StoreError::InvalidScope when a Scope target has
any empty field, and ClientError::Store wrapping
crate::store::StoreError::Database for database failures.
Sourcepub async fn reject(&self, pid: &str) -> Result<(), ClientError>
pub async fn reject(&self, pid: &str) -> Result<(), ClientError>
Rejects a memory: a wrong extraction the user corrected (epic 0011).
Marks the row retirement_reason = 'rejected' and evicts its vector,
so it disappears from every read and can no longer pollute search or
reprocessing. The row is kept (not deleted) — it is the reprocess
“don’t re-derive this” guard and counts toward the extraction-accuracy
metric. Rejection is the extraction-error case; for a source that
merely changed, use Self::mark_stale.
§Errors
Returns ClientError::Store wrapping
crate::store::StoreError::NotFound when no memory matches pid,
or crate::store::StoreError::Database for database failures. A
vector-eviction failure is logged at WARN (reconciliation cleans the
orphan) and does not fail the call once the row is marked.
Sourcepub async fn mark_stale(&self, pid: &str) -> Result<(), ClientError>
pub async fn mark_stale(&self, pid: &str) -> Result<(), ClientError>
Marks a memory stale: its episodic source changed (epic 0011).
Marks the row retirement_reason = 'stale' and evicts its vector. Like
Self::reject the row is hidden everywhere and kept, but stale is
NOT an extraction error (the model was right; the source moved), so it
does not count against the accuracy metric.
§Errors
See Self::reject.
Sourcepub fn feedback(&self, pid: impl Into<String>) -> FeedbackBuilder<'_>
pub fn feedback(&self, pid: impl Into<String>) -> FeedbackBuilder<'_>
Corrects a wrong extraction by teaching, not editing (epic 0011).
pid is the wrong semantic memory the user saw in recall. Awaiting
the returned builder enqueues a reprocess of that fact’s episodic
source: the derived rows are retired as rejected and re-derived with
the correction in context, so a corrected fact replaces the wrong one.
The user never hand-writes a semantic row — semantic memory stays
always-derived. Fire-and-forget: returns once the job is enqueued.
To correct the episodic record itself, use Self::edit; that is a
different correction (the source changed, not a wrong extraction). See
FeedbackBuilder for the builder methods.
§Errors
Returns ClientError::Store wrapping
crate::store::StoreError::NotFound when no memory matches pid,
ClientError::NotCorrectable when the target is not a semantic row
or has no episodic source, and ClientError::Jobs when the reprocess
job cannot be enqueued.
Sourcepub fn reconcile(&self) -> ReconcileBuilder<'_>
pub fn reconcile(&self) -> ReconcileBuilder<'_>
Runs reconciliation: retries failed rows and cleans Qdrant orphans.
Returns a per-call builder. Awaiting it runs the configured passes
and returns a ReconcileSummary. Both passes run by default;
narrow with ReconcileBuilder::only_retry_failed /
ReconcileBuilder::only_clean_orphans. Idempotent: running against
a clean store does nothing and exits cleanly.
§Examples
let summary = client.reconcile().await?;
let _ = summary;Sourcepub fn spawn_worker(&self) -> WorkerBuilder<'_>
pub fn spawn_worker(&self) -> WorkerBuilder<'_>
Configures the background queue worker; call .start().await to launch.
Returns a per-call builder. The worker polls the memory_jobs queue,
dispatches each row to its stage handler (embed in ticket 0007,
extract in ticket 0006), and runs lease recovery when the queue is
idle. Cooperative shutdown via WorkerHandle::shutdown.
§Examples
let worker = client.spawn_worker().start().await?;
// ... server runs ...
worker.shutdown().await;Sourcepub async fn failed_jobs(
&self,
limit: usize,
) -> Result<Vec<FailedJob>, ClientError>
pub async fn failed_jobs( &self, limit: usize, ) -> Result<Vec<FailedJob>, ClientError>
Lists failed jobs newest-first, capped at limit.
Returns metadata only (id, kind, source pid, attempts, failure
reason, last update); content from the referenced memory is NOT
included. Operators who need to inspect the memory’s content can
follow up with Self::recall against the source pid.
§Errors
Returns ClientError::Jobs wrapping any database failure.
Sourcepub async fn retry_job(&self, id: i64) -> Result<(), ClientError>
pub async fn retry_job(&self, id: i64) -> Result<(), ClientError>
Retries one failed job, clearing the attempt counter.
The attempt counter is reset to zero on operator-initiated retry: a
human has decided prior failures shouldn’t count against the new
attempt budget. Reconciliation-driven retries leave the counter
alone (see Self::reconcile).
§Errors
Returns ClientError::Jobs wrapping crate::jobs::JobsError::NotFound
when no failed job matches id, or wrapping a database failure.
Sourcepub fn retry_failed_jobs(&self) -> RetryBuilder<'_>
pub fn retry_failed_jobs(&self) -> RetryBuilder<'_>
Configures a bulk retry. Awaiting the returned builder runs it.
See RetryBuilder for filter and dry-run options. Returns the
number of affected (or for dry_run, would-affect) rows.
Sourcepub async fn delete_failed_job(&self, id: i64) -> Result<(), ClientError>
pub async fn delete_failed_job(&self, id: i64) -> Result<(), ClientError>
Permanently deletes one failed job. The referenced memory is untouched.
§Errors
Returns ClientError::Jobs wrapping crate::jobs::JobsError::NotFound
when no failed job matches id, or wrapping a database failure.
Sourcepub async fn pending_jobs_count(&self) -> Result<u64, ClientError>
pub async fn pending_jobs_count(&self) -> Result<u64, ClientError>
Returns the number of jobs currently in pending state.
Cheap observation for operators monitoring queue depth.
§Errors
Returns ClientError::Jobs wrapping any database failure.
Sourcepub async fn unsupersede(&self, pid: &str) -> Result<(), ClientError>
pub async fn unsupersede(&self, pid: &str) -> Result<(), ClientError>
Clears the supersession marker on pid, restoring it to active state.
Admin-only counterpart to the internal supersede path. Use when an
operator decides a future contradiction-detection pass wrongly
marked a row as outdated. Idempotent at the SQL level for rows that
were already active, but still errors if no row matches pid.
There is no symmetric public Client::supersede: supersession is a
decision made by the (forthcoming) detection engine against verified
contradicting facts, not by operator hand. Hand-rolled supersession
would defeat the audit trail the column is meant to preserve.
§Errors
Returns ClientError::Store wrapping
crate::store::StoreError::NotFound when no memory matches pid,
or wrapping a database failure.
Sourcepub async fn supersession_history(
&self,
pid: &str,
) -> Result<Vec<SupersessionEvent>, ClientError>
pub async fn supersession_history( &self, pid: &str, ) -> Result<Vec<SupersessionEvent>, ClientError>
Returns the full supersede/unsupersede event trail for pid.
Each SupersessionEvent is one decision against the memory,
chronological (oldest first). An event with winner_pid = None is
an unsupersede. A pid with no events — never superseded, or simply
not present in the store — returns an empty vec, not an error.
Surfaces the audit trail behind a row’s current Memory.supersession
marker, for the supersession-audit UI and rig-service introspection
of contradiction-detection decisions over time.
§Errors
Returns ClientError::Store wrapping a database failure.
Sourcepub async fn list_agents(
&self,
org_id: &str,
user_id: &str,
) -> Result<Vec<String>, ClientError>
pub async fn list_agents( &self, org_id: &str, user_id: &str, ) -> Result<Vec<String>, ClientError>
Lists the distinct agent ids with memories under org_id + user_id.
Caller-scoped agent discovery: returns only the agents within the given org and user, sorted ascending, so a tenant never sees another tenant’s agents. A scope with no memories yet returns an empty vec, not an error.
§Errors
Returns ClientError::Store wrapping a database failure.
Sourcepub fn extraction_stats(&self) -> ExtractionStatsBuilder<'_>
pub fn extraction_stats(&self) -> ExtractionStatsBuilder<'_>
Computes extraction accuracy per (provider, model) over a scope slice.
Returns an ExtractionStatsBuilder; its scope setters narrow the slice
before awaiting. A read-only aggregate proving extraction quality to a
consumer — accuracy = 1 − rejected/total, where rejected counts only
wrong extractions the user corrected (not Stale or superseded rows).
No LLM call. See ExtractionStatsBuilder for the builder methods.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for Client
impl !UnwindSafe for Client
impl Freeze for Client
impl Send for Client
impl Sync for Client
impl Unpin for Client
impl UnsafeUnpin for Client
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request