Documentation
¶
Index ¶
- Constants
- func MCPSigningSeed(secrets ...string) []byte
- func NewMCPAuthorizer(issuer, resource string, tokenTTL time.Duration, dev bool, ...) (*mcpAuthorizer, error)
- func NewMCPServer(memory *MemoryStore, logger zerolog.Logger, getToken func() string, ...) http.Handler
- func SetOAuthClientPersistence(onChange func())
- func SetSessionPersistence(hmacKey []byte, onChange func())
- func SetTrustedProxyHops(n int)
- func ValidEventDate(s string) bool
- func ValidateSearchKind(kind string) string
- func ValidateWrite(kind, confidence string) string
- func WithOrigin(ctx context.Context, origin string) context.Context
- type AzureOAuthConfig
- type CategoryConfig
- type CellSyncer
- func (s *CellSyncer) Persist(ctx context.Context, it *MemoryItem, namespace string) error
- func (s *CellSyncer) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
- func (s *CellSyncer) Remove(ctx context.Context, id, namespace string) error
- func (s *CellSyncer) SeedAll(ctx context.Context, namespace string) error
- func (s *CellSyncer) SetMapRegistrar(r MapRegistrar)
- func (s *CellSyncer) SetRegistryPullSecret(name string)
- func (s *CellSyncer) SetupWithManager(mgr ctrl.Manager) error
- func (s *CellSyncer) SyncCell(ctx context.Context, cell *mcell.MemoryCell) error
- type ConfigWatcher
- type ConsolidationGroup
- type ConsolidationReport
- type GitHubOAuthConfig
- type GraphFetcher
- type Hub
- func (h *Hub) ApplyRemoteChange(evt MemoryChangeEvent)
- func (h *Hub) ApplyRemoteHit(evt MemoryHitEvent)
- func (h *Hub) HitCount(id string) int64
- func (h *Hub) Publish(evt MemoryHitEvent)
- func (h *Hub) PublishChange(evt MemoryChangeEvent)
- func (h *Hub) PublishChangeTo(userHash string, evt MemoryChangeEvent)
- func (h *Hub) ServeWS(w http.ResponseWriter, r *http.Request, userHash string)
- func (h *Hub) SetChangePublishHook(fn func(MemoryChangeEvent))
- func (h *Hub) SetHitPublishHook(fn func(MemoryHitEvent))
- type MCPServerRef
- type MapRegistrar
- type MemoryChangeEvent
- type MemoryHitEvent
- type MemoryItem
- type MemoryItemSummary
- type MemoryNeighbor
- type MemoryNeighborEdge
- type MemoryNeighborhood
- type MemoryStore
- func (s *MemoryStore) AddUserTokens(userHash string, n int64)
- func (s *MemoryStore) AllItems(subject string) []*MemoryItem
- func (s *MemoryStore) ConsolidateMemory(subject string, apply bool) (ConsolidationReport, []*MemoryItem)
- func (s *MemoryStore) Context(anchor string, maxDepth int) (anchors []*MemoryItem, activated []ScoredItem)
- func (s *MemoryStore) Count() int
- func (s *MemoryStore) Delete(id string) bool
- func (s *MemoryStore) EmbedMetrics() vectorstore.EmbedMetrics
- func (s *MemoryStore) Expire(id string) *MemoryItem
- func (s *MemoryStore) ExportAll() []*MemoryItem
- func (s *MemoryStore) FilterItems(subject, kind string) []*MemoryItem
- func (s *MemoryStore) FlushStats()
- func (s *MemoryStore) Get(id string) *MemoryItem
- func (s *MemoryStore) GetItem(id string) *MemoryItem
- func (s *MemoryStore) KeywordSearch(query, kind, subject string, limit int) []*MemoryItem
- func (s *MemoryStore) KeywordSearchPerKind(query, kind, subject string, limit, perKind int) []*MemoryItem
- func (s *MemoryStore) KnowledgeIndex(subject string) []string
- func (s *MemoryStore) List(kind, scope, subject string, limit int) []*MemoryItem
- func (s *MemoryStore) ListRecent(kind, scope, subject string, limit int) []*MemoryItem
- func (s *MemoryStore) ListSummary(kind, scope, subject string, limit int) (items []MemoryItemSummary, total int)
- func (s *MemoryStore) ListSummaryPerKind(perKind int) (items []MemoryItemSummary, total int, totalsByKind map[string]int)
- func (s *MemoryStore) Metrics() SearchMetrics
- func (s *MemoryStore) Neighborhood(id string, top int) *MemoryNeighborhood
- func (s *MemoryStore) PublishHit(itemIDs []string, kind string, kinds []string)
- func (s *MemoryStore) Refute(ctx context.Context, id, reason string, evidence []string) (changed []*MemoryItem, found bool)
- func (s *MemoryStore) Restore(ctx context.Context, it *MemoryItem)
- func (s *MemoryStore) RestoreStats(st *mcell.RuntimeStats)
- func (s *MemoryStore) Search(ctx context.Context, query, kind, subject string, limit int) []*MemoryItem
- func (s *MemoryStore) SetCacheReader(r client.Reader, namespace string)
- func (s *MemoryStore) SetCandidateFanout(n int)
- func (s *MemoryStore) SetEpisodicTTL(ttl time.Duration)
- func (s *MemoryStore) SetLexicalShortCircuit(on bool)
- func (s *MemoryStore) SetLinkExpansion(on bool)
- func (s *MemoryStore) SetLinks(id string, links []mcell.ThoughtLink) bool
- func (s *MemoryStore) SetRecallShaping(on bool)
- func (s *MemoryStore) SetSemanticScoreGap(gap float32)
- func (s *MemoryStore) SetSourceCollapse(relation string)
- func (s *MemoryStore) SetWriteDedup(on bool)
- func (s *MemoryStore) SetWriter(c client.Client)
- func (s *MemoryStore) StartEpisodicReaper(ctx context.Context, syncer *CellSyncer, every time.Duration)
- func (s *MemoryStore) StartRetentionSweep(ctx context.Context, syncer *CellSyncer, every time.Duration)
- func (s *MemoryStore) StartStatsFlusher(ctx context.Context, every time.Duration)
- func (s *MemoryStore) Supersede(ctx context.Context, ...) (changed []*MemoryItem, errMsg string)
- func (s *MemoryStore) ToolMetrics() map[string]any
- func (s *MemoryStore) TrackKWTrigramHit()
- func (s *MemoryStore) TrackKWVoiceHit()
- func (s *MemoryStore) TrackKWVoiceMiss()
- func (s *MemoryStore) TrackToolCall(name string, d time.Duration)
- func (s *MemoryStore) TrackVoiceCall()
- func (s *MemoryStore) TrackVoiceEnd()
- func (s *MemoryStore) Upsert(ctx context.Context, ...) (UpsertResult, string)
- func (s *MemoryStore) UserTokensToday(userHash string) int64
- func (s *MemoryStore) VoiceMetrics() map[string]any
- type QuotaUsage
- type ScoredItem
- type SearchMetrics
- type SecretStore
- type Server
- type TokenBreakdown
- type UpsertOption
- type UpsertResult
- type VoiceBrowserMessage
- type VoiceManager
- func (m *VoiceManager) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (m *VoiceManager) SetMemMaps(deps *recallmcp.Deps)
- func (m *VoiceManager) Shutdown()
- func (m *VoiceManager) UserQuota(rawUserID string) (used, limit, remaining int64, ok bool)
- func (m *VoiceManager) VoiceMetrics() map[string]any
- type VoiceServerMessage
- type VoiceSession
- type VoiceSessionConfig
Constants ¶
const ( DefaultMCPListMemoryMaxLimit = 20 DefaultMCPSearchMemoryMaxLimit = 10 DefaultMCPRecentMemoryMaxLimit = 20 )
DefaultMCPListMemoryMaxLimit / DefaultMCPSearchMemoryMaxLimit are the fallback hard result-count ceilings for list_memory/search_memory when the caller (main.go) doesn't override them via --mcp-list-memory-max-limit / --mcp-search-memory-max-limit. Applied regardless of what limit the MCP caller requests — a misbehaving or adversarial client (the limit is fully caller-controlled otherwise) cannot force a full-store dump (ADR-051).
const ( // OriginObserved: written by code from facts. OriginObserved = "observed" // OriginDerived: written by a model. A prior claim, never evidence. A cell // with no origin is derived. OriginDerived = "derived" // OriginHuman: written by an identified person (an OAuth-authenticated MCP // call, a voice session). Outranks derived. OriginHuman = "human" // OriginVerified: derived, then passed a check. Set only in code. OriginVerified = "verified" )
Cell provenance (ADR-053), stored in MemoryCellSpec.Origin.
const SystemConfigCRName = "xray-system-configuration"
Variables ¶
This section is empty.
Functions ¶
func MCPSigningSeed ¶
MCPSigningSeed is the exported entry point for mcpSigningSeed, called from main.go with the same OAuth client secrets fed to sessionHMACKey (ADR-050).
func NewMCPAuthorizer ¶
func NewMCPAuthorizer(issuer, resource string, tokenTTL time.Duration, dev bool, writeAllowed []string, seed []byte, logger zerolog.Logger) (*mcpAuthorizer, error)
NewMCPAuthorizer builds the co-located OAuth 2.1 authorization server for MCP. issuer is the public base URL; resource is the MCP endpoint URL (issuer+"/mcp" by convention). dev=true auto-issues tokens for a synthetic user so the flow is testable without a real IdP. seed deterministically derives the Ed25519 signing key so every replica converges on the same key (ADR-050); pass mcpSigningSeed(...) in production. A nil seed generates a random per-process key (single-replica dev). The returned *mcpAuthorizer is passed to both NewMCPServer (for token validation) and Server.SetMCPAuth (for the endpoints).
func NewMCPServer ¶
func NewMCPServer(memory *MemoryStore, logger zerolog.Logger, getToken func() string, auth *mcpAuthorizer, syncer *CellSyncer, namespace string, memMapsDeps *msnap.Deps, listMemoryMaxLimit, searchMemoryMaxLimit, recentMemoryMaxLimit int) http.Handler
NewMCPServer returns an HTTP handler exposing memory tools. syncer and namespace are optional: if syncer is non-nil, upsert/delete also persist changes to AwarenessMemoryCell CRs in the cluster. listMemoryMaxLimit/searchMemoryMaxLimit/recentMemoryMaxLimit are hard result-count ceilings for list_memory/search_memory/recent_memory (ADR-051); pass <= 0 to use the Default* constants.
Authentication accepts two credential types, tried in order:
- an X-Ray-issued OAuth JWT (when auth != nil) — carries per-user scopes;
- the static shared Bearer secret from getToken() — treated as full scope.
At least one must be configured; otherwise the endpoint fails closed (503).
func SetOAuthClientPersistence ¶
func SetOAuthClientPersistence(onChange func())
SetOAuthClientPersistence wires the flush hook so a new/updated registration is persisted to CR status on the next flush tick. Called once at startup, BEFORE mgr.Start, so the first CR replay restores into this store. A nil hook leaves the registry in-memory only (local dev without k8s).
func SetSessionPersistence ¶
func SetSessionPersistence(hmacKey []byte, onChange func())
SetSessionPersistence enables CR-backed session persistence: tokens are hashed with hmacKey (derived from a stable secret so it survives restarts and matches across replicas) and onChange is invoked after each login/logout to flush the set to status. Called once at startup from main.go; a nil hmacKey leaves sessions in-memory only. See ADR-047.
func SetTrustedProxyHops ¶
func SetTrustedProxyHops(n int)
SetTrustedProxyHops configures how many trusted reverse proxies sit in front of the server (e.g. 1 for a single ingress/LB that appends the client IP). Negative values are clamped to 0.
func ValidEventDate ¶
ValidEventDate reports whether s is an event date the store can render back: RFC3339 or a bare YYYY-MM-DD date.
func ValidateSearchKind ¶
ValidateSearchKind rejects a kind filter that names no searchable kind. An empty filter means every kind and is valid.
func ValidateWrite ¶
ValidateWrite checks what an external writer may put in an item: a kind from the vocabulary, and high confidence on a stable_fact. "" means valid.
Types ¶
type AzureOAuthConfig ¶
type AzureOAuthConfig struct {
ClientID string
ClientSecret string
TenantID string
PublicURL string
// UICallbackOrigins allowlists origins a login may hand the session token
// to via ?ui=, for a UI served from a different host than this server.
// Empty (the default) disables the hand-off entirely — see resolveUIOrigin.
UICallbackOrigins []string
}
AzureOAuthConfig holds the Azure AD OAuth app credentials.
type CategoryConfig ¶
type CategoryConfig struct {
ID string `json:"id"`
Label string `json:"label"`
Color string `json:"color"`
Kind string `json:"kind"`
Tag string `json:"tag,omitempty"`
Tier float64 `json:"tier"`
Angle float64 `json:"angle"`
Icon string `json:"icon,omitempty"`
Scope string `json:"scope,omitempty"`
}
CategoryConfig is the parsed, typed form of mcell.CategoryConfig — a UI display category served pre-auth via /v1/categories. JSON tags match the frontend's RuntimeCategoryConfig shape (ui/lib/category-config.ts).
type CellSyncer ¶
CellSyncer seeds the MemoryStore HNSW index from MemoryCell CRs and watches for changes.
func NewCellSyncer ¶
func NewCellSyncer(c client.Client, memory *MemoryStore, logger zerolog.Logger) *CellSyncer
func (*CellSyncer) Persist ¶
func (s *CellSyncer) Persist(ctx context.Context, it *MemoryItem, namespace string) error
Persist writes a MemoryItem to the cluster as an MemoryCell CR. Preserves any existing embedding so the reconciler won't re-embed unnecessarily.
func (*CellSyncer) Remove ¶
func (s *CellSyncer) Remove(ctx context.Context, id, namespace string) error
Remove deletes an MemoryCell CR by item ID.
func (*CellSyncer) SeedAll ¶
func (s *CellSyncer) SeedAll(ctx context.Context, namespace string) error
SeedAll lists all MemoryCell CRs in the namespace and loads them into the HNSW index. Called once at startup before serving requests.
func (*CellSyncer) SetMapRegistrar ¶
func (s *CellSyncer) SetMapRegistrar(r MapRegistrar)
SetMapRegistrar wires the recall registry so graph-carrying cells (Data["graph"]) are loaded as searchable in-memory maps. No-op transport when nil.
func (*CellSyncer) SetRegistryPullSecret ¶
func (s *CellSyncer) SetRegistryPullSecret(name string)
SetRegistryPullSecret names the Secret whose registry credentials are used to pull graphRef artifacts.
func (*CellSyncer) SetupWithManager ¶
func (s *CellSyncer) SetupWithManager(mgr ctrl.Manager) error
SetupWithManager registers the CellSyncer as a controller.
func (*CellSyncer) SyncCell ¶
func (s *CellSyncer) SyncCell(ctx context.Context, cell *mcell.MemoryCell) error
SyncCell reconciles a single MemoryCell into the in-memory store.
type ConfigWatcher ¶
type ConfigWatcher struct {
// contains filtered or unexported fields
}
ConfigWatcher watches the single system_configuration MemoryCell CR by name and keeps a parsed VoiceSessionConfig atomically up to date. It is the only place that reads voice config — no List, no tag parsing.
func NewConfigWatcher ¶
func NewConfigWatcher(memory *MemoryStore) *ConfigWatcher
NewConfigWatcher creates a ConfigWatcher that restores persisted stats into memory when the system_configuration CR is first loaded.
func (*ConfigWatcher) EventHandlerFuncs ¶
func (w *ConfigWatcher) EventHandlerFuncs() toolscache.ResourceEventHandlerFuncs
EventHandlerFuncs returns handler funcs to register on the MemoryCell informer.
func (*ConfigWatcher) Get ¶
func (w *ConfigWatcher) Get() *VoiceSessionConfig
Get returns the current config. Never nil — returns an empty config if not yet loaded.
type ConsolidationGroup ¶
type ConsolidationGroup struct {
Kind string `json:"kind"`
Keep string `json:"keep"`
KeepTitle string `json:"keep_title"`
Superseded []string `json:"superseded"`
Scores []float64 `json:"scores"`
}
ConsolidationGroup is one near-duplicate cluster found by ConsolidateMemory: Keep is the id a reader should trust going forward, Superseded lists every other member (parallel to Scores, each member's score against Keep).
type ConsolidationReport ¶
type ConsolidationReport struct {
Scanned int `json:"scanned"`
Groups []ConsolidationGroup `json:"groups"`
Superseded int `json:"superseded"`
Applied bool `json:"applied"`
}
ConsolidationReport is the outcome of one ConsolidateMemory sweep.
type GitHubOAuthConfig ¶
type GitHubOAuthConfig struct {
ClientID string
ClientSecret string
Org string
Team string // optional — if set, user must be an active team member
PublicURL string // externally reachable base URL, e.g. https://xray.example.com
// UICallbackOrigins allowlists origins a login may hand the session token
// to via ?ui=, for a UI served from a different host than this server.
// Empty (the default) disables the hand-off entirely — see resolveUIOrigin.
UICallbackOrigins []string
}
GitHubOAuthConfig holds the OAuth App credentials and org restriction.
type GraphFetcher ¶
GraphFetcher pulls a graph artifact. Nil on a CellSyncer means the registry client below; a test injects an in-process one.
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub broadcasts memory events to connected SSE clients. Clients already hold a session token gated by requireSession middleware, so the hub does not re-authenticate — but it records each client's userHash so private events (e.g. personal notes) can be delivered to their owner alone instead of being broadcast to every client. Shared events (hits, non-private changes) still fan out to all clients.
hitHook/changeHook are the replication seam for a future multi-replica deployment (ported from a sibling project's memoryhub package — see SetHitPublishHook for the full rationale). Unlike that package, this Hub is instance-based rather than a package-level singleton, so the hooks are plain struct fields/methods instead of package vars/funcs — whatever embeds a mesh transport holds the *Hub and wires its own forwarding closure onto it, with no risk of one process's hooks leaking into another's (a real concern for a package singleton, moot here).
func (*Hub) ApplyRemoteChange ¶
func (h *Hub) ApplyRemoteChange(evt MemoryChangeEvent)
ApplyRemoteChange applies a change event received from a PEER replica: the same local broadcast as PublishChange but WITHOUT invoking the change-publish hook, enforcing split horizon exactly like ApplyRemoteHit. Intended to be called only from a replication transport's inbound handler.
func (*Hub) ApplyRemoteHit ¶
func (h *Hub) ApplyRemoteHit(evt MemoryHitEvent)
ApplyRemoteHit applies a hit event received from a PEER replica: the exact same local effects as Publish (increment hitCounts, broadcast to this process's own subscribers) but WITHOUT invoking the hit-publish hook. This omission is the split-horizon half of the contract described on SetHitPublishHook — a remote apply must never be re-forwarded, or two replicas would echo the same hit back and forth forever. Intended to be called only from a replication transport's inbound handler, never from any of this package's own call sites.
func (*Hub) HitCount ¶
HitCount returns the number of times the item with the given ID has been hit by search.
func (*Hub) Publish ¶
func (h *Hub) Publish(evt MemoryHitEvent)
Publish sends an event to all connected SSE clients. Non-blocking. Also increments per-item hit counters for demand ranking. Call this for a LOCALLY-originated hit only — every existing call site already does, this is unchanged. After the local effects land, the hit-publish hook (if any) fires so a replication transport can forward the event to peer replicas — see SetHitPublishHook and ApplyRemoteHit.
func (*Hub) PublishChange ¶
func (h *Hub) PublishChange(evt MemoryChangeEvent)
PublishChange sends a memory change event to all connected SSE clients. Non-blocking. Use only for shared (non-private) items — every authenticated client receives it. Call this for a LOCALLY-originated change only. After the local broadcast, the change-publish hook (if any) fires so a replication transport can forward the event to peer replicas — see SetChangePublishHook and ApplyRemoteChange.
func (*Hub) PublishChangeTo ¶
func (h *Hub) PublishChangeTo(userHash string, evt MemoryChangeEvent)
PublishChangeTo sends a memory change event only to clients owned by userHash — the channel for private items (personal notes) whose content must not reach other users. When userHash is empty (auth disabled), it falls back to a full broadcast so the single local user still receives live updates.
func (*Hub) ServeWS ¶
ServeWS upgrades the connection and streams memory events as SSE. The channel is registered under userHash so private events (e.g. personal notes) reach only their owner; pass "" for an anonymous (broadcast-only) subscription.
func (*Hub) SetChangePublishHook ¶
func (h *Hub) SetChangePublishHook(fn func(MemoryChangeEvent))
SetChangePublishHook installs (or clears with nil) the callback invoked at the end of every LOCALLY-originated PublishChange call — the change-event analogue of SetHitPublishHook, with the identical split-horizon contract: never invoked for a change applied via ApplyRemoteChange.
PublishChangeTo (the userHash-scoped variant, used here for private items such as personal notes) deliberately has NO hook of its own, mirroring the reference implementation's choice even though the reasons differ slightly: the reference has nothing calling PublishChangeTo at all yet, so there was nothing to forward; this Hub's callers DO use PublishChangeTo today (memory.go's private-item upsert/delete paths), but MemoryChangeEvent carries no userHash on the wire — only the recipient-side subscriber map knows who owns a channel. A peer replica applying a forwarded private event via ApplyRemoteChange would have no owner to scope it to and would fall back to broadcasting it to every one of ITS subscribers, leaking the private item to users who never should have seen it. Replicating a scoped publish is only safe once the userHash travels with the event itself — a future private-scope replication feature needs that wire-format change first, not just a hook here.
func (*Hub) SetHitPublishHook ¶
func (h *Hub) SetHitPublishHook(fn func(MemoryHitEvent))
SetHitPublishHook installs (or clears with nil) the callback invoked at the end of every LOCALLY-originated Publish call — never for an event applied via ApplyRemoteHit (see that method's doc comment). That asymmetry is the split-horizon rule a replication transport needs: a hit received from a peer is delivered to this process's own SSE subscribers but never handed back to this hook, so a mesh built on top of it cannot loop a hit back and forth between replicas forever. The intended caller is whatever embeds a future replication transport (a mesh client, a pub/sub bridge, …) — it installs a closure that forwards evt onto that transport's outbound send. Nil-safe: with no hook installed (the default, and every single-replica deployment), Publish behaves exactly as it did before this method existed — zero added cost, zero behavior change.
type MCPServerRef ¶
type MCPServerRef struct {
Name string
URL string
TokenSecretName string // k8s Secret name
TokenSecretKey string // key within the Secret; defaults to "token"
Token string // resolved from SecretStore at session start; never persisted
Tools []string // allowlist of tool names; empty = all
}
MCPServerRef describes a remote MCP server to connect to at session start. Token is resolved at session start from the SecretStore, not stored in the CR.
type MapRegistrar ¶
type MapRegistrar interface {
AddSnapshotBytes(ctx context.Context, b []byte) error
RemoveSnapshotByLabel(label string)
}
MapRegistrar registers/de-registers a mental-map graph carried in a MemoryCell as an in-memory snapshot. Satisfied by *recall/engine.Registry. Optional on the CellSyncer — nil when the server runs without a recall registry (keyword mode, or --mental-maps-dir unset).
type MemoryChangeEvent ¶
type MemoryChangeEvent struct {
Op string `json:"op"` // "upsert" | "delete"
Item *MemoryItem `json:"item,omitempty"` // set for op=upsert
ID string `json:"id,omitempty"` // set for op=delete
}
MemoryChangeEvent is broadcast over SSE when a memory item is created, updated, or deleted.
type MemoryHitEvent ¶
type MemoryHitEvent struct {
ItemIDs []string `json:"itemIds"`
Query string `json:"query"`
Kind string `json:"kind,omitempty"` // the search's kind FILTER, usually empty — not a per-item kind, see Kinds
// Kinds is the per-item kind, parallel to ItemIDs (same order, same
// length) — lets a client resolve each hit's UI category (see
// ui/lib/category-config.ts categoryOf) even when the item itself isn't
// currently rendered on the bounded graph preview (MAX_RENDER_NODES caps
// what's drawn; hits land overwhelmingly on kinds like event/
// active_concern that dwarf the per-kind render budget, so without this
// the flash effect was a no-op for the overwhelming majority of hits).
// Purely additive (`omitempty`; empty/absent on the wire when a caller
// passes nil): an older client ignores an unknown field. A caller MUST
// fill it from the same items it derived ItemIDs from, in the same
// order. A client that finds Kinds absent or shorter than ItemIDs must
// fall back to cell-only behavior rather than index out of range.
Kinds []string `json:"kinds,omitempty"`
}
MemoryHitEvent is broadcast over SSE whenever search_memory is called.
type MemoryItem ¶
type MemoryItem struct {
ID string `json:"id"`
Kind string `json:"kind"`
Scope string `json:"scope"`
Subject string `json:"subject,omitempty"`
Title string `json:"title"`
Content string `json:"content"`
Confidence string `json:"confidence,omitempty"`
Tags []string `json:"tags,omitempty"`
Status string `json:"status"`
// EventDate is only set for kind=event items. RFC3339.
EventDate string `json:"eventDate,omitempty"`
// Data holds arbitrary named blobs, e.g. Data["image"] for thumbnails.
Data map[string]mcell.DataEntry `json:"data,omitempty"`
// ImageURL is set server-side to /v1/memory/{id}/image when Data["image"] is present.
ImageURL string `json:"imageUrl,omitempty"`
// StaleAfter is the item's expiry deadline (decay by default). nil means no
// deadline — the item never auto-expires via the retention sweep
// (StartRetentionSweep). Stamped from a compiled per-kind default on CREATE
// only (see defaultTTLByKind); an update never restamps or clears it
// implicitly — renewal is an explicit StaleAfter set by the writer. Mirrors
// MemoryCellSpec.StaleAfter (cellToItem / CellSyncer.Persist thread it both
// directions).
StaleAfter *time.Time `json:"staleAfter,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
// Links mirrors MemoryCellSpec.Links (ThoughtLink — an existing CR field,
// not new schema) for CR-backed items, so Search's optional 1-hop
// expansion (SetLinkExpansion, docs/todo/graphiti-comparison-refinement.md
// item #4) can walk it without a second read. Populated by cellToItem;
// ephemeral (s.items-only) items carry no Links since nothing currently
// writes them outside the CR path.
Links []mcell.ThoughtLink `json:"links,omitempty"`
// Origin is the cell's provenance (ADR-053): observed, derived, human or
// verified. A cell stored without one reads as derived.
Origin string `json:"origin,omitempty"`
// EvidenceRefs names the cells or events this item was drawn from. Refute
// follows them: a derived cell that cites a refuted one is retired with it.
EvidenceRefs []string `json:"evidenceRefs,omitempty"`
// Supersedes lists the ids of cells this one absorbed (ConsolidateMemory).
Supersedes []string `json:"supersedes,omitempty"`
// Via is set only on an item appended by Search's ThoughtLink expansion —
// the ID of the higher-ranked result whose link surfaced it. Empty for
// every directly matched result. Render/annotation-only: never read back
// by Upsert, so a caller cannot set or spoof it.
Via string `json:"via,omitempty"`
// Collapsed is set only on a Search result that stands for near-duplicates
// folded into it (see collapseNearDuplicates). Render-only, like Via.
Collapsed int `json:"collapsed,omitempty"`
}
MemoryItem is a single awareness memory cell stored in the server.
type MemoryItemSummary ¶
type MemoryItemSummary struct {
ID string `json:"id"`
Kind string `json:"kind"`
Scope string `json:"scope"`
Subject string `json:"subject,omitempty"`
Title string `json:"title"`
Tags []string `json:"tags,omitempty"`
Status string `json:"status"`
UpdatedAt time.Time `json:"updatedAt"`
HitCount int64 `json:"hitCount"`
}
MemoryItemSummary is a lightweight view of a MemoryItem — no content or image data.
type MemoryNeighbor ¶
type MemoryNeighbor struct {
ID string `json:"id"`
Kind string `json:"kind"`
Title string `json:"title"`
Status string `json:"status"`
Tags []string `json:"tags,omitempty"`
Score float32 `json:"score"`
}
MemoryNeighbor is one neighbour of a cell, without its content.
type MemoryNeighborEdge ¶
type MemoryNeighborEdge struct {
Source string `json:"source"`
Target string `json:"target"`
Score float32 `json:"score"`
}
MemoryNeighborEdge links two neighbours that are tighter to each other than to the centre.
type MemoryNeighborhood ¶
type MemoryNeighborhood struct {
ID string `json:"id"`
Indexed bool `json:"indexed"`
Neighbors []MemoryNeighbor `json:"neighbors"`
Edges []MemoryNeighborEdge `json:"edges"`
}
MemoryNeighborhood is a cell's semantic neighbourhood. Indexed is false when the cell has no embedding (keyword-only mode, a private cell, or not embedded yet), which is different from an indexed cell with no neighbours.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an HNSW-backed semantic memory store. When an Embedder is provided, search_memory uses vector similarity. Without an embedder it falls back to keyword matching (for dev/testing).
CR-backed items are read directly from the controller-runtime cache (reader/namespace). Ephemeral items written via Upsert (agent_note, session writes) live only in items map.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore creates a keyword-only MemoryStore (no embedding endpoint).
func NewMemoryStoreWithEmbedder ¶
func NewMemoryStoreWithEmbedder(embedder *vectorstore.Embedder, logger zerolog.Logger) *MemoryStore
NewMemoryStoreWithEmbedder creates an HNSW-backed MemoryStore.
func (*MemoryStore) AddUserTokens ¶
func (s *MemoryStore) AddUserTokens(userHash string, n int64)
AddUserTokens accumulates Gemini Live token spend for an authenticated user on the current UTC day. Empty userHash (unauthenticated/dev sessions) is ignored — there is no stable key to meter. Rolls the counter over when the UTC day changes.
func (*MemoryStore) AllItems ¶
func (s *MemoryStore) AllItems(subject string) []*MemoryItem
AllItems returns all active memory items for the given subject. Used by get_knowledge_context to pre-load full context at session start.
func (*MemoryStore) ConsolidateMemory ¶
func (s *MemoryStore) ConsolidateMemory(subject string, apply bool) (ConsolidationReport, []*MemoryItem)
ConsolidateMemory groups the active public cells of dedupKinds (optionally of one subject) into near-duplicate clusters and, when apply is true, retires every member but each cluster's keeper (status superseded, the keeper's Supersedes lists them). apply=false returns the same groups and writes nothing. The second result is the changed cells, which a caller backed by the cluster persists.
func (*MemoryStore) Context ¶
func (s *MemoryStore) Context(anchor string, maxDepth int) (anchors []*MemoryItem, activated []ScoredItem)
Context returns the cells an anchor activates: the anchor cells themselves and everything reachable over ThoughtLinks, by spreading activation. Each hop multiplies the score by decay and the link's weight, a cell keeps the best score any path gave it, and at most maxDepth hops are followed (default 3, at most 8). Only shared, active cells take part: the traversal carries no caller identity to test a private cell's owner against.
func (*MemoryStore) Count ¶
func (s *MemoryStore) Count() int
Count returns the number of active items visible to the store.
func (*MemoryStore) Delete ¶
func (s *MemoryStore) Delete(id string) bool
Delete removes a memory item by ID. Returns false if not found in the ephemeral map. Always publishes a delete SSE event so CR-backed items (e.g. agent_note) are removed from the graph even when they are not present in s.items.
func (*MemoryStore) EmbedMetrics ¶
func (s *MemoryStore) EmbedMetrics() vectorstore.EmbedMetrics
EmbedMetrics returns embedding cache/call counters, or zero if no embedder configured.
func (*MemoryStore) Expire ¶
func (s *MemoryStore) Expire(id string) *MemoryItem
Expire retires a cell without a verdict on it: it is out of date, not wrong (Refute is for wrong). The cell is kept with status stale and leaves every index. The returned item is already stored in the process; a caller backed by the cluster persists it. nil when id names no cell.
func (*MemoryStore) ExportAll ¶
func (s *MemoryStore) ExportAll() []*MemoryItem
ExportAll returns a copy of every item the in-process store holds, private and retired ones included, for a program that persists the store itself.
func (*MemoryStore) FilterItems ¶
func (s *MemoryStore) FilterItems(subject, kind string) []*MemoryItem
FilterItems returns active memory items filtered by subject and/or kind. Unlike AllItems, private notes are included when kind="note".
func (*MemoryStore) FlushStats ¶
func (s *MemoryStore) FlushStats()
FlushStats marks stats dirty so the next flush tick persists them. Exported so the session store can request a flush on login/logout (wired via SetSessionPersistence). It no longer writes synchronously — see ADR-047 (debounced stats flush). The change lands on the next StartStatsFlusher tick.
func (*MemoryStore) Get ¶
func (s *MemoryStore) Get(id string) *MemoryItem
Get returns a single active memory item by ID, or nil if not found.
func (*MemoryStore) GetItem ¶
func (s *MemoryStore) GetItem(id string) *MemoryItem
GetItem returns a memory item by ID, or nil if not found. Checks the cache first (for CR-backed items), then the ephemeral map.
func (*MemoryStore) KeywordSearch ¶
func (s *MemoryStore) KeywordSearch(query, kind, subject string, limit int) []*MemoryItem
KeywordSearch scans items by substring match on title+content. When the cache reader is available, queries the controller-runtime cache for CR-backed items (using field indexes for kind/subject); ephemeral items from the in-process map are merged in.
func (*MemoryStore) KeywordSearchPerKind ¶
func (s *MemoryStore) KeywordSearchPerKind(query, kind, subject string, limit, perKind int) []*MemoryItem
KeywordSearchPerKind is KeywordSearch with per-kind fairness: at most perKind results per kind, so a dominant kind can't monopolise the flat limit and starve smaller categories. Relevance ordering within each kind is preserved and limit still caps the total. Used by the graph search (/v1/memory?q=), which spans all kinds over a skewed store.
func (*MemoryStore) KnowledgeIndex ¶
func (s *MemoryStore) KnowledgeIndex(subject string) []string
KnowledgeIndex returns a compact list of titles available for the given subject.
func (*MemoryStore) List ¶
func (s *MemoryStore) List(kind, scope, subject string, limit int) []*MemoryItem
List returns active items filtered by kind/scope/subject up to limit. Uses the cache when available; falls back to items map.
func (*MemoryStore) ListRecent ¶
func (s *MemoryStore) ListRecent(kind, scope, subject string, limit int) []*MemoryItem
ListRecent returns active items matching kind/scope/subject sorted newest-first by CreatedAt (UpdatedAt breaks ties), capped to limit. Unlike List, the result is time-ordered — this is the recall path for episodic ("what happened last") memory, where "the latest creation time" is the selection criterion. ADR-020.
func (*MemoryStore) ListSummary ¶
func (s *MemoryStore) ListSummary(kind, scope, subject string, limit int) (items []MemoryItemSummary, total int)
ListSummary returns lightweight summaries of active items filtered by kind/scope/subject. Total is the count of all matching active items (ignoring limit).
func (*MemoryStore) ListSummaryPerKind ¶
func (s *MemoryStore) ListSummaryPerKind(perKind int) (items []MemoryItemSummary, total int, totalsByKind map[string]int)
ListSummaryPerKind returns a bounded graph preview: up to perKind active items for EACH kind (not a global cap), so every category is represented on the graph even when one kind dwarfs the rest. total is the true count of all matching active items, independent of perKind — the UI shows it as the real store size while rendering only the preview. perKind <= 0 means no per-kind cap (return everything). Private/system_configuration cells are excluded (the shared graph is public-only); scope/subject filters still apply.
func (*MemoryStore) Metrics ¶
func (s *MemoryStore) Metrics() SearchMetrics
Metrics returns a snapshot of search operation counters.
func (*MemoryStore) Neighborhood ¶
func (s *MemoryStore) Neighborhood(id string, top int) *MemoryNeighborhood
Neighborhood returns the semantic k-NN neighbourhood of the cell id from the embedding index (top is clamped to 1..50, default 20). Only shared, active neighbours appear: a private cell is never embedded, and the filter keeps one out should it ever be.
func (*MemoryStore) PublishHit ¶
func (s *MemoryStore) PublishHit(itemIDs []string, kind string, kinds []string)
PublishHit broadcasts a memory search hit event to SSE subscribers. kinds must be parallel to itemIDs (same order, same length) — see MemoryHitEvent.Kinds for why: it's what lets the UI flash a hit's category region even when the hit item itself isn't one of the currently-rendered graph nodes.
The event reaches every connected client, so it carries ids and kinds only: the query is what a person asked and is not theirs to share, and callers pass shared items only (see sharedHit).
func (*MemoryStore) Refute ¶
func (s *MemoryStore) Refute(ctx context.Context, id, reason string, evidence []string) (changed []*MemoryItem, found bool)
Refute marks a cell wrong (ADR-053): it records {reason, evidence, at} under Data["refuted"], retires the cell (status stale) and retires every ACTIVE derived cell that cites it, one hop only: an evidence ref naming the id or a link to it. Observed, human and verified cells are never cascaded. The returned items are the changed cells, the refuted one first, already stored in the process; a caller backed by the cluster persists each. found is false when id names no cell.
func (*MemoryStore) Restore ¶
func (s *MemoryStore) Restore(ctx context.Context, it *MemoryItem)
Restore puts an item back as it was exported: ids, origin, status, links, timestamps and deadline are kept as given. An active shared item is indexed again (keyword and, with an embedder, semantic); a retired or private one is held and left out of the shared indexes, exactly as Upsert would leave it.
func (*MemoryStore) RestoreStats ¶
func (s *MemoryStore) RestoreStats(st *mcell.RuntimeStats)
RestoreStats loads persisted counters from a RuntimeStats snapshot (e.g. read by ConfigWatcher at startup). voiceActive is intentionally not restored — it is session-local and always starts at 0.
func (*MemoryStore) Search ¶
func (s *MemoryStore) Search(ctx context.Context, query, kind, subject string, limit int) []*MemoryItem
Search returns items matching the query (ADR-030). It runs lexical search first; when a semantic store is configured and the top lexical hit is not a confident (full-coverage) match, it fuses semantic results in by Reciprocal Rank Fusion. Without an embedder it returns lexical results directly.
func (*MemoryStore) SetCacheReader ¶
func (s *MemoryStore) SetCacheReader(r client.Reader, namespace string)
SetCacheReader wires the controller-runtime cache reader so List/KeywordSearch queries go to the cache instead of the in-process items map for CR-backed items. Call this after the manager cache has synced.
func (*MemoryStore) SetCandidateFanout ¶
func (s *MemoryStore) SetCandidateFanout(n int)
SetCandidateFanout overrides the over-fetch factor Search multiplies limit by when a second-stage step (source collapse) is going to shrink the candidate pool again — see candidateFanout. It exists because source collapse changes the pool's unit: Search's channels rank *facts*, but a caller wants *sources* (episodes) back, and one source can dominate the pool with many derived facts. On LoCoMo, a 10-turn episode yields roughly 26 fact candidates; a 3×limit pool of facts (the candidateFanout default) collapses to only ~7 distinct episodes for limit=20 — nowhere near enough to fill the request. A wider pool (10x) is needed so enough distinct sources survive the collapse to fill limit. This is cheap to buy: the over-fetch cost is paid entirely by the in-process HNSW/keyword search, not a network round-trip. Values below 1 are treated as 1 (Search never over-fetches less than limit itself). Safe to call at any time; takes effect on the next Search call.
func (*MemoryStore) SetEpisodicTTL ¶
func (s *MemoryStore) SetEpisodicTTL(ttl time.Duration)
SetEpisodicTTL sets the max age before an episodic memory is forgotten. 0 disables expiry. Call once at startup before serving.
func (*MemoryStore) SetLexicalShortCircuit ¶
func (s *MemoryStore) SetLexicalShortCircuit(on bool)
SetLexicalShortCircuit turns Search's lexical confident short-circuit on or off (see MemoryStore.alwaysFuse). The library default (an unconfigured MemoryStore) is on=true: a full-coverage top lexical hit returns the lexical list without ever consulting the semantic channel. on=false makes Search always fuse lexical with semantic by RRF whenever a semantic store is configured — the lexical-only return still happens when there is no semantic store at all (s.cells == nil), since there is nothing to fuse with. cmd/xray, the xray binary, calls SetLexicalShortCircuit(false) at startup unless --search-lexical-shortcircuit is passed, so the binary's product default differs from this library default; embedders of internal/xray that never call this setter keep getting on=true.
Why this needs to be switchable: the short-circuit is the right call for short named-entity voice queries ("Billing RAG project") — one confident lexical hit, no reason to pay for a Vertex AI round-trip. It is the wrong call for long natural-language questions asked over long cells: a 10-turn LoCoMo episode shares tokens with almost any question about it, so the top lexical hit reads as "confident" by coverage even when it isn't the actual answer. Measured on the LoCoMo eval set, that false confidence returned a single lexical hit — and skipped semantic retrieval entirely — for 41% of questions. SetLexicalShortCircuit(false) is the escape hatch for that regime. Safe to call at any time; takes effect on the next Search call.
func (*MemoryStore) SetLinkExpansion ¶
func (s *MemoryStore) SetLinkExpansion(on bool)
SetLinkExpansion turns Search's optional ThoughtLink 1-hop expansion on or off (default off — see docs/todo/graphiti-comparison-refinement.md item #4). Safe to call at any time; takes effect on the next Search call.
func (*MemoryStore) SetLinks ¶
func (s *MemoryStore) SetLinks(id string, links []mcell.ThoughtLink) bool
SetLinks replaces the ThoughtLinks of an ephemeral (s.items) item. CR-backed items get their links from the cell spec via cellToItem; this is the write path for links on items that were never persisted — an ingest pipeline deriving atomic-fact cells from an episode cell links each fact back to its source with it. Returns false when id is not an ephemeral item.
func (*MemoryStore) SetRecallShaping ¶
func (s *MemoryStore) SetRecallShaping(on bool)
SetRecallShaping turns the near-duplicate collapse and state-aware re-rank on or off (default on). With it off, Search returns the fused order unchanged. Safe to call at any time.
func (*MemoryStore) SetSemanticScoreGap ¶
func (s *MemoryStore) SetSemanticScoreGap(gap float32)
SetSemanticScoreGap overrides the semantic relevance cutoff (absolute cosine gap below the top hit) on the underlying cell store. No-op in keyword-only mode.
func (*MemoryStore) SetSourceCollapse ¶
func (s *MemoryStore) SetSourceCollapse(relation string)
SetSourceCollapse turns on Search's source-collapse step for links of the given relation (see MemoryStore.sourceRel); "" turns it off. Safe to call at any time; takes effect on the next Search call.
func (*MemoryStore) SetWriteDedup ¶
func (s *MemoryStore) SetWriteDedup(on bool)
SetWriteDedup turns write-side near-duplicate merging on or off (default on): an Upsert that restates an existing stable_fact, operational_pattern, watch_item or hypothesis lands on that cell instead of minting a peer.
func (*MemoryStore) SetWriter ¶
func (s *MemoryStore) SetWriter(c client.Client)
SetWriter wires the controller-runtime client so flushStats can update CR status.
func (*MemoryStore) StartEpisodicReaper ¶
func (s *MemoryStore) StartEpisodicReaper(ctx context.Context, syncer *CellSyncer, every time.Duration)
StartEpisodicReaper runs a background sweep that deletes episodic memories older than episodicTTL (the "forget" mechanism, ADR-020). It mirrors StartStatsFlusher: one goroutine, a ticker, exits on ctx cancel. A no-op when TTL<=0 or every<=0. syncer removes the backing CR (nil → in-memory only).
func (*MemoryStore) StartRetentionSweep ¶
func (s *MemoryStore) StartRetentionSweep(ctx context.Context, syncer *CellSyncer, every time.Duration)
StartRetentionSweep runs a background sweep that hard-deletes memory items past their StaleAfter deadline — decay by default, the general counterpart to StartEpisodicReaper's episodic-only TTL. Mirrors StartEpisodicReaper's structure: one goroutine, a ticker, exits on ctx cancel. A no-op when every<=0.
func (*MemoryStore) StartStatsFlusher ¶
func (s *MemoryStore) StartStatsFlusher(ctx context.Context, every time.Duration)
StartStatsFlusher runs the single CR-status writer: every `every` it writes the current counters + sessions to the system_configuration CR, but only when something changed since the last write (markStatsDirty). This replaces the old per-request synchronous flush, so search/voice/login throughput is no longer gated on an etcd round-trip and N concurrent requests cause at most one write per tick. On ctx cancellation it does a final flush so the last state is durable across a graceful shutdown. Call once at startup after SetWriter. A zero or negative interval disables the loop (in-memory only).
func (*MemoryStore) Supersede ¶
func (s *MemoryStore) Supersede(ctx context.Context, oldID, newID, kind, scope, title, content, subject, confidence string, tags []string, opts ...UpsertOption) (changed []*MemoryItem, errMsg string)
Supersede replaces the cell oldID with a new one: the new cell is written (as Upsert writes it, so origin, dedup-free id handling and size caps apply) and lists oldID in Supersedes, and the old cell is retired with status superseded. A derived write cannot supersede a human cell. The returned items are the new cell then the old one, both already stored in the process.
func (*MemoryStore) ToolMetrics ¶
func (s *MemoryStore) ToolMetrics() map[string]any
ToolMetrics returns per-tool call counts and average latency in milliseconds.
func (*MemoryStore) TrackKWTrigramHit ¶
func (s *MemoryStore) TrackKWTrigramHit()
TrackKWTrigramHit records that the trigram index contributed at least one candidate to a search result.
func (*MemoryStore) TrackKWVoiceHit ¶
func (s *MemoryStore) TrackKWVoiceHit()
TrackKWVoiceHit records that the keyword-first path in the voice search_memory tool returned enough results to skip semantic search.
func (*MemoryStore) TrackKWVoiceMiss ¶
func (s *MemoryStore) TrackKWVoiceMiss()
TrackKWVoiceMiss records that keyword-first returned too few results and fell through to semantic search.
func (*MemoryStore) TrackToolCall ¶
func (s *MemoryStore) TrackToolCall(name string, d time.Duration)
TrackToolCall records a tool invocation and its execution duration.
func (*MemoryStore) TrackVoiceCall ¶
func (s *MemoryStore) TrackVoiceCall()
TrackVoiceCall records a new voice session start and marks stats dirty.
func (*MemoryStore) TrackVoiceEnd ¶
func (s *MemoryStore) TrackVoiceEnd()
TrackVoiceEnd records a voice session end and marks stats dirty.
func (*MemoryStore) Upsert ¶
func (s *MemoryStore) Upsert(ctx context.Context, id, kind, scope, title, content, subject, confidence, eventDate string, data map[string]mcell.DataEntry, tags []string, opts ...UpsertOption) (UpsertResult, string)
Upsert creates or updates a memory item matched by ID (if provided) or auto-generates one. The write's provenance comes from ctx (withWriteOrigin): an identified person is human, anything else derived. A restated stable_fact, operational_pattern, watch_item or hypothesis lands on the cell it repeats (status "merged").
func (*MemoryStore) UserTokensToday ¶
func (s *MemoryStore) UserTokensToday(userHash string) int64
UserTokensToday returns the user's token spend for the current UTC day, or 0 if the stored counter is from a previous day (not yet rolled over) or the user is unknown.
func (*MemoryStore) VoiceMetrics ¶
func (s *MemoryStore) VoiceMetrics() map[string]any
VoiceMetrics returns a snapshot of voice session counters.
type QuotaUsage ¶
type QuotaUsage struct {
Used int64 `json:"used"`
Limit int64 `json:"limit"`
Remaining int64 `json:"remaining"`
Breakdown *TokenBreakdown `json:"breakdown,omitempty"`
}
QuotaUsage is the per-user daily token quota snapshot pushed to the browser. Used/Limit/Remaining are the daily cap (total tokens). Breakdown is the current session's cumulative per-modality split — what the UI shows to explain where the tokens go (audio dominates cost), since the daily store keeps only a fused total.
type ScoredItem ¶
type ScoredItem struct {
Item *MemoryItem
Score float32
}
ScoredItem is a cell reached by Context and how strongly.
type SearchMetrics ¶
type SearchMetrics struct {
TotalSearches int64 `json:"totalSearches"`
KeywordHits int64 `json:"keywordHits"`
KWVoiceHits int64 `json:"kwVoiceHits"`
KWVoiceMisses int64 `json:"kwVoiceMisses"`
KWTrigramHits int64 `json:"kwTrigramHits"`
SemanticCalls int64 `json:"semanticCalls"`
SemanticFallbacks int64 `json:"semanticFallbacks"`
}
SearchMetrics tracks search operation counters since process start.
type SecretStore ¶
type SecretStore struct {
// contains filtered or unexported fields
}
SecretStore holds Secret data watched via the controller-runtime cache. It is safe for concurrent use.
func NewSecretStore ¶
func NewSecretStore() *SecretStore
func (*SecretStore) Delete ¶
func (s *SecretStore) Delete(namespace, name string)
func (*SecretStore) Get ¶
func (s *SecretStore) Get(namespace, name, key string) string
Get returns the value for a key in a Secret. Returns empty string if not found.
func (*SecretStore) SecretEventHandlerFuncs ¶
func (s *SecretStore) SecretEventHandlerFuncs() toolscache.ResourceEventHandlerFuncs
SecretEventHandlerFuncs returns informer handlers that keep the store in sync.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the X-Ray HTTP server.
func NewServer ¶
func NewServer(logger zerolog.Logger, memory *MemoryStore, uiFS fs.FS, ghCfg GitHubOAuthConfig, azCfg AzureOAuthConfig, allowedOrigin string, maxBodyBytes int64, config *ConfigWatcher) *Server
func (*Server) SetMCPAuth ¶
SetMCPAuth mounts the MCP resource endpoint and the co-located OAuth 2.1 authorization server on the main mux. auth provides discovery + token issuance; mcpHandler is the bearer-guarded MCP JSON-RPC handler (built via NewMCPServer with the same authorizer). Either argument may be nil to skip. Must be called before the server starts accepting connections.
func (*Server) SetVoiceManager ¶
func (s *Server) SetVoiceManager(vm *VoiceManager)
SetVoiceManager attaches a VoiceManager and registers the /v1/voice route. Must be called before the server starts accepting connections.
type TokenBreakdown ¶
type TokenBreakdown struct {
InputAudio int64 `json:"inputAudio"`
InputText int64 `json:"inputText"`
OutputAudio int64 `json:"outputAudio"`
OutputText int64 `json:"outputText"`
Cached int64 `json:"cached"`
}
TokenBreakdown is the per-modality token split for the current voice session.
type UpsertOption ¶
type UpsertOption func(*upsertOpts)
UpsertOption sets an optional field of an Upsert.
func WithEvidenceRefs ¶
func WithEvidenceRefs(refs []string) UpsertOption
WithEvidenceRefs records the cells or events the item was drawn from. Refute follows them.
type UpsertResult ¶
type UpsertResult struct {
ID string
Status string
Item *MemoryItem
}
type VoiceBrowserMessage ¶
type VoiceBrowserMessage struct {
Audio []byte `json:"audio,omitempty"`
AudioStreamEnd bool `json:"audioStreamEnd,omitempty"`
Text string `json:"text,omitempty"`
Ping bool `json:"ping,omitempty"`
Pinned []string `json:"pinned,omitempty"` // live update of pinned item IDs
}
VoiceBrowserMessage is the JSON envelope from the browser.
type VoiceManager ¶
type VoiceManager struct {
// contains filtered or unexported fields
}
VoiceManager manages active voice sessions. Lazily initialises the genai client.
func NewVoiceManager ¶
func NewVoiceManager(memory *MemoryStore, config *ConfigWatcher, secrets *SecretStore, syncer *CellSyncer, namespace, gcpProject, gcpLocation, geminiKey, voiceModel, voiceName string, userDailyAudioTokenLimit int64, maxOutputTokens, headroomLimit int, affectiveDialog, proactiveAudio bool, allowedOrigin string, maxConcurrent int, logger zerolog.Logger) *VoiceManager
NewVoiceManager creates a VoiceManager. syncer may be nil when k8s is unavailable. allowedOrigin scopes the WebSocket Origin check (empty = allow any, dev only). maxConcurrent caps simultaneous sessions (0 = unlimited).
func (*VoiceManager) ServeHTTP ¶
func (m *VoiceManager) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP upgrades the request to a WebSocket and runs a voice session.
func (*VoiceManager) SetMemMaps ¶
func (m *VoiceManager) SetMemMaps(deps *recallmcp.Deps)
SetMemMaps wires the embedded code-graph snapshot into voice sessions so the model gets search_graph et al. as NATIVE in-process tools (direct function calls — no MCP). No-op effect when deps is nil. Call before serving.
func (*VoiceManager) Shutdown ¶
func (m *VoiceManager) Shutdown()
Shutdown closes all active sessions.
func (*VoiceManager) UserQuota ¶
func (m *VoiceManager) UserQuota(rawUserID string) (used, limit, remaining int64, ok bool)
UserQuota reports the per-user daily token quota for the given raw SSO ID. limit is the effective cap (0 = unlimited); used is today's spend; remaining is limit-used floored at 0. ok is false when there is no cap to report — either no limit is configured or the user is unauthenticated (empty rawUserID).
func (*VoiceManager) VoiceMetrics ¶
func (m *VoiceManager) VoiceMetrics() map[string]any
VoiceMetrics returns lifetime voice session counters.
type VoiceServerMessage ¶
type VoiceServerMessage struct {
Audio []byte `json:"audio,omitempty"`
Transcript string `json:"transcript,omitempty"`
InputTranscript string `json:"inputTranscript,omitempty"`
ToolCall string `json:"toolCall,omitempty"`
ToolResult string `json:"toolResult,omitempty"`
Interrupt bool `json:"interrupt,omitempty"`
Error string `json:"error,omitempty"`
Done bool `json:"done,omitempty"`
Quota *QuotaUsage `json:"quota,omitempty"` // pushed per turn while a daily limit is in effect
}
VoiceServerMessage is the JSON envelope sent to the browser.
type VoiceSession ¶
type VoiceSession struct {
// contains filtered or unexported fields
}
VoiceSession is one active browser ↔ Gemini Live session.
func (*VoiceSession) Close ¶
func (s *VoiceSession) Close()
Close cancels the session and waits for cleanup.
type VoiceSessionConfig ¶
type VoiceSessionConfig struct {
SystemPrompt string
Greeting string
VoiceName string
VoiceModel string
EnabledTools []string
MCPServers []MCPServerRef
WriteAllowedUsers []string // usernames permitted to invoke write tools via voice
// MaxUserDailyAudioTokens caps per-user Gemini Live AUDIO token spend (audio-in
// + audio-out) per UTC day. 0 means "not set in CR" — the caller falls back to
// the --user-daily-audio-token-limit flag. See ADR-042.
MaxUserDailyAudioTokens int64
// MaxItemContentChars caps each search-result item's content (headroom
// LossyCompress budget). 0 means "not set in CR" — the caller falls back to the
// --headroom-content-limit flag. See ADR-038.
MaxItemContentChars int
// AffectiveDialog / ProactiveAudio are native-audio quality toggles. nil means
// "not set in CR" — the caller falls back to the corresponding flag.
AffectiveDialog *bool
ProactiveAudio *bool
// Categories is the UI display category taxonomy (ADR-035 item 4). Empty
// means the UI falls back to its in-repo default taxonomy.
Categories []CategoryConfig
}
VoiceSessionConfig is the parsed, typed voice session configuration loaded from the system_configuration MemoryCell CR.