Documentation
¶
Overview ¶
Package memory defines the three-layer memory architecture used by Glyphoxa NPC agents.
The architecture is organised as a hierarchy of increasing abstraction:
- L1 – Session Store (SessionStore): hot, time-ordered transcript log. Allows fast writes and recency-window retrieval during an active session.
- L2 – Semantic Index (SemanticIndex): vector store for embedding-based similarity search over chunked transcript content.
- L3 – Knowledge Graph (KnowledgeGraph / GraphRAGQuerier): a graph of named entities and typed relationships, supporting multi-hop traversal and graph-augmented retrieval (GraphRAG).
All interfaces are public so that external packages can supply alternative storage backends (Postgres/pgvector, Redis, Neo4j, in-memory, …) without depending on glyphoxa internals.
Every implementation must be safe for concurrent use.
Index ¶
- Constants
- type Chunk
- type ChunkFilter
- type ChunkResult
- type ContextResult
- type Entity
- type EntityFilter
- type GraphRAGQuerier
- type KnowledgeGraph
- type NPCIdentity
- type Provenance
- type Recap
- type RecapStore
- type RelQueryOpt
- type RelQueryParams
- type Relationship
- type SearchOpts
- type SemanticIndex
- type SessionInfo
- type SessionStore
- type SpeakerRole
- type TranscriptEntry
- type TraversalOpt
- type TraversalParams
Constants ¶
const ( AttrOccupation = "occupation" AttrAppearance = "appearance" AttrSpeakingStyle = "speaking_style" AttrPersonality = "personality" AttrAlignment = "alignment" )
Well-known entity attribute keys. These are not exhaustive — entities may carry arbitrary keys — but these are recognised by the formatter and other subsystems for special ordering or display treatment.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Chunk ¶
type Chunk struct {
// ID is the unique identifier for this chunk (e.g., a UUID).
ID string
// SessionID is the session this chunk belongs to.
SessionID string
// Content is the raw text of the chunk (may be a sentence, paragraph, or utterance).
Content string
// Embedding is the vector representation of Content.
// Dimension must match the index configuration (e.g., 1536 for OpenAI
// text-embedding-3-small).
Embedding []float32
// SpeakerID identifies who produced this chunk.
SpeakerID string
// EntityID is the knowledge-graph entity associated with this chunk
// (NPC, location, item, etc.). Used for GraphRAG scoping.
EntityID string
// Topic is an optional coarse topic label (e.g., "quest", "trade", "lore").
Topic string
// Timestamp is when this chunk was recorded.
Timestamp time.Time
}
Chunk is a processed segment of transcript content prepared for semantic indexing (L2). A Chunk carries its pre-computed embedding so the index does not need to re-embed on insertion.
type ChunkFilter ¶
type ChunkFilter struct {
// SessionID restricts results to a single session.
SessionID string
// SpeakerID restricts results to chunks produced by a specific speaker.
SpeakerID string
// EntityID restricts results to chunks associated with a specific entity.
EntityID string
// After filters chunks recorded after this instant (exclusive).
After time.Time
// Before filters chunks recorded before this instant (exclusive).
Before time.Time
}
ChunkFilter narrows a semantic search to a subset of indexed chunks (L2). All non-zero fields are applied as AND conditions.
type ChunkResult ¶
type ChunkResult struct {
// Chunk is the retrieved segment.
Chunk Chunk
// Distance is the vector-space distance to the query embedding
// (e.g., cosine distance or L2 — interpretation is implementation-defined).
Distance float64
}
ChunkResult pairs a retrieved chunk with its vector-space distance from the query embedding (L2). Lower Distance values indicate higher semantic similarity.
type ContextResult ¶
type ContextResult struct {
// Entity is the knowledge-graph node that anchors this result.
Entity Entity
// Content is the retrieved text passage relevant to the query.
Content string
// Score is the combined retrieval relevance score (0.0–1.0, higher is better).
Score float64
}
ContextResult pairs a knowledge-graph entity with retrieved textual content that is relevant to a [GraphRAGQuerier.QueryWithContext] call.
type Entity ¶
type Entity struct {
// ID is the unique, stable identifier for this entity (e.g., a UUID).
ID string
// Type classifies the entity.
// Recommended values: npc, player, location, item, faction, event, quest, concept.
// Custom values are allowed.
Type string
// Name is the canonical display name (e.g., "Eldrinax the Undying").
Name string
// Attributes holds arbitrary key/value metadata specific to this entity
// (e.g., alignment, health, occupation, description).
Attributes map[string]any
// CreatedAt is when the entity was first added to the graph.
CreatedAt time.Time
// UpdatedAt is when the entity was last modified.
UpdatedAt time.Time
}
Entity represents a named object in the knowledge graph (L3). Entities are typed nodes; their dynamic attributes are stored in a free-form map to accommodate the diversity of tabletop RPG settings.
type EntityFilter ¶
type EntityFilter struct {
// Type restricts results to entities of this type. Empty matches all types.
Type string
// Name restricts results to entities whose name contains this substring
// (case-insensitive). Empty matches all names.
Name string
// AttributeQuery is a map of attribute keys to required values.
// An entity matches if every key/value pair in AttributeQuery is present
// in its Attributes map.
AttributeQuery map[string]any
}
EntityFilter specifies predicates for entity lookup queries. All non-zero fields are applied as AND conditions.
type GraphRAGQuerier ¶
type GraphRAGQuerier interface {
KnowledgeGraph
// QueryWithContext performs a GraphRAG query using full-text search (FTS):
// it matches the query string against chunk content using PostgreSQL
// plainto_tsquery, scoped to entities in graphScope.
// Results are ranked by FTS relevance (ts_rank).
//
// graphScope limits results to chunks whose entity association is in the
// list. An empty graphScope searches all chunks.
//
// Use this when no embedding vector is available. For higher-quality
// semantic retrieval, prefer [GraphRAGQuerier.QueryWithEmbedding].
QueryWithContext(ctx context.Context, query string, graphScope []string) ([]ContextResult, error)
// QueryWithEmbedding performs a GraphRAG query using vector similarity:
// it finds the topK chunks whose embeddings are closest (cosine distance)
// to the provided query embedding, scoped to entities in graphScope.
// Results are ranked by ascending cosine distance (most similar first).
//
// graphScope limits results to chunks whose entity association is in the
// list. An empty graphScope searches all chunks.
//
// The embedding must match the dimensionality of stored chunk embeddings.
// topK controls the maximum number of results returned.
QueryWithEmbedding(ctx context.Context, embedding []float32, topK int, graphScope []string) ([]ContextResult, error)
}
GraphRAGQuerier extends KnowledgeGraph with graph-augmented retrieval (GraphRAG). It combines structured graph traversal with semantic text retrieval to produce contextually grounded results for LLM consumption.
Two query methods are provided:
- [GraphRAGQuerier.QueryWithContext] uses full-text search (FTS) and requires no embedding provider. Useful as a fallback when embeddings are unavailable or during entity extraction where embedding budget is limited.
- [GraphRAGQuerier.QueryWithEmbedding] uses pgvector cosine similarity against pre-computed chunk embeddings. This is the true GraphRAG path described in the design docs and produces higher-quality results when an embedding provider is available.
type KnowledgeGraph ¶
type KnowledgeGraph interface {
// AddEntity upserts an entity into the graph.
// If an entity with the same ID already exists it is completely replaced.
AddEntity(ctx context.Context, entity Entity) error
// GetEntity retrieves an entity by its unique ID.
// Returns (nil, nil) when the entity does not exist.
GetEntity(ctx context.Context, id string) (*Entity, error)
// UpdateEntity merges attrs into the Attributes map of the specified entity
// and refreshes its UpdatedAt timestamp. Keys present in attrs overwrite
// existing values; absent keys are left unchanged.
// Returns an error when the entity does not exist.
UpdateEntity(ctx context.Context, id string, attrs map[string]any) error
// DeleteEntity removes the entity and all its associated relationships from
// the graph. Deleting a non-existent entity is not an error.
DeleteEntity(ctx context.Context, id string) error
// FindEntities returns all entities matching filter.
// Returns an empty (non-nil) slice when no entities match.
FindEntities(ctx context.Context, filter EntityFilter) ([]Entity, error)
// AddRelationship upserts a directed edge between two entities.
// If a relationship with the same (SourceID, TargetID, RelType) already
// exists it is completely replaced.
AddRelationship(ctx context.Context, rel Relationship) error
// GetRelationships returns relationships associated with entityID.
// By default only outgoing edges are returned; use [WithIncoming] to include
// inbound edges, and [WithRelTypes] to filter by edge type.
// Returns an empty (non-nil) slice when no relationships match.
GetRelationships(ctx context.Context, entityID string, opts ...RelQueryOpt) ([]Relationship, error)
// DeleteRelationship removes the directed edge identified by (sourceID,
// targetID, relType). Deleting a non-existent edge is not an error.
DeleteRelationship(ctx context.Context, sourceID, targetID, relType string) error
// Neighbors performs a breadth-first traversal from entityID up to depth
// hops and returns all reachable entities (the start entity is excluded).
// [TraversalOpt] options can restrict which edge or node types are followed.
// Returns an empty (non-nil) slice when no neighbours are reachable.
Neighbors(ctx context.Context, entityID string, depth int, opts ...TraversalOpt) ([]Entity, error)
// FindPath returns the shortest sequence of entities connecting fromID to
// toID inclusive, following directed edges up to maxDepth hops.
// Returns an empty (non-nil) slice when no path exists within maxDepth.
FindPath(ctx context.Context, fromID, toID string, maxDepth int) ([]Entity, error)
// VisibleSubgraph returns the subset of the graph visible from the
// perspective of npcID: the NPC node itself, all entities it has direct
// relationships with, and those relationships.
// Implementations may apply visibility rules (e.g., only publicly known facts).
VisibleSubgraph(ctx context.Context, npcID string) ([]Entity, []Relationship, error)
// IdentitySnapshot assembles a compact [NPCIdentity] for npcID, suitable for
// injecting into a system prompt or context window.
IdentitySnapshot(ctx context.Context, npcID string) (*NPCIdentity, error)
}
KnowledgeGraph is the L3 memory layer: a graph of named Entity nodes connected by typed Relationship edges.
It supports full CRUD on nodes and edges, multi-hop neighbourhood traversal, shortest-path queries, and NPC-specific projection methods.
Mutating operations that act on a primary key (AddEntity, AddRelationship) must behave as upserts rather than returning an error on duplicates. Deletions of non-existent records are not errors.
Implementations must be safe for concurrent use.
type NPCIdentity ¶
type NPCIdentity struct {
// Entity is the NPC's own node in the knowledge graph.
Entity Entity
// Relationships are all direct edges from/to this NPC.
Relationships []Relationship
// RelatedEntities are the entities connected to this NPC by Relationships.
RelatedEntities []Entity
}
NPCIdentity is a compact snapshot of an NPC's knowledge-graph identity, suitable for injection into a system prompt or context window.
type Provenance ¶
type Provenance struct {
// SessionID is the game session during which this fact was established.
SessionID string
// Timestamp is when the fact was established.
Timestamp time.Time
// Confidence is the model's confidence in this fact (0.0–1.0).
Confidence float64
// Source describes how the fact was derived.
// Well-known values: "stated" (directly spoken), "inferred" (model reasoning).
Source string
// DMConfirmed indicates a human Dungeon Master has validated this fact.
DMConfirmed bool
}
Provenance records the origin of a fact asserted in the knowledge graph. It is embedded in Relationship to allow downstream reasoning about reliability.
type Recap ¶ added in v0.1.0
type Recap struct {
// SessionID is the session this recap was generated from.
SessionID string
// CampaignID identifies the campaign.
CampaignID string
// Text is the dramatic narrative recap text.
Text string
// AudioData is the rendered PCM audio bytes.
AudioData []byte
// SampleRate is the audio sample rate in Hz.
SampleRate int
// Channels is the number of audio channels.
Channels int
// Duration is the estimated speech duration.
Duration time.Duration
// GeneratedAt is when this recap was created.
GeneratedAt time.Time
}
Recap is the generated "Previously On..." voiced summary for a session.
type RecapStore ¶ added in v0.1.0
type RecapStore interface {
// SaveRecap persists a recap. If a recap for the same session already
// exists it is replaced (upsert).
SaveRecap(ctx context.Context, recap Recap) error
// GetRecap retrieves the recap for the given session.
// Returns (nil, nil) when no recap exists.
GetRecap(ctx context.Context, sessionID string) (*Recap, error)
}
RecapStore persists and retrieves generated session recaps. Implementations must be safe for concurrent use.
type RelQueryOpt ¶
type RelQueryOpt func(*relQueryOptions)
RelQueryOpt is a functional option for [KnowledgeGraph.GetRelationships].
func WithIncoming ¶
func WithIncoming() RelQueryOpt
WithIncoming includes relationships where the queried entity is the target (i.e., inbound edges). By default only outgoing relationships are returned.
func WithOutgoing ¶
func WithOutgoing() RelQueryOpt
WithOutgoing includes relationships where the queried entity is the source (i.e., outbound edges). This is the default behaviour; calling it explicitly is a no-op but improves readability when combined with WithIncoming.
func WithRelLimit ¶
func WithRelLimit(n int) RelQueryOpt
WithRelLimit caps the number of relationships returned. A value of 0 means the implementation may apply its own default.
func WithRelTypes ¶
func WithRelTypes(relTypes ...string) RelQueryOpt
WithRelTypes restricts the returned relationships to those whose RelType is in the provided list. An empty list (the default) returns all types.
type RelQueryParams ¶
RelQueryParams holds the resolved parameters from a slice of RelQueryOpt.
func ApplyRelQueryOpts ¶
func ApplyRelQueryOpts(opts []RelQueryOpt) RelQueryParams
ApplyRelQueryOpts applies a slice of RelQueryOpt functional options and returns the resolved query parameters as a RelQueryParams. This helper allows external packages (such as storage backends) to read the option values without needing to access the unexported [relQueryOptions] type directly.
type Relationship ¶
type Relationship struct {
// SourceID is the ID of the originating entity.
SourceID string
// TargetID is the ID of the destination entity.
TargetID string
// RelType is the semantic label of the relationship
// (e.g., "knows", "hates", "owns", "member_of").
RelType string
// Attributes holds additional edge metadata
// (e.g., since, strength, public, description).
Attributes map[string]any
// Provenance records the evidence trail for this relationship.
Provenance Provenance
// CreatedAt is when this relationship was first added.
CreatedAt time.Time
}
Relationship is a directed, typed edge between two entities in the knowledge graph (L3).
type SearchOpts ¶
type SearchOpts struct {
// SessionID restricts the search to a single session.
// An empty string searches across all sessions.
SessionID string
// After filters entries recorded after this instant (exclusive).
// A zero Time disables the lower bound.
After time.Time
// Before filters entries recorded before this instant (exclusive).
// A zero Time disables the upper bound.
Before time.Time
// SpeakerID restricts results to a specific speaker.
// An empty string matches all speakers.
SpeakerID string
// Limit caps the number of results returned.
// A value of 0 means the implementation may apply its own default.
Limit int
}
SearchOpts configures a keyword / full-text search over session entries (L1). All non-zero fields are applied as AND conditions.
type SemanticIndex ¶
type SemanticIndex interface {
// IndexChunk stores a pre-embedded [Chunk] in the vector index.
// If a chunk with the same ID already exists it must be replaced (upsert).
IndexChunk(ctx context.Context, chunk Chunk) error
// Search finds the topK chunks whose embeddings are closest to the query
// embedding, filtered by filter.
// Results are ordered by ascending Distance (most similar first).
// Returns an empty (non-nil) slice when no chunks match.
Search(ctx context.Context, embedding []float32, topK int, filter ChunkFilter) ([]ChunkResult, error)
}
SemanticIndex is the L2 memory layer: a vector store for embedding-based similarity search over chunked transcript content.
Callers are responsible for producing embeddings before calling IndexChunk or Search. Implementations must be safe for concurrent use.
type SessionInfo ¶ added in v0.1.0
type SessionInfo struct {
// SessionID is the unique session identifier.
SessionID string
// CampaignID identifies the campaign this session belongs to.
CampaignID string
// StartedAt is when the session was started.
StartedAt time.Time
// EndedAt is when the session ended. Zero value if still active.
EndedAt time.Time
}
SessionInfo holds metadata about a recorded session.
type SessionStore ¶
type SessionStore interface {
// WriteEntry appends a TranscriptEntry to the store for the given session.
// sessionID must be non-empty.
// Returns an error only on persistent storage failure.
WriteEntry(ctx context.Context, sessionID string, entry TranscriptEntry) error
// GetRecent returns all entries for the given session whose Timestamp is
// no earlier than time.Now()-duration.
// Returns an empty (non-nil) slice when no matching entries exist.
GetRecent(ctx context.Context, sessionID string, duration time.Duration) ([]TranscriptEntry, error)
// Search performs keyword / full-text search over stored entries.
// The query string is matched against the Text field.
// opts refines the result set by time range, speaker, or session scope.
// Returns an empty (non-nil) slice when no entries match.
Search(ctx context.Context, query string, opts SearchOpts) ([]TranscriptEntry, error)
// EntryCount returns the total number of transcript entries stored for
// the given session. Returns 0 when the session has no entries.
EntryCount(ctx context.Context, sessionID string) (int, error)
// ListSessions returns sessions for the current campaign, newest first.
// limit caps the number of results (0 = implementation default).
// Returns an empty (non-nil) slice when no sessions exist.
ListSessions(ctx context.Context, limit int) ([]SessionInfo, error)
}
SessionStore is the L1 memory layer: a time-ordered, append-only log of TranscriptEntry records for one or more game sessions.
Entries must be returned in chronological order unless otherwise specified. Implementations must be safe for concurrent use.
type SpeakerRole ¶ added in v0.1.1
type SpeakerRole string
SpeakerRole classifies the speaker within the session context.
const ( // RoleDefault is the zero-value role for ordinary players. RoleDefault SpeakerRole = "" // RoleGM identifies the Game Master (DM) player. RoleGM SpeakerRole = "gm" // RoleGMAssistant identifies an NPC acting as the GM's AI assistant. RoleGMAssistant SpeakerRole = "gm_assistant" )
func (SpeakerRole) DisplaySuffix ¶ added in v0.1.1
func (r SpeakerRole) DisplaySuffix() string
DisplaySuffix returns a human-readable label suitable for appending to speaker names in formatted transcripts. Returns "" for RoleDefault.
type TranscriptEntry ¶
type TranscriptEntry struct {
// SpeakerID identifies who spoke (player user ID or NPC name).
SpeakerID string
// SpeakerName is the human-readable speaker name.
SpeakerName string
// SpeakerRole classifies the speaker (e.g., GM, GM assistant).
// The zero value means ordinary player.
SpeakerRole SpeakerRole
// Text is the (possibly corrected) transcript text.
Text string
// RawText is the original uncorrected STT output. Preserved for debugging.
RawText string
// NPCID identifies the NPC agent that produced this entry.
// Empty for non-NPC (e.g. player) entries.
NPCID string
// Timestamp is when this entry was recorded.
Timestamp time.Time
// Duration is the length of the utterance.
Duration time.Duration
}
TranscriptEntry is a complete exchange record written to the session log. It captures both the speaker's utterance and optionally the NPC's response, forming the atomic unit of session history.
func (TranscriptEntry) IsNPC ¶
func (e TranscriptEntry) IsNPC() bool
IsNPC reports whether this entry was produced by an NPC agent.
type TraversalOpt ¶
type TraversalOpt func(*traversalOptions)
TraversalOpt is a functional option for [KnowledgeGraph.Neighbors] graph traversals.
func TraverseMaxNodes ¶
func TraverseMaxNodes(n int) TraversalOpt
TraverseMaxNodes caps the number of entities returned during a traversal. A value of 0 means the implementation may apply its own default.
func TraverseNodeTypes ¶
func TraverseNodeTypes(nodeTypes ...string) TraversalOpt
TraverseNodeTypes restricts traversal to entity nodes whose Type is in the provided list. An empty list (the default) visits all node types.
func TraverseRelTypes ¶
func TraverseRelTypes(relTypes ...string) TraversalOpt
TraverseRelTypes restricts traversal to edges whose RelType is in the provided list. An empty list (the default) follows all edge types.
type TraversalParams ¶
TraversalParams holds the resolved parameters from a slice of TraversalOpt.
func ApplyTraversalOpts ¶
func ApplyTraversalOpts(opts []TraversalOpt) TraversalParams
ApplyTraversalOpts applies a slice of TraversalOpt functional options and returns the resolved traversal parameters as a TraversalParams. This helper allows external packages to read the option values without accessing the unexported [traversalOptions] type.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package export provides campaign export and import as .tar.gz archives.
|
Package export provides campaign export and import as .tar.gz archives. |
|
Package mock provides in-memory test doubles for the memory layer interfaces.
|
Package mock provides in-memory test doubles for the memory layer interfaces. |
|
Package postgres provides a PostgreSQL-backed implementation of the three-layer Glyphoxa memory architecture (L1 session log, L2 semantic index, L3 knowledge graph).
|
Package postgres provides a PostgreSQL-backed implementation of the three-layer Glyphoxa memory architecture (L1 session log, L2 semantic index, L3 knowledge graph). |