memory

package module
v1.0.7 Latest Latest
Warning

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

Go to latest
Published: May 12, 2026 License: MPL-2.0 Imports: 16 Imported by: 0

README

adk-go-memory

Go Reference Go Report Card

Production-grade memory layer for Google ADK-Go agents with automatic observation extraction, hybrid search (vector + FTS), and intelligent context assembly.

Overview

adk-go-memory provides a complete memory management system for Google ADK-Go agents. It extracts facts from conversations, stores them in SQLite with vector and full-text search capabilities, and retrieves relevant context to augment agent responses.

Key Features
  • Automatic Observation Extraction: LLM-powered fact extraction from conversations with confidence levels (explicit, deductive, inductive, contradiction)
  • Hybrid Search: Combines sqlite-vec vector similarity search with FTS5 full-text search via Reciprocal Rank Fusion (RRF)
  • Deduplication: Automatically detects and merges near-duplicate observations
  • Working Representations: Multi-strategy context assembly combining semantic search, most-derived facts, and recent observations
  • Peer Cards: User profiles with up to 40 scored facts for persistent user modeling
  • ADK-Go Integration: Implements google.golang.org/adk/memory.Service interface
  • Production Ready: SQLite-based storage with CGO-enabled sqlite-vec for efficient vector operations

Installation

go get github.com/ieshan/adk-go-memory
Requirements
  • Go 1.26+
  • For SQLite storage: CGO enabled (for sqlite-vec) and SQLite with FTS5 support
  • For map-based storage: Pure Go, no CGO required

Project Setup

This repository uses Go workspaces for multi-module development. To set up the project:

make setup

This initializes the workspace with the root module, SQLite adapter, and examples. The go.work file is gitignored and managed locally by each developer.

Build Tags

The SQLite adapter requires the sqlite_fts5 build tag:

# For SQLite adapter only (requires CGO)
CGO_ENABLED=1 go build -tags=sqlite_fts5 ./...

# Core package tests (no CGO required)
go test ./...

Quick Start

Basic Usage
package main

import (
    "context"
    "log"

    memory "github.com/ieshan/adk-go-memory"
    "github.com/ieshan/adk-go-memory/adapter"
    adkmemory "google.golang.org/adk/memory" // ADK interface
)

func main() {
    ctx := context.Background()

    // Create in-memory storage (use sqlite.NewSQLiteStorage for persistence)
    storage := adapter.InMemory()
    defer storage.Close()

    // Create a memory service
    svc := memory.NewService(memory.ServiceConfig{
        Storage: storage,
    })
    defer svc.Close()

    // Service implements google.golang.org/adk/memory.Service
    var _ adkmemory.Service = svc
}

API Reference

Service

The Service is the main entry point that implements google.golang.org/adk/memory.Service.

// ServiceConfig configures the Service.
type ServiceConfig struct {
    Storage  adapter.Storage  // Storage backend for observations
    Deriver  *Deriver           // Optional: LLM-powered fact extraction
    Provider *Provider          // Optional: Context assembly provider
}

// NewService creates a new Service with the given configuration.
func NewService(cfg ServiceConfig) *Service

// AddSessionToMemory extracts observations from session events and stores them.
// Requires a Deriver to be configured.
func (s *Service) AddSessionToMemory(ctx context.Context, sess session.Session) error

// SearchMemory searches the memory for relevant observations.
func (s *Service) SearchMemory(ctx context.Context, req *memory.SearchRequest) (*memory.SearchResponse, error)

// SetDeriver sets the deriver on the service (useful for lazy initialization).
func (s *Service) SetDeriver(d *Deriver)

// SetProvider sets the provider on the service.
func (s *Service) SetProvider(p *Provider)

// Close releases resources held by the service.
func (s *Service) Close() error
Deriver

The Deriver uses an LLM to automatically extract factual observations from conversations.

// DeriverConfig configures the Deriver.
type DeriverConfig struct {
    LLM     model.LLM      // LLM for observation extraction
    Storage adapter.Storage // Storage for saving observations
}

// NewDeriver creates a new Deriver with the given configuration.
func NewDeriver(cfg DeriverConfig) *Deriver

// Derive extracts observations from timestamped messages and stores them.
// Automatically deduplicates near-duplicate observations.
func (d *Deriver) Derive(ctx context.Context, messages []TimestampedMessage, sessionID, userID, appName string) error
Provider

The Provider wires together all memory components for comprehensive context assembly.

// ProviderConfig configures the Provider.
type ProviderConfig struct {
    Storage       adapter.Storage                        // Storage backend
    EmbeddingFunc func(ctx context.Context, text string) ([]float32, error) // For semantic search
}

// NewProvider creates a new Provider with the given configuration.
func NewProvider(cfg ProviderConfig) *Provider

// GetMemoryContext retrieves memory context for the given query using the representation manager.
func (p *Provider) GetMemoryContext(ctx context.Context, query string, sessionID, userID, appName string) (string, error)

// GetOrCreatePeerCard gets or creates a peer card for the given peer ID.
func (p *Provider) GetOrCreatePeerCard(peerID string) *PeerCard

// LoadPeerCardFromMemory loads peer card facts from storage.
func (p *Provider) LoadPeerCardFromMemory(ctx context.Context, peerID string) error

// OnSessionStart is called when a session starts to load peer cards.
func (p *Provider) OnSessionStart(ctx context.Context, sessionID, userID, appName string) error

// Close releases resources held by the provider.
func (p *Provider) Close() error
RepresentationManager

The RepresentationManager assembles working representations using multiple retrieval strategies.

// RepresentationConfig configures the RepresentationManager.
type RepresentationConfig struct {
    Storage           adapter.Storage
    SemanticBudget    int                                    // Max semantic search results
    MostDerivedBudget int                                    // Max most-derived observations
    RecentBudget      int                                    // Max recent observations
    EmbeddingFunc     func(ctx context.Context, text string) ([]float32, error)
}

// NewRepresentationManager creates a new RepresentationManager.
func NewRepresentationManager(cfg RepresentationConfig) *RepresentationManager

// GetWorkingRepresentation retrieves a working representation combining semantic,
// most-derived, and recent observations.
func (rm *RepresentationManager) GetWorkingRepresentation(ctx context.Context, query, sessionID, userID, appName string) (*WorkingRepresentation, error)

// WorkingRepresentation holds assembled observations for context injection.
type WorkingRepresentation struct {
    Observations  []adapter.Observation
    SemanticCount int
    DerivedCount  int
    RecentCount   int
    TotalScore    float64
}

// Format returns a formatted string with [semantic], [derived], [recent] prefixes.
func (wr *WorkingRepresentation) Format() string
PeerCard

PeerCard stores scored facts about users (peers) with a capacity of 40 facts.

// PeerFact represents a single fact about a peer.
type PeerFact struct {
    Content string                   // The fact content
    Score   float64                  // Confidence score (0.0 - 1.0)
    Type    adapter.ObservationLevel  // explicit, deductive, inductive, contradiction
    Tags    []string                 // Categorical tags
}

// PeerCard stores facts about a peer (user).
type PeerCard struct {
    // peerID and facts are internal
}

// NewPeerCard creates a new peer card for the given peer ID.
func NewPeerCard(peerID string) *PeerCard

// PeerID returns the ID of the peer this card represents.
func (pc *PeerCard) PeerID() string

// AddFact adds a fact to the peer card. Evicts lowest-scoring fact if at capacity.
func (pc *PeerCard) AddFact(fact PeerFact)

