Help us shape the future of Go! Take the 2026 Go Developer Survey

xray

package
v2.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: Apache-2.0 Imports: 66 Imported by: 0

Documentation

Index

Constants

View Source
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).

View Source
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.

View Source
const SystemConfigCRName = "xray-system-configuration"

Variables

This section is empty.

Functions

func MCPSigningSeed

func MCPSigningSeed(secrets ...string) []byte

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:

  1. an X-Ray-issued OAuth JWT (when auth != nil) — carries per-user scopes;
  2. 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

func ValidEventDate(s string) bool

ValidEventDate reports whether s is an event date the store can render back: RFC3339 or a bare YYYY-MM-DD date.

func ValidateSearchKind

func ValidateSearchKind(kind string) string

ValidateSearchKind rejects a kind filter that names no searchable kind. An empty filter means every kind and is valid.

func ValidateWrite

func ValidateWrite(kind, confidence string) string

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.

func WithOrigin

func WithOrigin(ctx context.Context, origin string) context.Context

WithOrigin returns ctx carrying the provenance of the caller. Upsert reads it: a write made as OriginHuman is never replaced by a derived one.

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

type CellSyncer struct {
	client.Client
	// contains filtered or unexported fields
}

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) Reconcile

func (s *CellSyncer) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

Reconcile handles create/update/delete of MemoryCell CRs.

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

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

type GraphFetcher func(ctx context.Context, ref name.Digest, kc authn.Keychain) ([]byte, error)

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

func (h *Hub) HitCount(id string) int64

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

func (h *Hub) ServeWS(w http.ResponseWriter, r *http.Request, userHash string)

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 (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.

func (*SecretStore) Set

func (s *SecretStore) Set(namespace, name string, data map[string][]byte)

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) Handler

func (s *Server) Handler() http.Handler

func (*Server) SetMCPAuth

func (s *Server) SetMCPAuth(auth *mcpAuthorizer, mcpHandler http.Handler)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL