Documentation
¶
Overview ¶
Package store defines the storage abstraction memini retrieves memories through. Drivers (sqlite-vec, Postgres/VectorChord) implement Store; the hybrid-search and service layers depend only on this interface.
Index ¶
- Variables
- func MemoryTypeLabel(m *memory.Memory) string
- func OrEmptyMap(m map[string]any) map[string]any
- func OrEmptySlice(s []string) []string
- type APIKey
- type APIKeyStore
- type ActivityStore
- type EmbedModelStore
- type Filter
- type LinkStore
- type Metrics
- type NamespaceActivity
- type NamespaceLink
- type Scored
- type Store
Constants ¶
This section is empty.
Variables ¶
var ErrConflict = errors.New("id exists in a different namespace")
ErrConflict is returned by Upsert when the given ID already exists in a different namespace, preventing cross-tenant hijacking.
var ErrNotFound = errors.New("memory not found")
ErrNotFound is returned by Get/Delete when no memory matches.
Functions ¶
func MemoryTypeLabel ¶ added in v0.2.5
MemoryTypeLabel returns m's typed-extraction class for the memory_type metric label, or "" when absent or unrecognized (keeping cardinality bounded).
func OrEmptyMap ¶ added in v0.2.8
OrEmptyMap returns m, or an empty map when m is nil, so drivers persist an empty JSON object rather than null for absent metadata.
func OrEmptySlice ¶ added in v0.2.8
OrEmptySlice returns s, or an empty slice when s is nil, so drivers persist an empty JSON array rather than null for absent tags.
Types ¶
type APIKey ¶ added in v0.6.7
type APIKey struct {
Name string // unique human label, primary key
Hash string // hex SHA-256 of the secret; never the secret itself
HomeNS string // bound home namespace; "" means unbound
// DefaultNS is the namespace applied to a request that presents this key
// but carries no X-Memini-Namespace header; an explicit header always
// wins. "" means no per-key default (the server-wide default applies).
DefaultNS string
CreatedAt time.Time
Disabled bool
}
APIKey is a persisted API credential: a unique human label, the hex SHA-256 hash of the secret (the secret itself is never stored), an optional home namespace it is bound to, when it was created, and whether it is disabled.
type APIKeyStore ¶ added in v0.6.7
type APIKeyStore interface {
// PutAPIKey inserts or replaces the key keyed by k.Name.
//
// Unlike PutLink (which deliberately overwrites created_at on every
// upsert, since links carry no recency semantics — see its doc above),
// this upsert preserves the existing row's CreatedAt when the incoming
// k.CreatedAt is the zero value: API keys are long-lived identity, and
// rotating a key's hash or home namespace must not reset "when was this
// key first created". Passing a non-zero k.CreatedAt (e.g. import
// restore replaying an original timestamp) still overwrites it, mirroring
// PutLink's own conditional-overwrite convention.
PutAPIKey(ctx context.Context, k APIKey) error
// DeleteAPIKey removes the key by name. The bool reports whether a key
// existed to delete; a missing key is not an error.
DeleteAPIKey(ctx context.Context, name string) (bool, error)
// ListAPIKeys returns every key ordered by name. Never returns secrets —
// only the stored hash — since the plaintext secret is never persisted.
ListAPIKeys(ctx context.Context) ([]APIKey, error)
// GetAPIKeyByHash returns the key whose Hash matches, or nil, nil when
// none does. This is the auth-path lookup, so drivers must maintain an
// index/unique constraint on hash for it to stay fast.
GetAPIKeyByHash(ctx context.Context, hash string) (*APIKey, error)
// RenameAPIKeyNamespaces rewrites every key whose HomeNS or DefaultNS
// equals from to to instead — both columns in one call, since a
// namespace move (maintenance.Move, which also calls
// RenameLinkEndpoints) must not leave either binding pointing at the
// old name. Keys matching in neither column, and the non-matching
// column of a partially-matching key, are untouched; CreatedAt is never
// modified. A no-op when from == to.
RenameAPIKeyNamespaces(ctx context.Context, from, to string) error
}
APIKeyStore is implemented by drivers that persist api_keys, the multiple-API-keys-with-optional-home-namespace feature. It is an optional capability interface — the EmbedModelStore/LinkStore/ActivityStore precedent above — so callers type-assert and degrade gracefully against a driver that predates it. The auth middleware consumes GetAPIKeyByHash on the request path; the CLI consumes Put/Delete/List.
type ActivityStore ¶ added in v0.6.6
type ActivityStore interface {
// NamespaceActivity returns one row per namespace holding at least one
// live memory, ordered by namespace. now is the expiry-evaluation instant
// (mirroring Filter.Now); the zero value means the wall clock.
NamespaceActivity(ctx context.Context, now time.Time) ([]NamespaceActivity, error)
}
ActivityStore is implemented by drivers that can compute per-namespace activity in a single aggregate query (SELECT namespace, COUNT(*), MAX(created_at) ... GROUP BY namespace). It is an optional capability interface — the EmbedModelStore/LinkStore precedent — so callers type-assert and degrade gracefully against a driver that predates it: the briefing child rollup skips itself entirely rather than falling back to per-namespace scans.
type EmbedModelStore ¶ added in v0.3.9
type EmbedModelStore interface {
// EmbedModel returns the recorded embedding model name, or "" when none has
// been recorded yet (a fresh store, or one created before this was tracked).
EmbedModel(ctx context.Context) (string, error)
// SetEmbedModel records model as the embedding model the stored vectors were
// produced with, overwriting any previous value.
SetEmbedModel(ctx context.Context, model string) error
}
EmbedModelStore is implemented by drivers that record which embedding model produced their stored vectors. It lets the bootstrap detect a silent model swap — a new MEMINI_EMBED_MODEL at the same dimensionality, which the dims guard cannot catch — where old and new vectors share a width but live in incomparable spaces, quietly degrading recall.
type Filter ¶
type Filter struct {
// Tiers restricts results to these tiers; empty means all tiers.
Tiers []memory.Tier
// Levels restricts results to memories whose derivation level (explicit or
// deduced) matches one of the listed values; empty means no level constraint.
Levels []memory.Level
// Tags restricts results to memories carrying every listed tag (AND). Empty
// means no tag constraint.
Tags []string
// Metadata restricts results to memories whose top-level metadata contains
// each listed key with the given string value (AND). Empty means no metadata
// constraint. Only top-level string-valued entries are matched.
Metadata map[string]string
// ExcludeMetadata drops memories whose top-level metadata carries any of the
// listed key=value pairs (the inverse of Metadata). Empty means no exclusion.
// Used to keep a caller from recalling its own just-written memories — e.g.
// the OpenClaw plugin tags each captured turn with its session id and excludes
// that session on the pre-turn auto-recall, so a turn already in the live
// transcript is not echoed back as "long-term memory".
ExcludeMetadata map[string]string
// IncludeExpired includes memories past their TTL (default excludes them).
IncludeExpired bool
// IncludeSuperseded includes contradiction-tombstoned memories.
IncludeSuperseded bool
// Now is the instant expiry is evaluated at; the zero value means the wall
// clock. Callers with an injected clock (service.WithClock) should set it so
// store-level expiry filtering agrees with their notion of "now".
Now time.Time
// AsOf, when non-zero, switches to time-travel recall: results are the
// memories whose validity window contained AsOf (valid_from <= AsOf < valid_to,
// treating NULL bounds as open). It overrides the superseded exclusion, so a
// fact that was true then but has since been replaced is still returned.
AsOf time.Time
}
Filter narrows a search to a subset of memories. The zero value matches all live (non-expired, non-superseded) memories in the namespace.
type LinkStore ¶ added in v0.6.6
type LinkStore interface {
// PutLink inserts or replaces the link keyed by (l.Src, l.Dst). An
// upsert overwrites created_at — deliberately, unlike memory Put's
// preserve-on-upsert: links have no recency semantics, and import
// restore relies on this to replay a link's original CreatedAt rather
// than stamping "now".
PutLink(ctx context.Context, l NamespaceLink) error
// DeleteLink removes the link from src to dst. The bool reports whether a
// link existed to delete; a missing link is not an error.
DeleteLink(ctx context.Context, src, dst string) (bool, error)
// ListLinks returns the links whose Src is src, ordered by Dst. Empty (not
// an error) when src has no outgoing links.
ListLinks(ctx context.Context, src string) ([]NamespaceLink, error)
// ListAllLinks returns every link in the store, for the CLI/UI.
ListAllLinks(ctx context.Context) ([]NamespaceLink, error)
// RenameLinkEndpoints rewrites every link whose Src or Dst equals from to
// to instead, used when a namespace is moved (maintenance.Move). When a
// rewritten link collides with a pre-existing row at its new key, the
// pre-existing row wins and the renamed link is dropped: the target
// namespace's own explicit grant must never be silently widened or
// narrowed by an inherited one. A no-op when from == to.
RenameLinkEndpoints(ctx context.Context, from, to string) error
}
LinkStore is implemented by drivers that persist namespace_links, the cross-namespace read-linking table (namespace-cascade design). It is an optional capability interface — the EmbedModelStore precedent above — so callers type-assert and degrade gracefully against a driver that predates it.
type Metrics ¶
type Metrics interface {
// Upsert records one Upsert outcome. op is "insert" or "update"; tier
// is the memory's tier (working/episodic/semantic/procedural); memoryType
// is the typed-extraction class (decision/preference/problem) or "".
Upsert(op, tier, memoryType string)
// Delete records a hard delete from the Forget API path.
Delete()
// SoftDelete records a tombstone written by consolidation.
SoftDelete()
// SweepExpired records one memory removed by the decay sweeper.
SweepExpired(tier string)
// ActiveByTier sets the current count of live (non-superseded,
// non-expired) memories per tier. Called periodically after sweeps/fsck.
ActiveByTier(tier string, n int)
// DedupTombstoned records the number of memories tombstoned by one
// dedup pass (the periodic vector-cluster job or a one-shot dedup call).
// Called once per pass with the pass total.
DedupTombstoned(n int)
}
Metrics receives store events for observability. Methods must be safe for concurrent use; a nil Metrics is replaced by a no-op. Implementations live alongside the Prometheus registry in cmd/memini.
type NamespaceActivity ¶ added in v0.6.6
NamespaceActivity summarizes one namespace's live activity: the count of live memories (excluding expired, superseded, and closed-validity rows — the same liveness a default List filter applies) and the most recent created_at among them (the "last write" column Stats.LastWriteAt uses; unlike Stats, tombstoned rows do not advance it, since a single aggregate query shares one liveness WHERE for both figures).
type NamespaceLink ¶ added in v0.6.6
type NamespaceLink struct {
Src, Dst string
Tiers []memory.Tier // nil = durable default applied by service
Note string
CreatedAt time.Time
}
NamespaceLink is a directed cross-namespace read link: recall scoped to Src additionally reads durable memories from Dst. Tiers restricts which tiers cross the boundary; nil means the service layer applies its durable-tier default (only semantic/procedural cross namespace boundaries — episodic and working never do, in either direction). Note is a free-text annotation for operators explaining why the link exists.
type Scored ¶
Scored is a memory paired with a relevance score for the query that produced it. Results are always returned best-first; Score is higher-is-better and is only comparable within a single method's result set.
type Store ¶
type Store interface {
// Upsert inserts or replaces a memory (matched by ID within its namespace),
// including its keyword-search index entry. When len(m.Embedding) == 0 the
// row is stored with no vector-index entry — kept keyword-searchable only,
// the write path used when embedding generation is unavailable; a stale
// vector-index entry from a prior upsert of the same ID is removed. Any
// other embedding length must equal the store's configured dims, or
// Upsert errors. VectorSearch never returns a vectorless row. Returns
// ErrConflict when the given ID already exists under a different namespace.
Upsert(ctx context.Context, m *memory.Memory) error
// Get returns a memory by ID, or ErrNotFound.
Get(ctx context.Context, namespace, id string) (*memory.Memory, error)
// PredecessorIDs returns the IDs of memories in the namespace whose
// SupersededBy points at id — the versions id replaced. Empty when none.
// Used to walk a memory's supersession lineage backwards.
PredecessorIDs(ctx context.Context, namespace, id string) ([]string, error)
// GetByFingerprint returns a live (non-superseded, non-expired) memory in the
// namespace and tier whose content fingerprint (memory.Fingerprint) matches,
// for exact-restatement dedup at write time. Returns ErrNotFound when none
// matches. Now is the instant expiry is evaluated at (zero means wall clock).
GetByFingerprint(ctx context.Context, namespace string, tier memory.Tier, fingerprint string, now time.Time) (*memory.Memory, error)
// Delete removes a memory by ID, or returns ErrNotFound.
Delete(ctx context.Context, namespace, id string) error
// SetSuperseded tombstones a memory by recording the ID that replaced it,
// excluding it from default search results. Returns ErrNotFound if missing.
SetSuperseded(ctx context.Context, namespace, id, supersededBy string) error
// Restore clears superseded_by/valid_to so a tombstoned memory is live
// again. Returns ErrNotFound if missing.
Restore(ctx context.Context, namespace, id string) error
// VectorSearch returns the k memories nearest to vec, best-first.
VectorSearch(ctx context.Context, namespace string, vec []float32, f Filter, k int) ([]Scored, error)
// KeywordSearch returns the k best full-text matches for query, best-first.
KeywordSearch(ctx context.Context, namespace, query string, f Filter, k int) ([]Scored, error)
// Reinforce records that the given memories were just recalled: it bumps
// access_count and last_accessed_at to accessedAt. When newExpiry is non-nil
// it also slides the TTL forward for those that already expire (short-term
// memories), so frequently-recalled memories don't go stale. Missing IDs are
// ignored.
Reinforce(ctx context.Context, namespace string, ids []string, accessedAt time.Time, newExpiry *time.Time) error
// DeleteIfExpiredBefore removes a memory only when its expiry is still at or
// before cutoff. Returns ErrNotFound when the memory was absent or when its
// TTL was slid past cutoff by Reinforce, so the caller never over-deletes a
// memory that was accessed between ListExpired and the delete call.
DeleteIfExpiredBefore(ctx context.Context, namespace, id string, cutoff time.Time) error
// ListExpired returns up to limit memories whose TTL has passed, for the
// decay sweeper.
ListExpired(ctx context.Context, now time.Time, limit int) ([]*memory.Memory, error)
// List returns memories in a namespace matching f (without embeddings),
// for maintenance tasks (short-term capacity, fsck). limit <= 0 means all.
List(ctx context.Context, namespace string, f Filter, limit int) ([]*memory.Memory, error)
// ListNamespaces returns the distinct namespaces that hold memories.
ListNamespaces(ctx context.Context) ([]string, error)
// DeleteNamespace removes every memory in a namespace (including embeddings
// and keyword-search index entries). Returns the number of memories deleted.
DeleteNamespace(ctx context.Context, namespace string) (int64, error)
// Reassign moves the given memories from fromNS to toNS (recovery from a
// botched import or a shared-pool migration). IDs absent from fromNS are
// skipped; since IDs are globally unique a move never collides in toNS.
// Returns the number of memories moved.
Reassign(ctx context.Context, fromNS string, ids []string, toNS string) (int64, error)
// Retier changes a memory's tier and expiry in place (used by retro-tiering
// to demote stale durable memories). Tier lives only in the main row, so no
// vector/keyword reindex is needed. Returns ErrNotFound if missing.
Retier(ctx context.Context, namespace, id string, tier memory.Tier, expiresAt *time.Time) error
// SetConfidence updates a memory's corroboration confidence in place and
// bumps updated_at to now (resetting the lazy-decay baseline), used when a
// durable fact is re-observed. Returns ErrNotFound if missing.
SetConfidence(ctx context.Context, namespace, id string, confidence float64, now time.Time) error
// MarkContradicted invalidates a durable fact a newer write contradicts: it
// sets confidence, stamps valid_to=now (unless already set) so the fact
// drops out of live recall while staying reachable via AsOf time-travel, and
// records the contradicting id plus the pre-update confidence in metadata for
// audit and reversal (Restore clears valid_to). Non-destructive: the row and
// its history are kept. Bumps updated_at to now. Returns ErrNotFound if
// missing.
MarkContradicted(ctx context.Context, namespace, id, contradictedBy string, confidence float64, now time.Time) error
// Ping verifies the backend is reachable, for readiness checks.
Ping(ctx context.Context) error
// Close releases backend resources.
Close() error
}
Store is the persistence and retrieval contract for memories. Implementations must be safe for concurrent use.