// Facts returns all facts in the peer card.
func (pc *PeerCard) Facts() []PeerFact

// ReplaceFacts replaces all facts with the given slice.
func (pc *PeerCard) ReplaceFacts(facts []PeerFact)

// Render returns a formatted string representation of the peer card.
func (pc *PeerCard) Render() string
Summarizer

The Summarizer creates periodic conversation summaries at defined intervals.

// SummarizerConfig configures the summarizer.
type SummarizerConfig struct {
    LLM           model.LLM
    Storage       adapter.Storage
    ShortInterval int // Messages per short summary (default: 20)
    LongInterval  int // Messages per long summary (default: 60)
}

// NewSummarizer creates a new summarizer.
func NewSummarizer(cfg SummarizerConfig) *Summarizer

// ShouldSummarize checks if a summary should be created at this message count.
// Returns the summary type and true if a summary should be created.
func (s *Summarizer) ShouldSummarize(messageCount int) (SummaryType, bool)

// Summarize creates a summary of the given messages.
func (s *Summarizer) Summarize(ctx context.Context, messages []TimestampedMessage) (*Summary, error)

// StoreSummary persists a summary to storage as a special observation.
func (s *Summarizer) StoreSummary(ctx context.Context, sessionID string, summary *Summary) error

// GetBothSummaries retrieves short and long summaries for a session.
func (s *Summarizer) GetBothSummaries(ctx context.Context, sessionID string) (*Summary, *Summary, error)
CompactionPlugin

The CompactionPlugin provides session event compaction for context window management.

// Config configures the compaction plugin.
type Config struct {
    Strategy   Strategy  // Required: TruncationStrategy, SummarizationStrategy, or CompositeStrategy
    MaxEvents  int       // Trigger when event count exceeds this (0 = disabled)
    MaxTokens  int       // Trigger when estimated tokens exceed this (0 = disabled)
    Interval   int       // Trigger every N events (0 = disabled)
    KeepRecent int       // Always preserve this many recent events (default: 10)
    EnableAfterAgent bool // Enable post-run compaction check
}

// Create a truncation strategy (fast, keeps last N events)
truncationStrategy := &compaction.TruncationStrategy{RetainCount: 50}

// Create a summarization strategy (LLM-based summary)
summaryStrategy := &compaction.SummarizationStrategy{
    LLM: llm,  // Required for summarization
    Instruction: "Custom prompt template",  // Optional
}

// Create the plugin
plugin, err := compaction.NewPlugin(&compaction.Config{
    Strategy:   truncationStrategy,
    MaxEvents:  100,
    MaxTokens:  4000,
    KeepRecent: 20,
})

// Or use standalone callbacks for direct LlmAgent usage
beforeCallback, err := compaction.BeforeModelCallback(config)
afterCallback, err := compaction.AfterAgentCallback(config)
BackgroundCompactor

The BackgroundCompactor provides APIs for external background compaction jobs.

// Create a background compactor
bc := compaction.NewBackgroundCompactor(storage, llm)

// Query observations for compaction
candidates, err := bc.QueryObservationsForCompaction(ctx, compaction.CompactionQueryOptions{
    OlderThan:  time.Now().Add(-30 * 24 * time.Hour),  // 30 days old
    MinAge:     7 * 24 * time.Hour,                   // At least 7 days
    MaxResults: 100,
})

// Create a summary from observations
summary, err := bc.CreateCompactionSummary(ctx, candidates, compaction.SummaryOptions{
    Instruction: "Consolidate these observations",
    Tags:        []string{"archived"},
})

// Archive observations (soft delete)
err = bc.ArchiveObservations(ctx, []string{"obs-1", "obs-2"})

// Purge archived observations older than cutoff
count, err := bc.PurgeArchivedObservations(ctx, time.Now().Add(-90*24*time.Hour))
Dialectic

The Dialectic provides LLM-powered memory query capabilities with synthesized answers.

// DialecticConfig configures the Dialectic.
type DialecticConfig struct {
    LLM        model.LLM
    Storage    adapter.Storage
    MaxResults int // Default: 10
}

// NewDialectic creates a new Dialectic with the given configuration.
func NewDialectic(cfg DialecticConfig) *Dialectic

// Query performs a memory-based query and returns a synthesized answer.
func (d *Dialectic) Query(ctx context.Context, query string, opts QueryOptions) (string, error)
MemoryTool

MemoryTool implements the ADK tool interface, allowing agents to search their own memory.

// SearchMemoryArgs matches the tool schema expected by the LLM.
type SearchMemoryArgs struct {
    Query      string `json:"query"`
    MaxResults int    `json:"max_results,omitempty"`
}

// ToolObservation represents a single observation in tool results.
type ToolObservation struct {
    Content string   `json:"content"`
    Level   string   `json:"level"`
    Tags    []string `json:"tags,omitempty"`
}

// NewMemoryTool creates a new memory search tool wired to a Provider.
func NewMemoryTool(provider *Provider) *MemoryTool

// Name returns the name of the tool: "search_memory"
func (m *MemoryTool) Name() string

// Description returns a description for the LLM.
func (m *MemoryTool) Description() string

// Declaration returns the function declaration for the LLM.
func (m *MemoryTool) Declaration() *genai.FunctionDeclaration

// Run executes the memory search (implements toolinternal.FunctionTool).
func (m *MemoryTool) Run(ctx tool.Context, args any) (map[string]any, error)

// ProcessRequest implements toolinternal.RequestProcessor.
func (m *MemoryTool) ProcessRequest(ctx tool.Context, req *model.LLMRequest) error
Storage (Adapter)

The Storage interface defines the contract for observation storage backends.

// Storage defines the interface for observation storage backends.
type Storage interface {
    // Store saves an observation to storage.
    Store(ctx context.Context, obs *Observation) error

    // GetByID retrieves an observation by its ID.
    GetByID(ctx context.Context, id string) (*Observation, error)

    // Search finds observations matching the given options.
    Search(ctx context.Context, opts *SearchOptions) ([]SearchResult, error)

    // Forget deletes an observation by ID.
    Forget(ctx context.Context, id string) error

    // Purge deletes observations matching the filter.
    Purge(ctx context.Context, filter map[string]string) error

    // IncrementTimesDerived increments the times_derived counter for an observation.
    IncrementTimesDerived(ctx context.Context, id string) error

    // Close releases any resources held by the storage.
    Close() error

    // QueryMostDerived returns observations sorted by times_derived DESC.
    QueryMostDerived(ctx context.Context, sessionID, userID, appName string, limit int) ([]Observation, error)

    // QueryRecent returns observations sorted by id DESC (ULID-based, time-ordered).
    QueryRecent(ctx context.Context, sessionID, userID, appName string, limit int) ([]Observation, error)
}

// SearchMode indicates which search strategy to use.
const (
    SearchModeHybrid SearchMode = iota   // Reciprocal Rank Fusion of vector + FTS (default zero value)
    SearchModeVector                     // Vector similarity search
    SearchModeFTS                        // Full-text search
)

// Observation represents a single extracted fact.
type Observation struct {
    ID           string
    Content      string
    Level        ObservationLevel  // explicit, deductive, inductive, contradiction
    SessionID    string
    UserID       string
    AppName      string
    Tags         []string
    TimesDerived int
    CreatedAt    time.Time
    Embedding    []float32
}

