Documentation
¶
Overview ¶
Package memory provides persistent, agent-managed memory across sessions.
Architecture ¶
Three tiers:
Facts — Two typed files (user.md, env.md) with character caps, injected into the system prompt as a frozen snapshot. Managed by the agent via the memory tool (add/replace/remove/consolidate/read).
Buffer — In-memory ring buffer on the Session struct. One-line summaries appended after each turn. Injected only when non-empty.
Episodes — LLM-extracted durable facts written after sessions with ≥3 turns. Searchable via memory(search=...).
Merge-on-Write ¶
When adding a fact, go-vector's RandomProjections provides a fast similarity check vs existing entries. cos > 0.7 = auto-merge, cos < 0.3 = auto-add, 0.3-0.7 = SimpleCall judgment. This saves ~80% of LLM calls on writes.
Index ¶
- Constants
- func FormatBufferLine(role, message string) string
- func ScanContent(content string) error
- type Buffer
- type EpisodeMeta
- type EpisodeStore
- func (e *EpisodeStore) Read(sessionID string) (string, error)
- func (e *EpisodeStore) ReadIndex() ([]EpisodeMeta, error)
- func (e *EpisodeStore) Search(query string, limit int) ([]EpisodeMeta, error)
- func (e *EpisodeStore) Write(sessionID, summary string, turns int) error
- func (e *EpisodeStore) WriteIfEnough(sessionID, summary string, turns int) error
- type FactStore
- type LLMClient
- type MemoryConfig
- type MemoryManager
- func (m *MemoryManager) AddFact(target, content string) error
- func (m *MemoryManager) AppendBuffer(role, message string)
- func (m *MemoryManager) BuildSystemPrompt() string
- func (m *MemoryManager) ClearBuffer()
- func (m *MemoryManager) Consolidate(target string) error
- func (m *MemoryManager) GetBuffer() []string
- func (m *MemoryManager) OnSessionEnd(sessionID string, turns int, messages []string)
- func (m *MemoryManager) ReadFacts() (userContent, envContent string, err error)
- func (m *MemoryManager) RemoveFact(target, oldText string) error
- func (m *MemoryManager) ReplaceFact(target, oldText, content string) error
- func (m *MemoryManager) RestoreBuffer(lines []string)
- func (m *MemoryManager) SearchEpisodes(query string, limit int) ([]EpisodeMeta, error)
- type MemoryTool
- type MergeDetector
- type RankStrategy
Constants ¶
const ( // MergeThreshold is the cosine similarity threshold above which entries // are considered duplicates and auto-merged. MergeThreshold float32 = 0.7 // AddThreshold is the cosine similarity below which entries are // considered distinct and auto-added without LLM judgment. AddThreshold float32 = 0.3 )
MergeThresholds for merge-on-write classification.
Variables ¶
This section is empty.
Functions ¶
func FormatBufferLine ¶
FormatBufferLine creates a timestamped buffer line. Format: "HH:MM role message"
func ScanContent ¶
ScanContent checks memory content for security threats. Returns an error if the content contains patterns that could compromise the agent.
Checks:
- Invisible Unicode characters (zero-width spaces, direction overrides, BOM)
- Prompt injection markers ("ignore previous instructions", etc.)
- Credential exfiltration patterns (API keys, private keys, bearer tokens)
Types ¶
type Buffer ¶
type Buffer struct {
// contains filtered or unexported fields
}
Buffer is a simple ring buffer for session-level turn summaries. Thread-safe only if accessed from a single goroutine (the loop engine owns the buffer and serializes all access).
func NewBuffer ¶
NewBuffer creates a ring buffer with the given capacity. cap=0 means disabled (all appends are silently discarded).
type EpisodeMeta ¶
type EpisodeMeta struct {
SessionID string `json:"session_id"`
Turns int `json:"turns"`
CreatedAt time.Time `json:"created_at"`
Summary string `json:"summary"` // truncated for index listing
}
EpisodeMeta holds metadata for a single episode.
type EpisodeStore ¶
type EpisodeStore struct {
// contains filtered or unexported fields
}
EpisodeStore manages on-disk episode summaries (Tier 3 memory). Written after sessions with sufficient turns, searchable via SimpleCall.
func NewEpisodeStore ¶
func NewEpisodeStore(dir string, rankFn RankStrategy) *EpisodeStore
NewEpisodeStore creates an EpisodeStore rooted at dir. If rankFn is nil, a default ranker is used (SimpleCall-based — requires LLM client).
func (*EpisodeStore) Read ¶
func (e *EpisodeStore) Read(sessionID string) (string, error)
Read returns the full summary content for a session.
func (*EpisodeStore) ReadIndex ¶
func (e *EpisodeStore) ReadIndex() ([]EpisodeMeta, error)
ReadIndex reads the episode index from disk. Returns empty slice if the index file doesn't exist yet. Entries are ordered newest-first.
func (*EpisodeStore) Search ¶
func (e *EpisodeStore) Search(query string, limit int) ([]EpisodeMeta, error)
Search returns the most relevant episodes for a query, ranked by the configured RankStrategy. Limited to limit results.
func (*EpisodeStore) Write ¶
func (e *EpisodeStore) Write(sessionID, summary string, turns int) error
Write stores an episode summary for a session. Creates the episodes directory and updates the index.
func (*EpisodeStore) WriteIfEnough ¶
func (e *EpisodeStore) WriteIfEnough(sessionID, summary string, turns int) error
WriteIfEnough calls Write only if turns >= extractThreshold (3). Returns nil without writing if the threshold isn't met.
type FactStore ¶
type FactStore struct {
// contains filtered or unexported fields
}
FactStore manages typed fact files (user.md and env.md) with character caps, duplicate prevention, and entry-level CRUD via substring matching.
func NewFactStore ¶
NewFactStore creates a FactStore rooted at dir. Fact files are stored as dir/user.md and dir/env.md. Caps limit total file size.
func (*FactStore) Add ¶
Add appends a new entry to a fact file. Returns error if:
- target is invalid
- content is empty
- content already exists (dedup)
- adding would exceed the character cap
func (*FactStore) Read ¶
Read returns the full content of a fact file. Returns empty string if the file doesn't exist yet.
type LLMClient ¶
LLMClient abstracts the LLM calls needed by the memory system (SimpleCall for consolidation, episode extraction, and search).
type MemoryConfig ¶
type MemoryConfig struct {
Enabled bool `json:"enabled"`
FactsLimitUser int `json:"facts_limit_user"`
FactsLimitEnv int `json:"facts_limit_env"`
BufferLines int `json:"buffer_lines"`
BufferEnabled bool `json:"buffer_enabled"`
MergeOnWrite bool `json:"merge_on_write"`
ExtractOnEnd bool `json:"extract_on_end"`
LLMSearch bool `json:"llm_search"`
LLMExtract bool `json:"llm_extract"`
LLMConsolidate bool `json:"llm_consolidate"`
MergeThreshold float32 `json:"merge_threshold"`
AddThreshold float32 `json:"add_threshold"`
}
MemoryConfig holds configuration for the memory system. Mirrors the JSON config section.
func DefaultMemoryConfig ¶
func DefaultMemoryConfig() MemoryConfig
DefaultMemoryConfig returns sensible defaults.
type MemoryManager ¶
type MemoryManager struct {
// contains filtered or unexported fields
}
MemoryManager orchestrates all three tiers of memory: Facts (durable, in system prompt), Buffer (session-level context), and Episodes (on-disk extracted summaries with search).
func NewMemoryManager ¶
func NewMemoryManager(memoryDir string, llc LLMClient, cfg MemoryConfig) *MemoryManager
NewMemoryManager creates a fully wired MemoryManager. If llc is nil, LLM-dependent features (consolidation, episode search) degrade gracefully (no LLM call, fallback behavior).
func (*MemoryManager) AddFact ¶
func (m *MemoryManager) AddFact(target, content string) error
AddFact appends a new fact entry. Performs:
- Security scan (reject if dangerous)
- Dedup (FactStore handles this)
- Merge-on-write using go-vector RP if MergeOnWrite is enabled
- Fits the merge detector after mutation
func (*MemoryManager) AppendBuffer ¶
func (m *MemoryManager) AppendBuffer(role, message string)
AppendBuffer adds a turn summary to the in-memory ring buffer.
func (*MemoryManager) BuildSystemPrompt ¶
func (m *MemoryManager) BuildSystemPrompt() string
BuildSystemPrompt returns the memory section to inject into the system prompt. Returns empty string if memory is disabled or nothing to show.
func (*MemoryManager) ClearBuffer ¶
func (m *MemoryManager) ClearBuffer()
ClearBuffer resets the buffer for a new session.
func (*MemoryManager) Consolidate ¶
func (m *MemoryManager) Consolidate(target string) error
Consolidate uses the LLM to merge related entries in a target file for better density. Falls back to no-op if LLM is unavailable or LLMConsolidate is disabled in config.
func (*MemoryManager) GetBuffer ¶
func (m *MemoryManager) GetBuffer() []string
GetBuffer returns the current buffer lines (for system prompt injection).
func (*MemoryManager) OnSessionEnd ¶
func (m *MemoryManager) OnSessionEnd(sessionID string, turns int, messages []string)
OnSessionEnd is called when a session ends. If turns >= threshold, extracts durable facts using the LLM and stores them as an episode.
func (*MemoryManager) ReadFacts ¶
func (m *MemoryManager) ReadFacts() (userContent, envContent string, err error)
ReadFacts returns the full content of user and env fact files.
func (*MemoryManager) RemoveFact ¶
func (m *MemoryManager) RemoveFact(target, oldText string) error
RemoveFact removes a fact entry by substring.
func (*MemoryManager) ReplaceFact ¶
func (m *MemoryManager) ReplaceFact(target, oldText, content string) error
ReplaceFact replaces an existing fact entry.
func (*MemoryManager) RestoreBuffer ¶
func (m *MemoryManager) RestoreBuffer(lines []string)
RestoreBuffer loads buffer lines from a saved slice (e.g., from session).
func (*MemoryManager) SearchEpisodes ¶
func (m *MemoryManager) SearchEpisodes(query string, limit int) ([]EpisodeMeta, error)
SearchEpisodes returns the most relevant episodes for a query.
type MemoryTool ¶
type MemoryTool struct {
// contains filtered or unexported fields
}
MemoryTool wraps a MemoryManager as a kode-compatible Tool.
func NewMemoryTool ¶
func NewMemoryTool(mm *MemoryManager) *MemoryTool
NewMemoryTool creates a tool that exposes memory CRUD + search to the agent.
func (*MemoryTool) Description ¶
func (t *MemoryTool) Description() string
func (*MemoryTool) Name ¶
func (t *MemoryTool) Name() string
func (*MemoryTool) Schema ¶
func (t *MemoryTool) Schema() any
type MergeDetector ¶
type MergeDetector struct {
// contains filtered or unexported fields
}
MergeDetector uses RandomProjections to quickly estimate whether a new fact entry overlaps with existing entries. This avoids ~80% of LLM calls during merge-on-write.
Lifecycle:
- NewMergeDetector(dims) — creates RP embedder
- Fit(corpus) — builds vocabulary from existing entries
- Classify(entry) → action + similarIdx + similarity
- After facts change → Fit(newCorpus) to rebuild vocabulary
Thresholds control the classification:
- mergeThreshold: cosine above this → auto-merge (default 0.7)
- addThreshold: cosine below this → auto-add (default 0.3)
- Between thresholds: "judge" — requires LLM to decide
func NewMergeDetector ¶
func NewMergeDetector(dims int) *MergeDetector
NewMergeDetector creates a MergeDetector with the given output dimensionality for the RP embedder. Pass 0 for default (256). Uses default thresholds (0.7 merge, 0.3 add).
func NewMergeDetectorWithThresholds ¶
func NewMergeDetectorWithThresholds(dims int, mergeThreshold, addThreshold float32) *MergeDetector
NewMergeDetectorWithThresholds creates a MergeDetector with custom thresholds.
func (*MergeDetector) Classify ¶
func (m *MergeDetector) Classify(entry string) (action string, similarIdx int, similarity float32)
Classify returns the merge decision for a new entry vs the fitted corpus.
Returns:
- action: "merge" | "add" | "judge" | "nobody"
- similarIdx: index of the most similar corpus entry (for merge/judge)
- similarity: cosine similarity [0, 1]
"nobody" means the corpus is empty — there's nothing to compare against.
func (*MergeDetector) Corpus ¶
func (m *MergeDetector) Corpus() []string
Corpus returns the current corpus (for inspection).
func (*MergeDetector) Fit ¶
func (m *MergeDetector) Fit(corpus []string)
Fit builds the RP vocabulary and pre-computes embeddings for all corpus entries. Call whenever facts change (after add/replace/remove).
type RankStrategy ¶
type RankStrategy func(query string, episodes []EpisodeMeta) ([]EpisodeMeta, error)
RankStrategy is an injectable function for ranking episodes by relevance to a query. The default implementation uses SimpleCall; tests can inject a deterministic mock.
func NewLLMRanker ¶
func NewLLMRanker(llm LLMClient) RankStrategy
NewLLMRanker creates a RankStrategy that uses an LLM client to rank episodes by semantic relevance to the query. Falls back to recency ordering if the LLM call fails or returns unparseable output.