mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-08-06 20:25:10 +00:00
Auto-inject previously searched episodic memory using only the latest user message. Follow-up questions like "what's my favorite?" returned poor matches because the embedding lost the conversational frame. InjectParams now carries an optional RecentContext field that pgAuto Injector prepends to the search query as "Context: ... \nQuery: ..." before running the FTS+vector hybrid search. The "Context:"/"Query:" framing works with both instruction-tuned embedding models (which respect the labels) and plain models (neutral separators). ContextStage walks the message history backward, collects up to 2 trailing user turns capped at 300 runes total, and threads the snippet through the AutoInject callback to the injector. Empty RecentContext preserves legacy single-message search semantics — zero-risk fallback for callers that haven't adopted the new field. Rune-based truncation (not byte) keeps vi/zh locales safe: a byte-wise tail-clip would slice multi-byte runes and emit invalid UTF-8 to the embedding model, degrading exactly the cases Phase 9 is meant to fix. tailClipRunes helper covers Vietnamese, Chinese, Japanese, emoji. 13 regression tests: recall query builder (unicode-safe clip, whitespace handling, position ordering), buildRecentContext (order preservation, turn cap, truncation, non-user skip), and tailClipRunes (CJK, short input, zero cap). All passing with -race. Refs plans/260410-1009-openclaw-ts-feature-port/phase-09-active- memory-recall.md — minimal-viable delivery; Tier 2 LLM re-ranking and per-session recall cache deferred until operational data shows context-aware search alone is insufficient.
74 lines
2.7 KiB
Go
74 lines
2.7 KiB
Go
// Package memory extends the v3 memory system with auto-injection and tiered retrieval.
|
|
//
|
|
// V3 design: Phase 3 — L0/L1/L2 context tiering + smart auto-inject.
|
|
package memory
|
|
|
|
import "context"
|
|
|
|
// AutoInjector checks relevance and produces L0 injection for system prompt.
|
|
// Called once per turn in ContextStage.
|
|
type AutoInjector interface {
|
|
// Inject checks user message against memory index.
|
|
// Returns formatted section for system prompt, or "" if nothing relevant.
|
|
// Budget: max ~200 tokens of L0 summaries.
|
|
Inject(ctx context.Context, params InjectParams) (*InjectResult, error)
|
|
}
|
|
|
|
// InjectParams configures a single auto-inject call.
|
|
type InjectParams struct {
|
|
AgentID string
|
|
UserID string
|
|
TenantID string
|
|
UserMessage string
|
|
|
|
// RecentContext carries a short snippet of recent conversation (typically
|
|
// the last 1-2 user turns concatenated) used to enrich the search query.
|
|
// Context-aware recall: without this, vector search on "what's my favorite?"
|
|
// misses memories about the topic under discussion. With it, the query
|
|
// embedding captures conversational intent and returns materially better
|
|
// matches for follow-up questions.
|
|
//
|
|
// Empty = legacy behaviour (search on UserMessage only).
|
|
// Target length: ≤ ~400 chars. Longer context dilutes the embedding.
|
|
RecentContext string
|
|
|
|
MaxEntries int // default 5
|
|
MaxTokens int // default 200
|
|
Threshold float64 // relevance threshold (default 0.3)
|
|
}
|
|
|
|
// InjectResult contains the injection output + observability data.
|
|
type InjectResult struct {
|
|
Section string // formatted prompt section (empty = nothing relevant)
|
|
MatchCount int // total matches found
|
|
Injected int // entries injected (after budget trim)
|
|
TopScore float64 // highest relevance score
|
|
}
|
|
|
|
// L0Summary is a single auto-inject entry for the system prompt.
|
|
type L0Summary struct {
|
|
Topic string // short topic label
|
|
Summary string // ~1 sentence abstract
|
|
ID string // for memory_expand(id) deep retrieval
|
|
}
|
|
|
|
// MemoryConfig holds per-agent memory settings (stored in agents.settings JSONB).
|
|
type MemoryConfig struct {
|
|
AutoInjectEnabled bool `json:"auto_inject_enabled"` // default true
|
|
AutoInjectThreshold float64 `json:"auto_inject_threshold"` // default 0.3
|
|
AutoInjectMaxTokens int `json:"auto_inject_max_tokens"` // default 200
|
|
EpisodicTTLDays int `json:"episodic_ttl_days"` // default 90
|
|
ConsolidationEnabled bool `json:"consolidation_enabled"` // default true
|
|
}
|
|
|
|
// DefaultMemoryConfig returns sensible defaults.
|
|
func DefaultMemoryConfig() MemoryConfig {
|
|
return MemoryConfig{
|
|
AutoInjectEnabled: true,
|
|
AutoInjectThreshold: 0.3,
|
|
AutoInjectMaxTokens: 200,
|
|
EpisodicTTLDays: 90,
|
|
ConsolidationEnabled: true,
|
|
}
|
|
}
|