// Score returns a composite score based on level and times_derived.
func (o Observation) Score() float64
Storage Implementations
// InMemory creates a lightweight map-based storage (for testing).
// Located in github.com/ieshan/adk-go-memory/adapter package.
func InMemory() *MemoryStorage

// NewSQLiteStorage creates a file-based SQLite storage (for production).
// Located in github.com/ieshan/adk-go-memory/adapter/sqlite submodule.
func NewSQLiteStorage(path string) (*SQLiteStorage, error)

// NewSQLiteStorageWithGORM creates a SQLite storage from an existing *gorm.DB connection.
// The caller retains ownership of the GORM connection and must close it separately.
// Located in github.com/ieshan/adk-go-memory/adapter/sqlite submodule.
func NewSQLiteStorageWithGORM(db *gorm.DB) (*SQLiteStorage, error)
Using an Existing GORM Connection

When integrating with an existing GORM setup or connection pool:

package main

import (
    "log"

    "github.com/ieshan/adk-go-memory/adapter/sqlite"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func main() {
    // You might have an existing GORM connection
    db, err := gorm.Open(sqlite.Open("/path/to/existing.db"), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }

    // Create storage using the existing connection
    // Caller retains ownership of db
    storage, err := sqlite.NewSQLiteStorageWithGORM(db)
    if err != nil {
        log.Fatal(err)
    }
    defer storage.Close() // This won't close db

    // Use storage normally...
    // db remains usable after storage.Close()
}
Exported GORM Model

The SQLite adapter exports its GORM model for advanced use cases:

import "github.com/ieshan/adk-go-memory/adapter/sqlite"

// Access the GORM model directly for custom queries
var obs sqlite.StorageObservation

// Use with GORM's fluent API
db.Where("session_id = ?", "sess1").Find(&obs)

Agent Integration Examples

Basic Agent with Memory

A single agent that automatically extracts and recalls facts:

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "strings"

    "google.golang.org/genai"

    "google.golang.org/adk/agent"
    adkmemory "google.golang.org/adk/memory"

    memory "github.com/ieshan/adk-go-memory"
    "github.com/ieshan/adk-go-memory/adapter"
    adkagent "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
)

func main() {
    ctx := context.Background()

    // Setup in-memory storage
    storage := adapter.InMemory()
    defer storage.Close()

    // Initialize LLM
    modelLLM, err := createLLM(ctx)
    if err != nil {
        log.Fatalf("Failed to create LLM: %v", err)
    }

    // Create deriver for automatic fact extraction
    deriver := memory.NewDeriver(memory.DeriverConfig{
        LLM:     modelLLM,
        Storage: storage,
    })

    // Create memory service
    svc := memory.NewService(memory.ServiceConfig{
        Storage: storage,
        Deriver: deriver,
    })
    defer svc.Close()

    // Create agent with memory-aware instruction and callback
    agentInst, err := llmagent.New(llmagent.Config{
        Name:        "memory_assistant",
        Model:       modelLLM,
        Description: "An assistant that remembers facts about users",
        Instruction: "You are a helpful assistant. Use the conversation context to answer questions.",
        BeforeModelCallbacks: []llmagent.BeforeModelCallback{
            injectMemoryContext(svc),
        },
    })
    if err != nil {
        log.Fatalf("Failed to create agent: %v", err)
    }

    // Create runner with memory service
    appName := "memory_app"
    sessionService := session.InMemoryService()
    r, err := runner.New(runner.Config{
        AppName:           appName,
        Agent:             agentInst,
        SessionService:    sessionService,
        MemoryService:     svc,
        AutoCreateSession: true,
    })
    if err != nil {
        log.Fatalf("Failed to create runner: %v", err)
    }

    // Run the agent
    userID := "user1"
    msg := genai.NewContentFromText("My name is Alice and I love Go programming", genai.RoleUser)
    
    for event, err := range r.Run(ctx, userID, "", msg, adkagent.RunConfig{}) {
        if err != nil {
            log.Printf("Error: %v", err)
            continue
        }
        if event.LLMResponse.Content != nil {
            for _, part := range event.LLMResponse.Content.Parts {
                fmt.Print(part.Text)
            }
        }
    }
}

// injectMemoryContext returns a BeforeModelCallback that searches memory
// and injects relevant context into the system prompt.
func injectMemoryContext(svc *memory.Service) llmagent.BeforeModelCallback {
    return func(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
        // Find the most recent user content
        var userQuery string
        for i := len(req.Contents) - 1; i >= 0; i-- {
            content := req.Contents[i]
            if content.Role == genai.RoleUser && len(content.Parts) > 0 {
                userQuery = content.Parts[0].Text
                break
            }
        }

        if userQuery == "" {
            return nil, nil
        }

        // Search memory for relevant context
        searchResp, err := svc.SearchMemory(ctx, &adkmemory.SearchRequest{
            Query:   userQuery,
            UserID:  ctx.UserID(),
            AppName: ctx.AppName(),
        })
        if err != nil || len(searchResp.Memories) == 0 {
            return nil, nil
        }

        // Build and inject memory context
        var memContext strings.Builder
        memContext.WriteString("\n\n**Relevant Information from Memory:**\n")
        for _, mem := range searchResp.Memories {
            if mem.Content != nil && len(mem.Content.Parts) > 0 {
                memContext.WriteString("- " + mem.Content.Parts[0].Text + "\n")
            }
        }

        // Append to system instruction
        for _, content := range req.Contents {
            if content.Role == "system" || content.Role == genai.RoleModel {
                if len(content.Parts) > 0 {
                    content.Parts[0].Text += memContext.String()
                    break
                }
            }
        }
        return nil, nil
    }
}

func createLLM(ctx context.Context) (model.LLM, error) {
    if os.Getenv("GOOGLE_API_KEY") != "" {
        return gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
            APIKey: os.Getenv("GOOGLE_API_KEY"),
        })
    }
    return nil, fmt.Errorf("GOOGLE_API_KEY not set")
}
Agent with Memory Tool

An agent that can explicitly search its memory using a tool:

package main

import (
    "context"
    "log"

    memory "github.com/ieshan/adk-go-memory"
    "github.com/ieshan/adk-go-memory/adapter"
    adkagent "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
)

func main() {
    ctx := context.Background()

    // Setup storage and memory service
    storage := adapter.InMemory()
    defer storage.Close()

    modelLLM := getLLM()

    // Create deriver for automatic fact extraction
    deriver := memory.NewDeriver(memory.DeriverConfig{
        LLM:     modelLLM,
        Storage: storage,
    })

    // Create memory service
    svc := memory.NewService(memory.ServiceConfig{
        Storage: storage,
        Deriver: deriver,
    })
    defer svc.Close()

    // Create memory kit with tools
    kit, err := memory.New(memory.KitConfig{
        Storage: storage,
        LLM:     modelLLM,
    })
    if err != nil {
        log.Fatalf("Failed to create memory kit: %v", err)
    }
    defer kit.Close()

    // Create agent with memory tool
    agent, err := llmagent.New(llmagent.Config{
        Name:        "tool_enabled_assistant",
        Model:       modelLLM,
        Description: "Assistant with memory search capability",
        Instruction: `You are a helpful assistant with access to a memory tool.

When users ask about their preferences, past activities, or personal information,
use the search_memory tool to find relevant information from previous conversations.

To use the tool, call search_memory with a query describing what you're looking for.
Example queries: "user preferences", "user name", "user hobbies", "past activities"`,
        Tools: []tool.Tool{kit.LoadTool},
    })
    if err != nil {
        log.Fatalf("Failed to create agent: %v", err)
    }

    // Create runner
    appName := "memory_tool_app"
    sessionService := session.InMemoryService()
    r, err := runner.New(runner.Config{
        AppName:           appName,
        Agent:             agent,
        SessionService:    sessionService,
        MemoryService:     svc,
        AutoCreateSession: true,
    })
    if err != nil {
        log.Fatalf("Failed to create runner: %v", err)
    }

    // Use runner...
    _ = r
}

