Documentation
¶
Overview ¶
Package inmemory is an in-memory store for development and tests: no PostgreSQL. It keeps only what the contract needs (insert a finalized record, read one back, the text search, when each record was last injected, the embeddings of contract 1.6 with their nearest-neighbour search, and the lessons view's read of contract 1.7), honouring scope and the authorization bounds the way the PostgreSQL store's row-level security does. Its text search is the .NET in-memory store's: words normalized to Unicode form KC and lower-cased, the English stopwords PostgreSQL drops left out of a query, and a record matching at least the minimum number of query terms (fewer for a shorter query), weighted 3 for the task summary and 1 each for the task ID and the lesson. Nothing survives the process.
It is its own module, so the core requires no module: Unicode normalization is golang.org/x/text.
Index ¶
- Constants
- Variables
- func CosineDistance(left, right []float32) float64
- func Permits(bounds ae.AuthorizationBounds, scope ae.Scope) bool
- func QueryTerms(text string) []string
- type Embedding
- type Event
- type HoldoutRow
- type Injection
- type Store
- func (s *Store) Embedding(id ae.UUID) (Embedding, bool)
- func (s *Store) Erase(id ae.UUID) bool
- func (s *Store) Event(id ae.UUID) (Event, bool)
- func (s *Store) Get(_ context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, id ae.UUID) (*ae.ExperienceRecord, error)
- func (s *Store) HoldoutRows() []HoldoutRow
- func (s *Store) Injections() map[ae.UUID]Injection
- func (s *Store) InsertFinalized(ctx context.Context, bounds ae.AuthorizationBounds, ...) error
- func (s *Store) InsertRecord(ctx context.Context, bounds ae.AuthorizationBounds, ...) error
- func (s *Store) ListLessonRecords(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, ...) (ae.LessonRecords, error)
- func (s *Store) RecordHoldoutAssignment(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, ...) error
- func (s *Store) RecordInjection(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, ...) error
- func (s *Store) Records() []ae.ExperienceRecord
- func (s *Store) Search(ctx context.Context, bounds ae.AuthorizationBounds, ...) ([]ae.SearchRow, error)
- func (s *Store) SearchVectors(ctx context.Context, bounds ae.AuthorizationBounds, query ae.VectorQuery) (ae.VectorSearchResult, error)
- func (s *Store) SetInjection(id ae.UUID, injection Injection)
- func (s *Store) WriteEmbedding(ctx context.Context, bounds ae.AuthorizationBounds, write ae.EmbeddingWrite) (ae.EmbeddingWriteResult, error)
Constants ¶
const MaxIndexedLength = 100_000
MaxIndexedLength is the most text indexed per record, in UTF-16 code units, across its task ID, task summary and lesson in that order: the bound the PostgreSQL store's search column applies.
Variables ¶
var ErrAuthorizationDenied = ae.ErrAuthorizationDenied
ErrAuthorizationDenied is a record whose scope lies outside the authorization bounds: the core's agentexperience.ErrAuthorizationDenied, so errors.Is matches either.
Functions ¶
func CosineDistance ¶
CosineDistance is 1 - cos of two vectors of equal length, clamped to [0, 2]: the components are 32-bit floats, the arithmetic double. NaN when either has no magnitude (a zero vector).
func Permits ¶
func Permits(bounds ae.AuthorizationBounds, scope ae.Scope) bool
Permits reports whether bounds permit scope: the tenant always, and every bounded field exactly.
func QueryTerms ¶
QueryTerms are the distinct terms a query searches for: its words, less stopwords and words shorter than two characters. Empty when nothing is left, and then nothing matches.
Types ¶
type Embedding ¶
type Embedding struct {
ModelID string
Dimension int
ContentHash string
SourceRevision int64
Vector []float32
}
Embedding is a record's stored embedding (contract 1.6): the model, the dimension, the content hash of the summary that was embedded, the record revision it was read at, and the vector, its components 32-bit floats.
type Event ¶
type Event struct {
Event ae.InitialLifecycleEvent
Actor string
RecordedAt time.Time
}
Event is a record's initial lifecycle event as InsertFinalized stored it, with its actor and recording time.
type HoldoutRow ¶
type HoldoutRow struct {
RunID ae.UUID
ExperienceID ae.UUID
Revision int64
Arm ae.HoldoutArm
Scope ae.Scope
AssignedAt ae.Instant
}
HoldoutRow is one holdout assignment row (contract 1.8): a run, a record its block named at the revision it was rendered from, the arm, the record's own scope, and when, as agent_experience.experience_holdout_assignments holds it.
type Injection ¶
Injection is when a record was last injected in its own scope, and how many injected blocks delivered it.
type Store ¶
type Store struct {
// Clock is the store's own clock, which an injection's instant is clamped to, as PostgreSQL clamps it to its own;
// nil reads agentexperience.Now. Set it before the store is used.
Clock func() ae.Instant
// contains filtered or unexported fields
}
Store holds records in memory, for development and tests. It is safe for concurrent use. The zero value is not usable: make one with New.
func New ¶
func New(records ...ae.ExperienceRecord) (*Store, error)
New is a store holding records (each experience ID once).
func (*Store) Embedding ¶
Embedding is a record's stored embedding, for tests; ok is false when it has none.
func (*Store) Erase ¶
Erase erases a record, as the .NET erasure (agent_experience.purge_experience_record) does as the schema's owner, for tests: the record is no longer read, searched, listed or recorded, and its ID can never be stored again. Its injection summary and embedding go, and so do its holdout rows: every row that names it, and, when no other live record of its run remains in its tenant, every row of that run in its tenant. It answers false for an ID it does not hold.
func (*Store) Event ¶
Event is the initial lifecycle event stored with a record; ok is false for a record written without one (InsertRecord, New) or none at all.
func (*Store) Get ¶
func (s *Store) Get(_ context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, id ae.UUID) (*ae.ExperienceRecord, error)
Get reads one record in exactly scope; (nil, nil) when there is none, or the bounds do not permit it.
func (*Store) HoldoutRows ¶
func (s *Store) HoldoutRows() []HoldoutRow
HoldoutRows are every holdout assignment row, ordered by run then record: for inspection in tests.
func (*Store) Injections ¶
Injections are when each record was last injected and how often: for inspection in tests.
func (*Store) InsertFinalized ¶
func (s *Store) InsertFinalized(ctx context.Context, bounds ae.AuthorizationBounds, finalized *ae.FinalizedExperience, actor string, recordedAt *time.Time) error
InsertFinalized writes a copy of a finalized record and its initial lifecycle event. It refuses a done context, a blank actor, an event that is not the record's own, a record outside the bounds (ErrAuthorizationDenied) and a record already stored.
func (*Store) InsertRecord ¶
func (s *Store) InsertRecord(ctx context.Context, bounds ae.AuthorizationBounds, record *ae.ExperienceRecord) error
InsertRecord writes one record exactly as given, with no lifecycle event: for seeding and tests.
func (*Store) ListLessonRecords ¶
func (s *Store) ListLessonRecords(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, statuses []ae.ExperienceStatus, limit int) (ae.LessonRecords, error)
ListLessonRecords is the lessons view's read (contract 1.7): the live records held in exactly scope, newest first (created_at descending, then experience ID ascending), filtered to statuses (nil: every status), at most limit, each with when it was last injected. Injections are always tracked here. It never writes, honours the bounds (ErrAuthorizationDenied for a scope outside them) and refuses a limit outside 1 to 500 and empty or undefined statuses.
func (*Store) RecordHoldoutAssignment ¶
func (s *Store) RecordHoldoutAssignment(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, assignment ae.HoldoutAssignment) error
RecordHoldoutAssignment records a run's holdout assignment in scope, as sql/record-holdout-assignment.sql does: one row per named record held live in exactly scope, at the revision named (a record named twice counts once, at its first revision); a row already there for the run and record is kept as it is; the instant, truncated to whole microseconds, is clamped to the store's Clock. Nothing is written for a run whose own records (those captured from the run, in scope's tenant) are all erased. It refuses a done context, an assignment HoldoutRecords refuses, and a scope outside the bounds (ErrAuthorizationDenied).
func (*Store) RecordInjection ¶
func (s *Store) RecordInjection(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, experienceIDs []ae.UUID, injectedAt ae.Instant) error
RecordInjection records that experienceIDs were injected in scope at injectedAt, as sql/record-injection.sql does: only a record held in exactly scope counts, the instant (truncated to whole microseconds) is clamped to the store's Clock and only moves forward, and the count rises by one and saturates at agentexperience.MaxInjectionCount. It refuses a done context, more than agentexperience.MaxRecordInjectionIDs IDs or a nil one, and a scope outside the bounds (ErrAuthorizationDenied).
func (*Store) Records ¶
func (s *Store) Records() []ae.ExperienceRecord
Records are copies of every record held, in the order they were written: for inspection in tests.
func (*Store) Search ¶
func (s *Store) Search(ctx context.Context, bounds ae.AuthorizationBounds, parameters ae.SearchParameters) ([]ae.SearchRow, error)
Search is the text search: records in exactly the requested scope, of an eligible status and confidence, matching enough query terms; by relevance, then experience ID, at most the limit. Nothing outside the bounds. Parameters that fail Validate are an error.
func (*Store) SearchVectors ¶
func (s *Store) SearchVectors(ctx context.Context, bounds ae.AuthorizationBounds, query ae.VectorQuery) (ae.VectorSearchResult, error)
SearchVectors is the nearest-neighbour search: embeddings of the query's model and dimension, of records in exactly the query's scope with an eligible status and confidence (and not model-authored, when excluded), by cosine distance, ties by experience ID, NaN last, at most the limit. With none, the outcome says why: ModelMismatch or DimensionMismatch when eligible embeddings exist but none is comparable, else Found and empty. Nothing outside the bounds. A query that fails Validate is an error.
func (*Store) SetInjection ¶
SetInjection replaces a record's injection summary: for seeding tests (a count near saturation, say).
func (*Store) WriteEmbedding ¶
func (s *Store) WriteEmbedding(ctx context.Context, bounds ae.AuthorizationBounds, write ae.EmbeddingWrite) (ae.EmbeddingWriteResult, error)
WriteEmbedding stores a record's embedding as sql/write-experience-embedding.sql does: only for a record held in exactly the write's scope and still at exactly its source revision (Written); otherwise Stale with the record's revision, or Missing for an unknown record or one in another scope or outside the bounds. A row stored from a newer revision is never replaced by an older one. A write that fails Validate is an error, and stores nothing.