func getLLM() model.LLM {
    // Return your LLM implementation
    return nil
}
Parent Agent with Sub-Agents Sharing Memory

A parent agent that delegates to specialized sub-agents, all sharing the same memory:

package main

import (
    "context"
    "fmt"
    "log"

    "google.golang.org/genai"
    adkmemory "google.golang.org/adk/memory"

    memory "github.com/ieshan/adk-go-memory"
    "github.com/ieshan/adk-go-memory/adapter"
    adkagent "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
)

func main() {
    ctx := context.Background()

    // Setup shared storage and memory service
    storage := adapter.InMemory()
    defer storage.Close()

    modelLLM := getLLM()

    // Create deriver for automatic fact extraction
    deriver := memory.NewDeriver(memory.DeriverConfig{
        LLM:     modelLLM,
        Storage: storage,
    })

    // Create shared memory service
    svc := memory.NewService(memory.ServiceConfig{
        Storage: storage,
        Deriver: deriver,
    })
    defer svc.Close()

    // Create sub-agents
    factRecorder := createFactRecorder(modelLLM)
    greeter := createGreeter(modelLLM, svc)

    // Create parent agent with sub-agents
    parentAgent, err := llmagent.New(llmagent.Config{
        Name:        "assistant",
        Model:       modelLLM,
        Description: "Main assistant that can record facts or greet users. Delegate to fact_recorder for recording facts, greeter for greetings.",
        Instruction: "You are a helpful assistant with two specialized sub-agents:\n" +
            "1. fact_recorder - Use when users share facts about themselves\n" +
            "2. greeter - Use for greetings and social interactions\n" +
            "\nDelegate to the appropriate sub-agent based on the user's message.",
        SubAgents: []adkagent.Agent{factRecorder, greeter},
    })
    if err != nil {
        log.Fatalf("Failed to create parent agent: %v", err)
    }

    // Create runner with SHARED memory service
    // All sub-agents benefit from the same memory context
    appName := "subagent_app"
    sessionService := session.InMemoryService()
    r, err := runner.New(runner.Config{
        AppName:           appName,
        Agent:             parentAgent,
        SessionService:    sessionService,
        MemoryService:     svc, // Shared across all agents
        AutoCreateSession: true,
    })
    if err != nil {
        log.Fatalf("Failed to create runner: %v", err)
    }

    // Use runner...
    _ = r
}

// createFactRecorder creates a sub-agent that records user facts.
func createFactRecorder(modelLLM model.LLM) adkagent.Agent {
    agent, err := llmagent.New(llmagent.Config{
        Name:        "fact_recorder",
        Model:       modelLLM,
        Description: "Records and acknowledges user facts.",
        Instruction: "When users share facts about themselves, acknowledge and confirm you've noted them. " +
            "Be warm and encouraging about sharing.",
    })
    if err != nil {
        log.Fatalf("Failed to create fact_recorder: %v", err)
    }
    return agent
}

// createGreeter creates a sub-agent that greets users using memory context.
// This sub-agent explicitly searches memory to personalize greetings.
func createGreeter(modelLLM model.LLM, svc *memory.Service) adkagent.Agent {
    agent, err := llmagent.New(llmagent.Config{
        Name:        "greeter",
        Model:       modelLLM,
        Description: "Greets users by name using memory context.",
        Instruction: "Greet the user warmly. If you know their name from context, use it! " +
            "Be friendly and personable.",
        BeforeModelCallbacks: []llmagent.BeforeModelCallback{
            func(ctx adkagent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
                // Search memory for user name
                searchResp, err := svc.SearchMemory(ctx, &adkmemory.SearchRequest{
                    Query:   "user name",
                    UserID:  ctx.UserID(),
                    AppName: ctx.AppName(),
                })
                if err != nil || len(searchResp.Memories) == 0 {
                    return nil, nil
                }

                // Inject memory into system context
                memContext := "\n\n**Known Facts:**\n"
                for _, mem := range searchResp.Memories {
                    if mem.Content != nil && len(mem.Content.Parts) > 0 {
                        memContext += "- " + mem.Content.Parts[0].Text + "\n"
                    }
                }

                // Find system content and append
                for _, content := range req.Contents {
                    if content.Role == "system" || content.Role == genai.RoleModel {
                        if len(content.Parts) > 0 {
                            content.Parts[0].Text += memContext
                            break
                        }
                    }
                }
                return nil, nil
            },
        },
    })
    if err != nil {
        log.Fatalf("Failed to create greeter: %v", err)
    }
    return agent
}

func getLLM() model.LLM {
    // Return your LLM implementation
    return nil
}
Advanced: Multi-Agent System with Provider and Peer Cards

A sophisticated setup using the Provider for context assembly and PeerCards for user modeling:

package main

import (
    "context"
    "log"

    memory "github.com/ieshan/adk-go-memory"
    "github.com/ieshan/adk-go-memory/adapter"
    "github.com/ieshan/adk-go-memory/adapter/sqlite"
    adkagent "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
)

func main() {
    ctx := context.Background()

    // Setup storage (requires CGO-enabled sqlite submodule)
    storage, err := sqlite.NewSQLiteStorage("/data/agent_memory.db")
    if err != nil {
        log.Fatalf("Failed to create storage: %v", err)
    }
    defer storage.Close()

    modelLLM := getLLM()

    // Create embedding function for semantic search
    embeddingFunc := func(ctx context.Context, text string) ([]float32, error) {
        // Use your embedding model here
        return nil, nil
    }

    // Create all memory components
    deriver := memory.NewDeriver(memory.DeriverConfig{
        LLM:     modelLLM,
        Storage: storage,
    })

    summarizer := memory.NewSummarizer(memory.SummarizerConfig{
        LLM:           modelLLM,
        Storage:       storage,
        ShortInterval: 20,
        LongInterval:  60,
    })

    dialectic := memory.NewDialectic(memory.DialecticConfig{
        LLM:        modelLLM,
        Storage:    storage,
        MaxResults: 10,
    })

    // Create provider with full pipeline
    provider := memory.NewProvider(memory.ProviderConfig{
        Storage:       storage,
        Deriver:       deriver,
        Summarizer:    summarizer,
        Dialectic:     dialectic,
        EmbeddingFunc: embeddingFunc,
    })
    defer provider.Close()

    // Create memory service with provider
    svc := memory.NewService(memory.ServiceConfig{
        Storage:  storage,
        Deriver:  deriver,
        Provider: provider,
    })
    defer svc.Close()

    // Load peer card for user on session start
    userID := "user123"
    if err := provider.LoadPeerCardFromMemory(ctx, userID); err != nil {
        log.Printf("Failed to load peer card: %v", err)
    }

    // Access peer card
    peerCard := provider.GetOrCreatePeerCard(userID)
    log.Printf("Loaded %d facts for user %s", len(peerCard.Facts()), userID)

    // Access peer card facts
    facts := peerCard.Facts()

    // Create agent with comprehensive memory context
    agent, err := llmagent.New(llmagent.Config{
        Name:        "advanced_assistant",
        Model:       modelLLM,
        Description: "Assistant with full memory pipeline",
        Instruction: "You are a helpful assistant with comprehensive memory.",
        BeforeModelCallbacks: []llmagent.BeforeModelCallback{
            func(ctx adkagent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
                // Get comprehensive memory context from provider
                memContext, err := provider.GetMemoryContext(ctx, "user preferences", ctx.SessionID(), ctx.UserID(), ctx.AppName())
                if err != nil {
                    return nil, nil
                }

                // Get peer card facts
                pc := provider.GetOrCreatePeerCard(ctx.UserID())
                peerContext := pc.Render()

                // Combine contexts
                fullContext := memContext + "\n" + peerContext

                // Inject into request
                for _, content := range req.Contents {
                    if content.Role == "system" {
                        if len(content.Parts) > 0 {
                            content.Parts[0].Text += "\n\n**Memory Context:**\n" + fullContext
                            break
                        }
                    }
                }
                return nil, nil
            },
        },
    })
    if err != nil {
        log.Fatalf("Failed to create agent: %v", err)
    }

    // Create runner
    sessionService := session.InMemoryService()
    r, err := runner.New(runner.Config{
        AppName:           "advanced_app",
        Agent:             agent,
        SessionService:    sessionService,
        MemoryService:     svc,
        AutoCreateSession: true,
    })
    if err != nil {
        log.Fatalf("Failed to create runner: %v", err)
    }

    // Use runner...
    _ = r
}

func getLLM() model.LLM {
    // Return your LLM implementation
    return nil
}
Agent with Memory Compaction

For long-running conversations, use the compaction plugin to manage context window size:

package main

import (
    "context"
    "log"

    memory "github.com/ieshan/adk-go-memory"
    "github.com/ieshan/adk-go-memory/adapter"
    "github.com/ieshan/adk-go-memory/adapter/sqlite"
    "github.com/ieshan/adk-go-memory/compaction"
    adkagent "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model"
    "google.golang.org/adk/plugin"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
)

func main() {
    ctx := context.Background()

    // Setup storage
    storage, err := sqlite.NewSQLiteStorage("/data/memory.db")
    if err != nil {
        log.Fatalf("Failed to create storage: %v", err)
    }
    defer storage.Close()

    modelLLM := getLLM()

    // Create memory kit with compaction enabled
    kit, err := memory.New(memory.KitConfig{
        Storage: storage,
        LLM:     modelLLM,
        Compaction: &compaction.Config{
            // Use summarization strategy for intelligent compaction
            Strategy:   &compaction.SummarizationStrategy{LLM: modelLLM},
            MaxEvents:  100,  // Compact when > 100 events
            MaxTokens:  4000, // Compact when > 4000 tokens
            KeepRecent: 20,   // Always keep 20 most recent events
        },
        DeltaMode: true, // Only process new events in AddSessionToMemory
    })
    if err != nil {
        log.Fatalf("Failed to create memory kit: %v", err)
    }
    defer kit.Close()

    // Create agent
    agent, err := llmagent.New(llmagent.Config{
        Name:        "compaction_assistant",
        Model:       modelLLM,
        Description: "Assistant with memory compaction for long conversations",
        Instruction: "You are a helpful assistant with long-term memory.",
    })
    if err != nil {
        log.Fatalf("Failed to create agent: %v", err)
    }

    // Create runner with compaction plugin
    r, err := runner.New(runner.Config{
        AppName:           "compaction_app",
        Agent:             agent,
        SessionService:    session.InMemoryService(),
        MemoryService:     kit.Service,
        AutoCreateSession: true,
        PluginConfig: runner.PluginConfig{
            Plugins: []*plugin.Plugin{kit.Plugin}, // Add compaction plugin
        },
    })
    if err != nil {
        log.Fatalf("Failed to create runner: %v", err)
    }

    // The runner now automatically compacts sessions when thresholds are exceeded
    _ = r
}

func getLLM() model.LLM {
    // Return your LLM implementation
    return nil
}

Architecture

Core Components
┌─────────────────┐     ┌─────────────┐     ┌─────────────────┐
│   ADK Agent     │────▶│   Service   │────▶│    Deriver      │
│                 │     │             │     │  (LLM-powered   │
└─────────────────┘     └──────┬──────┘     │  extraction)    │
                                 │            └─────────────────┘
                                 │
                    ┌────────────┴────────────┐
                    │      Provider           │
                    │  (Context assembly)     │
                    └────────────┬────────────┘
                                 │
                    ┌────────────┴────────────┐
                    │   RepresentationManager │
                    │  ├─ Semantic Search    │
                    │  ├─ Most Derived       │
                    │  └─ Recent             │
                    └────────────┬────────────┘
                                 │
                    ┌────────────┴────────────┐
                    │   Storage Interface     │
                    │  ├─ MemoryStorage       │
                    │  │   (map-based, root)   │
                    │  └─ SQLiteStorage       │
                    │      (sqlite submodule) │
                    │      ├─ vec0 (vectors)  │
                    │      ├─ fts5 (text)     │
                    │      └─ observations    │
                    └─────────────────────────┘

Storage Modules:

  • Root adapter package (github.com/ieshan/adk-go-memory/adapter): MemoryStorage - pure Go, no CGO required
  • SQLite submodule (github.com/ieshan/adk-go-memory/adapter/sqlite): SQLiteStorage - requires CGO for sqlite-vec
Observation Levels

Observations are classified by confidence:

Level Score Description
explicit 0.9 Directly stated facts
deductive 0.7 Logical inferences
inductive 0.5 Patterns from multiple observations
contradiction 0.3 Conflicting statements
Search Modes
// Vector search (semantic similarity)
results, err := storage.Search(ctx, &adapter.SearchOptions{
    Embedding:  queryEmbedding,
    Mode:       adapter.SearchModeVector,
    MaxResults: 10,
})

// FTS search (text matching)
results, err := storage.Search(ctx, &adapter.SearchOptions{
    Query:      "user likes golang",
    Mode:       adapter.SearchModeFTS,
    MaxResults: 10,
})

// Hybrid search (RRF fusion of both)
results, err := storage.Search(ctx, &adapter.SearchOptions{
    Query:      "user likes golang",
    Embedding:  queryEmbedding,
    Mode:       adapter.SearchModeHybrid,
    MaxResults: 10,
})
Working Representations

Working representations combine three strategies for context assembly:

rm := memory.NewRepresentationManager(memory.RepresentationConfig{
    Storage:           storage,
    SemanticBudget:    5,  // Top semantic search results
    MostDerivedBudget: 5,  // Most referenced facts
    RecentBudget:      5,  // Most recent observations
    EmbeddingFunc:     embedFunc, // For semantic search
})

rep, err := rm.GetWorkingRepresentation(ctx, query, sessionID, userID, appName)
if err != nil {
    log.Fatal(err)
}

// Format for context injection
context := rep.Format()
// Output:
// [semantic] User likes Go programming
// [semantic] User works at Google
// [derived] User prefers backend development
// [recent] User asked about databases
Peer Cards

User profiles with scored facts:

provider := memory.NewProvider(memory.ProviderConfig{
    Storage: storage,
})

// Load user's peer card
pc := provider.GetOrCreatePeerCard("user-123")

// Add facts
pc.AddFact(memory.PeerFact{
    Content: "User prefers Python over Java",
    Score:   0.85,
    Type:    adapter.LevelExplicit,
    Tags:    []string{"preference", "language"},
})

// Query by type or tag
preferences := pc.ByType(adapter.LevelExplicit)
langFacts := pc.ByTag("language")

// Render formatted profile
fmt.Println(pc.Render())

Addressing ADK-Go Memory Limitations

ADK-Go Built-in Memory

The standard ADK-Go memory system provides:

  • Session storage via session.Service
  • Basic memory interface via memory.Service
  • Simple key-value storage
Limitations Addressed by adk-go-memory
ADK-Go Limitation adk-go-memory Solution
No automatic fact extraction LLM-powered Deriver extracts observations from conversations
No semantic search sqlite-vec vector similarity search with 1536-dim embeddings
No full-text search FTS5 virtual table for text search
No deduplication Hybrid search detects duplicates; increments TimesDerived
No user profiles PeerCard system tracks up to 40 facts per user
Simple context assembly Multi-strategy RepresentationManager (semantic + derived + recent)
No observation confidence Four-level confidence system (explicit → contradiction)
In-memory only Persistent SQLite storage with in-memory option
No memory tools MemoryTool implements ADK tool interface

Configuration

Storage Options
// In-memory (testing) - pure Go, no CGO required
storage := adapter.InMemory()

// File-based (production) - requires CGO for sqlite-vec
// import "github.com/ieshan/adk-go-memory/adapter/sqlite"
storage, err := sqlite.NewSQLiteStorage("/data/memory.db")
Deriver Configuration
deriver := memory.NewDeriver(memory.DeriverConfig{
    LLM:     llm,      // Any model.LLM implementation
    Storage: storage,
})
Provider with Full Pipeline
provider := memory.NewProvider(memory.ProviderConfig{
    Storage:       storage,
    Deriver:       deriver,
    Summarizer:    summarizer,    // Optional: periodic summaries
    Dialectic:     dialectic,     // Optional: LLM-powered Q&A
    EmbeddingFunc: embedFunc,     // Required for semantic search
})

Testing

The core package uses pure Go (no CGO required). Only the SQLite adapter needs CGO.

Repository examples are intentionally split:

  • examples/*/main.go uses adapter/sqlite to demonstrate real SQLite-backed usage.
  • examples/*/main_test.go uses adapter.InMemory() for portable no-CGO test runs.

Testing commands:

# Run core package tests (no CGO required)
go test -v ./...

# Run SQLite adapter tests (requires CGO)
CGO_ENABLED=1 go test -v -tags=sqlite_fts5 ./adapter/sqlite/...

# Run all tests
make check

# Run with race detection
make test-race

# Coverage report (root + sqlite adapter)
make coverage

Makefile Targets

make test          # Run core package tests (no CGO)
make test-sqlite   # Run SQLite adapter tests (requires CGO)
make test-race     # Run with race detector
make build         # Build all packages
make vet           # Run go vet
make check         # Run vet + test + test-sqlite
make coverage      # Generate coverage report (root + sqlite adapter)
make clean         # Clean artifacts

Dependencies

Core package (root module):

  • google.golang.org/adk - ADK-Go framework
  • google.golang.org/genai - Google GenAI SDK

SQLite adapter (adapter/sqlite submodule - requires CGO):

  • github.com/asg017/sqlite-vec-go-bindings - Vector search for SQLite
  • github.com/mattn/go-sqlite3 - SQLite driver (CGO)
  • gorm.io/gorm - GORM ORM
  • gorm.io/driver/sqlite - GORM SQLite driver

Migration Guide

From Old API to New API (v2)

If you were using the old NewMemoryKit function, migrate to the new New function:

Old API (deprecated):

kit, err := memory.NewMemoryKit(memory.MemoryKitConfig{
    Storage: storage,
    Deriver: deriver,
})

New API:

kit, err := memory.New(memory.KitConfig{
    Storage: storage,
    LLM:     llm,  // Deriver created internally if LLM provided
    Compaction: &compaction.Config{
        Strategy:   &compaction.SummarizationStrategy{LLM: llm},
        MaxEvents:  100,
        MaxTokens:  4000,
    },
})
Key Changes
Old New
NewMemoryKit() New()
MemoryKitConfig KitConfig
Manual Deriver creation LLM auto-creates Deriver
No compaction support Built-in compaction plugin
Manual component wiring One-stop MemoryKit
Component Access
// Both old and new use the same component names
kit.Service     // memory.Service
kit.Provider    // *Provider
kit.LoadTool    // tool.Tool
kit.PreloadTool // tool.Tool
kit.Tools       // []tool.Tool

// New in v2:
kit.Plugin      // *plugin.Plugin (compaction, if configured)

License

This project is licensed under the Mozilla Public License 2.0. See the LICENSE file for the full license text.

Contributing

Contributions welcome! Please ensure:

  1. Core tests pass: go test ./... (no CGO required)
  2. SQLite tests pass: CGO_ENABLED=1 go test -tags=sqlite_fts5 ./adapter/sqlite/...
  3. All checks pass: make check
  4. Code is formatted: go fmt ./...
  5. Keep docs/examples aligned with current module layout (adapter root + adapter/sqlite submodule).

Documentation

Overview

Package memory provides a memory layer for ADK-Go agents.

It includes observation extraction, storage, retrieval, and context assembly for agent memory management. The package implements the ADK-Go memory.Service interface and provides tools for automatic memory preloading and explicit memory search.

Key components:

  • MemoryKit: One-stop setup for ADK-Go v1.2.0+ integration with compaction support
  • Service: Implements google.golang.org/adk/memory.Service
  • Provider: Advanced memory with representation management and peer cards
  • Deriver: LLM-powered fact extraction from conversations
  • MemoryTool: Explicit memory search tool (LLM-callable)
  • PreloadMemoryTool: Automatic memory injection into system instructions
  • CompactionPlugin: Optional session event compaction for context window management

Package memory provides ADK-Go memory service components.

This file contains the Provider which wires together all memory components for comprehensive context assembly including representation management and peer cards.

Package memory provides ADK-Go memory service with automatic observation extraction, semantic search, and context assembly.

Index

Constants

View Source
const ApproximateTokensPerChar = 0.25

ApproximateTokensPerChar is the rough token-to-character ratio (4 chars ≈ 1 token).

Variables

This section is empty.

Functions

func EstimateTokens added in v1.0.2

func EstimateTokens(messages []TimestampedMessage) int

EstimateTokens approximates the token count for a slice of timestamped messages. Uses a rough heuristic: 4 characters ≈ 1 token.

Types

type Deriver

type Deriver struct {
	// contains filtered or unexported fields
}

Deriver extracts factual observations from conversations using an LLM. It analyzes messages and produces Observation values at various confidence levels (explicit, deductive, inductive, contradiction). Near-duplicate observations are detected via hybrid search and deduplicated by incrementing the existing observation's TimesDerived counter.

func NewDeriver

func NewDeriver(cfg DeriverConfig) *Deriver

NewDeriver creates a new Deriver with the given configuration.

func (*Deriver) Derive

func (d *Deriver) Derive(ctx context.Context, messages []TimestampedMessage, sessionID, userID, appName string) error

Derive extracts observations from timestamped messages and stores them. userID and appName are stored on each observation for filtering.

type DeriverConfig

type DeriverConfig struct {
	// LLM is used to extract observations from conversation messages.
	LLM model.LLM
	// Storage is the database adapter for saving observations.
	Storage adapter.Storage
	// EmbeddingFunc generates embeddings for deduplication search.
	// When provided, vector-based deduplication is enabled alongside FTS.
	// When nil, deduplication relies on FTS text matching only.
	EmbeddingFunc func(ctx context.Context, text string) ([]float32, error)
}

DeriverConfig configures the Deriver.

type KitConfig added in v1.0.2

type KitConfig struct {
	// Required
	Storage adapter.Storage

	// Optional: LLM for observation extraction and compaction
	// If provided, a Deriver will be created internally
	LLM model.LLM

	// Optional: Semantic search
	EmbeddingFunc func(ctx context.Context, text string) ([]float32, error)

	// Optional: Compaction configuration
	// If nil, compaction is disabled
	Compaction *compaction.Config

	// Optional: Advanced features
	EnableDialectic bool // LLM-powered Q&A over memory
	EnablePeerCards bool // User modeling
	DeltaMode       bool // Only process new events in AddSessionToMemory
}

KitConfig configures the memory kit.

type MemoryKit added in v1.0.1

type MemoryKit struct {
	Service     memory.Service // Implements google.golang.org/adk/memory.Service
	Provider    *Provider      // Advanced memory with context assembly
	LoadTool    tool.Tool      // Explicit memory search tool
	PreloadTool tool.Tool      // Automatic memory injection
	Tools       []tool.Tool    // All memory tools
	Plugin      *plugin.Plugin // Optional: session compaction
}

MemoryKit is the one-stop setup for ADK-Go v1.2.0+ integration.

The kit provides:

  • Service: Implements google.golang.org/adk/memory.Service
  • Provider: Advanced memory with representation management
  • LoadTool: Explicit memory search tool (LLM-callable)
  • PreloadTool: Automatic memory injection into system instructions
  • Tools: Memory tools (search, preload)
  • Plugin: Optional compaction plugin for session event compaction

Example usage:

kit, err := memory.New(memory.KitConfig{
    Storage: storage,
    LLM:     llm,  // Creates Deriver internally
    Compaction: &compaction.Config{
        Strategy:   &compaction.SummarizationStrategy{LLM: llm},
        MaxEvents:  100,
        MaxTokens:  4000,
        KeepRecent: 20,
    },
})
if err != nil {
    log.Fatal(err)
}
defer kit.Close()

func New added in v1.0.2

func New(cfg KitConfig) (*MemoryKit, error)

New creates a fully configured memory system.

Usage:

kit, err := memory.New(memory.KitConfig{
    Storage: storage,
    LLM:     llm,
    Compaction: &compaction.Config{
        Strategy:   &compaction.SummarizationStrategy{LLM: llm},
        MaxEvents:  100,
        MaxTokens:  4000,
        KeepRecent: 20,
    },
})
if err != nil {
    log.Fatal(err)
}
defer kit.Close()

runner.New(runner.Config{
    PluginConfig: runner.PluginConfig{
        Plugins: []*plugin.Plugin{kit.Plugin},
    },
})

func (*MemoryKit) Close added in v1.0.1

func (k *MemoryKit) Close() error

Close releases all resources held by the kit.

type MemoryTool

type MemoryTool struct {
	// contains filtered or unexported fields
}

MemoryTool implements toolinternal.FunctionTool and RequestProcessor for memory search.

func NewMemoryTool

func NewMemoryTool(provider *Provider) *MemoryTool

NewMemoryTool creates a new memory search tool wired to a Provider.

func (*MemoryTool) Declaration

func (m *MemoryTool) Declaration() *genai.FunctionDeclaration

Declaration returns the function declaration for the LLM.

func (*MemoryTool) Description

func (m *MemoryTool) Description() string

Description returns a description of the tool.

func (*MemoryTool) IsLongRunning

func (m *MemoryTool) IsLongRunning() bool

IsLongRunning indicates whether the tool is a long-running operation.

func (*MemoryTool) Name

func (m *MemoryTool) Name() string

Name returns the name of the tool.

func (*MemoryTool) ProcessRequest

func (m *MemoryTool) ProcessRequest(ctx tool.Context, req *model.LLMRequest) error

ProcessRequest implements toolinternal.RequestProcessor. It packs the tool declaration into the LLM request.

func (*MemoryTool) Run added in v1.0.1

func (m *MemoryTool) Run(ctx tool.Context, args any) (map[string]any, error)

Run executes the memory search (implements toolinternal.FunctionTool).

type PeerCard

type PeerCard struct {
	// contains filtered or unexported fields
}

PeerCard stores facts about a peer (user).

func NewPeerCard

func NewPeerCard(peerID string) *PeerCard

NewPeerCard creates a new peer card for the given peer ID.

func (*PeerCard) AddFact

func (pc *PeerCard) AddFact(fact PeerFact)

AddFact adds a fact to the peer card. If the card is at capacity, the lowest-scoring fact is evicted only if the new fact has a higher score; otherwise the new fact is discarded.

func (*PeerCard) Facts

func (pc *PeerCard) Facts() []PeerFact

Facts returns all facts in the peer card.

func (*PeerCard) PeerID

func (pc *PeerCard) PeerID() string

PeerID returns the ID of the peer this card represents.

func (*PeerCard) Render

func (pc *PeerCard) Render() string

Render returns a formatted string representation of the peer card.

func (*PeerCard) ReplaceFacts

func (pc *PeerCard) ReplaceFacts(facts []PeerFact)

ReplaceFacts replaces all facts with the given slice.

type PeerFact

type PeerFact struct {
	Content string
	Score   float64
	Type    adapter.ObservationLevel
	Tags    []string
}

PeerFact represents a single fact about a peer.

type PreloadMemoryTool added in v1.0.1

type PreloadMemoryTool struct {
	// contains filtered or unexported fields
}

PreloadMemoryTool implements ONLY RequestProcessor (no FunctionTool). It automatically preloads relevant memory context into system instructions before each LLM request, without requiring the model to call a function.

func NewPreloadMemoryTool added in v1.0.1

func NewPreloadMemoryTool(provider *Provider) *PreloadMemoryTool

NewPreloadMemoryTool creates a new preload memory tool wired to a Provider.

func (*PreloadMemoryTool) Description added in v1.0.1

func (t *PreloadMemoryTool) Description() string

Description implements tool.Tool.

func (*PreloadMemoryTool) IsLongRunning added in v1.0.1

func (t *PreloadMemoryTool) IsLongRunning() bool

IsLongRunning implements tool.Tool.

func (*PreloadMemoryTool) Name added in v1.0.1

func (t *PreloadMemoryTool) Name() string

Name implements tool.Tool.

func (*PreloadMemoryTool) ProcessRequest added in v1.0.1

func (t *PreloadMemoryTool) ProcessRequest(ctx tool.Context, req *model.LLMRequest) error

ProcessRequest implements toolinternal.RequestProcessor. It searches memory using the user's current query and injects relevant past conversations into system instructions.

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider wires together all memory components for ADK-Go.

func NewProvider

func NewProvider(cfg ProviderConfig) *Provider

NewProvider creates a new Provider with the given configuration.

func (*Provider) Close

func (p *Provider) Close() error

Close releases resources held by the provider.

func (*Provider) GetMemoryContext

func (p *Provider) GetMemoryContext(ctx context.Context, query string, sessionID, userID, appName string) (string, error)

GetMemoryContext retrieves memory context for the given query.

func (*Provider) GetOrCreatePeerCard

func (p *Provider) GetOrCreatePeerCard(peerID string) *PeerCard

GetOrCreatePeerCard gets or creates a peer card for the given peer ID.

func (*Provider) LoadPeerCardFromMemory

func (p *Provider) LoadPeerCardFromMemory(ctx context.Context, peerID string) error

LoadPeerCardFromMemory loads peer card facts from storage. Uses QueryMostDerived to retrieve the most important observations (highest derivation count = most referenced) for the peer, rather than just the most recent ones.

func (*Provider) OnSessionStart

func (p *Provider) OnSessionStart(ctx context.Context, sessionID, userID, appName string) error

OnSessionStart is called when a session starts.

func (*Provider) SearchMemory added in v1.0.1

func (p *Provider) SearchMemory(ctx context.Context, query, sessionID, userID, appName string) ([]adapter.Observation, error)

SearchMemory provides the tool-accessible search interface. This is called by both MemoryTool.Run and PreloadMemoryTool.ProcessRequest.

type ProviderConfig

type ProviderConfig struct {
	Storage       adapter.Storage
	EmbeddingFunc func(ctx context.Context, text string) ([]float32, error)
}

ProviderConfig configures the Provider.

type RepresentationConfig

type RepresentationConfig struct {
	Storage           adapter.Storage
	SemanticBudget    int
	MostDerivedBudget int
	RecentBudget      int
	EmbeddingFunc     func(ctx context.Context, text string) ([]float32, error)
}

RepresentationConfig configures the RepresentationManager.

type RepresentationManager

type RepresentationManager struct {
	// contains filtered or unexported fields
}

RepresentationManager retrieves working representations using multiple strategies for context assembly.

func NewRepresentationManager

func NewRepresentationManager(cfg RepresentationConfig) *RepresentationManager

NewRepresentationManager creates a new RepresentationManager.

func (*RepresentationManager) GetWorkingRepresentation

func (rm *RepresentationManager) GetWorkingRepresentation(ctx context.Context, query, sessionID, userID, appName string) (*WorkingRepresentation, error)

GetWorkingRepresentation retrieves a working representation for the given query.

type SearchMemoryArgs

type SearchMemoryArgs struct {
	Query      string `json:"query"`
	MaxResults int    `json:"max_results,omitempty"`
}

SearchMemoryArgs matches the tool schema expected by the LLM. Query is the search query string. MaxResults optionally limits the number of results (default: 10).

type SearchMemoryResults

type SearchMemoryResults struct {
	Observations []ToolObservation `json:"observations"`
}

SearchMemoryResults returned by the tool. Observations contains the matching memory entries found by the search.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service implements google.golang.org/adk/memory.Service with automatic observation extraction and retrieval.

func NewService

func NewService(cfg ServiceConfig) *Service

NewService creates a new Service with the given configuration.

func (*Service) AddSessionToMemory

func (s *Service) AddSessionToMemory(ctx context.Context, sess session.Session) error

AddSessionToMemory implements memory.Service. Extracts observations from session events and stores them. When EnableDeltaMode is true, only processes events since last compaction.

func (*Service) Close

func (s *Service) Close() error

Close releases resources held by the service.

func (*Service) SearchMemory

func (s *Service) SearchMemory(ctx context.Context, req *memory.SearchRequest) (*memory.SearchResponse, error)

SearchMemory implements memory.Service. Uses Provider for consistent behavior with tools (includes representation manager).

type ServiceConfig

type ServiceConfig struct {
	Storage            adapter.Storage
	Deriver            *Deriver
	Provider           *Provider
	EnableDeltaMode    bool   // NEW: use NumRecentEvents for delta processing
	CompactionStateKey string // NEW: session.State key for compaction tracking
}

ServiceConfig configures the Service.

type Summarizer

type Summarizer struct {
	// contains filtered or unexported fields
}

Summarizer creates session summaries at defined intervals.

func NewSummarizer

func NewSummarizer(cfg SummarizerConfig) *Summarizer

NewSummarizer creates a new summarizer.

func (*Summarizer) GetBothSummaries

func (s *Summarizer) GetBothSummaries(ctx context.Context, sessionID string) (*Summary, *Summary, error)

GetBothSummaries retrieves short and long summaries for a session.

func (*Summarizer) GetMessagesForSummarization added in v1.0.2

func (s *Summarizer) GetMessagesForSummarization(messages []TimestampedMessage) []TimestampedMessage

GetMessagesForSummarization returns messages to summarize, respecting KeepRecent. It returns messages excluding the most recent KeepRecent messages.

func (*Summarizer) ShouldSummarize

func (s *Summarizer) ShouldSummarize(messageCount int) (SummaryType, bool)

ShouldSummarize checks if a summary should be created at this message count.

func (*Summarizer) ShouldSummarizeEnhanced added in v1.0.2

func (s *Summarizer) ShouldSummarizeEnhanced(messageCount, estimatedTokens int) (SummaryType, string, bool)

ShouldSummarizeEnhanced checks both interval and threshold triggers. Returns the summary type and true if a summary should be created. This is the Phase 3 enhanced version supporting threshold-based triggers.

func (*Summarizer) StoreSummary

func (s *Summarizer) StoreSummary(ctx context.Context, sessionID string, summary *Summary) error

StoreSummary persists a summary to the storage (as a special observation).

func (*Summarizer) Summarize

func (s *Summarizer) Summarize(ctx context.Context, messages []TimestampedMessage) (*Summary, error)

Summarize creates a summary of the given messages.

type SummarizerConfig

type SummarizerConfig struct {
	LLM           model.LLM
	Storage       adapter.Storage
	ShortInterval int // Messages per short summary (default: 20)
	LongInterval  int // Messages per long summary (default: 60)

	// Threshold-based triggers (Phase 3 enhancement)
	MaxEvents  int // Trigger when events exceed this (0 = disabled)
	MaxTokens  int // Trigger when estimated tokens exceed this (0 = disabled)
	KeepRecent int // Always preserve this many recent events (default: 10)
}

SummarizerConfig configures the summarizer.

type Summary

type Summary struct {
	Type      SummaryType
	Content   string
	MessageID string
	CreatedAt time.Time
}

Summary represents a session summary.

type SummaryType

type SummaryType string

SummaryType represents the type of summary.

const (
	SummaryTypeShort SummaryType = "short"
	SummaryTypeLong  SummaryType = "long"
)

type TimestampedMessage

type TimestampedMessage struct {
	Content *genai.Content
	At      time.Time
	// Author is the entity that created this message (e.g., "user", "model").
	Author string
}

TimestampedMessage pairs a genai content with its wall-clock timestamp.

type ToolObservation

type ToolObservation struct {
	Content string   `json:"content"`
	Level   string   `json:"level"`
	Tags    []string `json:"tags,omitempty"`
}

ToolObservation represents a single observation in tool results. Content is the observation text. Level indicates confidence (explicit, deductive, inductive, contradiction). Tags are optional category labels associated with this observation.

type WorkingRepresentation

type WorkingRepresentation struct {
	Observations  []adapter.Observation
	SemanticCount int
	DerivedCount  int
	RecentCount   int
	TotalScore    float64
}

WorkingRepresentation holds observations assembled for context injection.

func (*WorkingRepresentation) Format

func (wr *WorkingRepresentation) Format() string

Format returns a formatted string of the working representation.

Directories

Path Synopsis
Package adapter provides storage abstractions for the memory layer.
Package adapter provides storage abstractions for the memory layer.
sqlite module
Package compaction provides session event compaction strategies for adk-go-memory.
Package compaction provides session event compaction strategies for adk-go-memory.

Jump to

Keyboard shortcuts

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