Memory

Distributed memory with automatic scoping and vector operations for Go agents

Memory

Distributed memory with automatic scoping and vector operations

The Memory type provides hierarchical state management for Go agents. It supports multiple isolation scopes (workflow, session, user, global) with automatic scope ID resolution from execution context, plus vector operations for semantic search.

Memory is automatically shared between agents in the same workflow. When Agent A calls Agent B via app.Call(), both agents access the same workflow memory.

Basic Operations

app.RegisterReasoner("example", func(ctx context.Context, input map[string]any) (any, error) {
    // Set a value (session scope by default)
    err := app.Memory.Set(ctx, "user_preference", map[string]any{"theme": "dark"})
    if err != nil {
        return nil, err
    }

    // Get a value
    pref, err := app.Memory.Get(ctx, "user_preference")
    if err != nil {
        return nil, err
    }

    // Get with default value
    theme, err := app.Memory.GetWithDefault(ctx, "theme", "light")
    if err != nil {
        return nil, err
    }

    // Delete a value
    err = app.Memory.Delete(ctx, "user_preference")
    if err != nil {
        return nil, err
    }

    // List keys in current scope
    keys, err := app.Memory.List(ctx)
    if err != nil {
        return nil, err
    }

    return map[string]any{"keys": keys, "theme": theme}, nil
})

Core Methods

Prop

Type

Memory Scopes

Memory is organized into four hierarchical scopes. By default, operations use session scope, but you can access specific scopes using scope helpers.

Default scope. Persists across workflow executions within the same session.

// Default operations use session scope
app.Memory.Set(ctx, "preferences", prefs)
val, _ := app.Memory.Get(ctx, "preferences")

// Explicit session scope
sessionMem := app.Memory.SessionScope()
sessionMem.Set(ctx, "key", value)

Use for: User preferences, conversation history, shopping carts.

Task coordination. Shared across all agents in the same workflow execution.

workflowMem := app.Memory.WorkflowScope()

// Store intermediate results
workflowMem.Set(ctx, "step_result", data)

// Read in another agent (same workflow)
result, _ := workflowMem.Get(ctx, "step_result")

Use for: Multi-step workflows, intermediate results, cross-agent data sharing.

User-specific data. Persists across sessions for the same user/actor.

userMem := app.Memory.UserScope()

// Store user-specific data
userMem.Set(ctx, "learned_preferences", prefs)

// Access in future sessions
prefs, _ := userMem.Get(ctx, "learned_preferences")

Use for: Long-term user preferences, learned behaviors, user profiles.

System-wide data. Shared across all agents, sessions, and workflows.

globalMem := app.Memory.GlobalScope()

// Store feature flags
globalMem.Set(ctx, "feature_flags", map[string]any{
    "new_ui": true,
    "beta":   false,
})

// Access from any agent
flags, _ := globalMem.Get(ctx, "feature_flags")

Use for: Feature flags, system configuration, shared reference data.

Explicit Scoping

Use Scoped() for explicit scope and ID control:

// Explicit scope with custom scope ID
customMem := app.Memory.Scoped(agent.ScopeSession, "custom_session_123")
customMem.Set(ctx, "key", value)

MemoryScope Constants

const (
    ScopeWorkflow MemoryScope = "workflow"
    ScopeSession  MemoryScope = "session"
    ScopeUser     MemoryScope = "user"
    ScopeGlobal   MemoryScope = "global"
)

Vector Operations

Store and search embeddings for semantic similarity. Vector operations support the same scoping as regular memory.

SetVector

Store a vector embedding with optional metadata.

embedding := []float64{0.1, 0.2, 0.3, 0.4, 0.5}
metadata := map[string]any{
    "title":   "Introduction to Agentfield",
    "content": "Agentfield is a distributed agent framework...",
}

err := app.Memory.SetVector(ctx, "doc_123", embedding, metadata)

Prop

Type

GetVector

Retrieve a stored vector and its metadata.

embedding, metadata, err := app.Memory.GetVector(ctx, "doc_123")
if err != nil {
    return nil, err
}
if embedding == nil {
    // Vector not found
}
fmt.Println("Title:", metadata["title"])

Returns: (embedding []float64, metadata map[string]any, err error)

If the vector is not found, returns (nil, nil, nil).

SearchVector

Perform similarity search to find related vectors.

queryEmbedding := []float64{0.15, 0.25, 0.35, 0.45, 0.55}

results, err := app.Memory.SearchVector(ctx, queryEmbedding, agent.SearchOptions{
    Limit:     5,
    Threshold: 0.7,
})
if err != nil {
    return nil, err
}

for _, r := range results {
    fmt.Printf("Key: %s, Score: %.2f, Title: %s\n",
        r.Key, r.Score, r.Metadata["title"])
}

SearchOptions

Prop

Type

VectorSearchResult

Prop

Type

DeleteVector

Remove a vector from storage.

err := app.Memory.DeleteVector(ctx, "doc_123")

Scoped Vector Operations

Vector operations work with all scope helpers:

// Workflow-scoped vectors
workflowMem := app.Memory.WorkflowScope()
workflowMem.SetVector(ctx, "doc_1", embedding, metadata)
results, _ := workflowMem.SearchVector(ctx, query, agent.SearchOptions{Limit: 5})

// Global-scoped vectors (shared knowledge base)
globalMem := app.Memory.GlobalScope()
globalMem.SetVector(ctx, "kb_doc_1", embedding, metadata)

Examples

Cross-Agent Memory Sharing

// Agent A: Support Agent
app.RegisterReasoner("analyze_ticket", func(ctx context.Context, input map[string]any) (any, error) {
    ticket := input["ticket"].(string)

    analysis, err := app.AI(ctx, fmt.Sprintf("Analyze: %s", ticket))
    if err != nil {
        return nil, err
    }

    // Store in workflow memory (shared with other agents)
    workflowMem := app.Memory.WorkflowScope()
    workflowMem.Set(ctx, "ticket_analysis", map[string]any{
        "priority": "high",
        "category": "billing",
    })

    // Call specialist agent
    return app.Call(ctx, "specialist-agent.handle", input)
})

// Agent B: Specialist Agent (different process)
app.RegisterReasoner("handle", func(ctx context.Context, input map[string]any) (any, error) {
    // Access same workflow memory
    workflowMem := app.Memory.WorkflowScope()
    analysis, _ := workflowMem.Get(ctx, "ticket_analysis")

    // analysis contains {priority: "high", category: "billing"}
    return map[string]any{"handled": true, "analysis": analysis}, nil
})

RAG with Vector Memory

app.RegisterReasoner("index_document", func(ctx context.Context, input map[string]any) (any, error) {
    docID := input["doc_id"].(string)
    content := input["content"].(string)

    // Generate embedding (using external embedding service)
    embedding, err := generateEmbedding(content)
    if err != nil {
        return nil, err
    }

    // Store in global scope for cross-workflow access
    globalMem := app.Memory.GlobalScope()
    err = globalMem.SetVector(ctx, fmt.Sprintf("doc_%s", docID), embedding, map[string]any{
        "title":     input["title"],
        "content":   content[:500], // Store preview
        "indexed_at": time.Now().Format(time.RFC3339),
    })

    return map[string]any{"indexed": true, "doc_id": docID}, nil
})

app.RegisterReasoner("search_docs", func(ctx context.Context, input map[string]any) (any, error) {
    query := input["query"].(string)

    // Generate query embedding
    queryEmbedding, err := generateEmbedding(query)
    if err != nil {
        return nil, err
    }

    // Search in global scope
    globalMem := app.Memory.GlobalScope()
    results, err := globalMem.SearchVector(ctx, queryEmbedding, agent.SearchOptions{
        Limit:     5,
        Threshold: 0.7,
    })
    if err != nil {
        return nil, err
    }

    // Format results
    docs := make([]map[string]any, len(results))
    for i, r := range results {
        docs[i] = map[string]any{
            "key":   r.Key,
            "score": r.Score,
            "title": r.Metadata["title"],
        }
    }

    return map[string]any{"query": query, "results": docs}, nil
})

Session-Based State

app.RegisterReasoner("chat", func(ctx context.Context, input map[string]any) (any, error) {
    message := input["message"].(string)

    // Get conversation history from session
    history, _ := app.Memory.GetWithDefault(ctx, "history", []any{})

    // Type assertion for history
    historySlice, ok := history.([]any)
    if !ok {
        historySlice = []any{}
    }

    // Add user message
    historySlice = append(historySlice, map[string]any{
        "role":    "user",
        "content": message,
    })

    // Generate response
    response, err := app.AI(ctx, fmt.Sprintf("History: %v\nUser: %s", historySlice, message))
    if err != nil {
        return nil, err
    }

    // Add assistant response
    historySlice = append(historySlice, map[string]any{
        "role":    "assistant",
        "content": response.Text(),
    })

    // Save updated history
    app.Memory.Set(ctx, "history", historySlice)

    return map[string]any{"response": response.Text()}, nil
})

Typed Memory Access

Use GetTyped for automatic JSON unmarshaling into Go structs:

type UserPreferences struct {
    Theme        string `json:"theme"`
    Language     string `json:"language"`
    Timezone     string `json:"timezone"`
}

app.RegisterReasoner("get_preferences", func(ctx context.Context, input map[string]any) (any, error) {
    var prefs UserPreferences

    sessionMem := app.Memory.SessionScope()
    err := sessionMem.GetTyped(ctx, "user_preferences", &prefs)
    if err != nil {
        return nil, err
    }

    // prefs is now populated
    return prefs, nil
})

Storage Backends

The Go SDK supports two storage backends:

In-Memory Backend (Development)

Used by default when no control plane is configured. Data is lost when the process exits.

// Automatic when AgentFieldURL is empty
app, _ := agent.New(agent.Config{
    NodeID: "my-agent",
    // No AgentFieldURL = in-memory backend
})

Control Plane Backend (Production)

Distributed storage via the Agentfield control plane. Supports SQLite (development) or PostgreSQL (production).

app, _ := agent.New(agent.Config{
    NodeID:        "my-agent",
    AgentFieldURL: "http://localhost:8080",
    Token:         os.Getenv("BRAIN_TOKEN"),
})

The control plane backend automatically handles scope resolution via HTTP headers and provides distributed memory across multiple agent instances.

Best Practices

Use Appropriate Scopes

// ✅ Workflow scope for task coordination
workflowMem := app.Memory.WorkflowScope()
workflowMem.Set(ctx, "intermediate_result", data)

// ✅ Session scope for user state
app.Memory.Set(ctx, "user_preferences", prefs)

// ✅ Global scope for shared configuration
globalMem := app.Memory.GlobalScope()
globalMem.Set(ctx, "feature_flags", flags)

Handle Errors

// ✅ Always check for errors
val, err := app.Memory.Get(ctx, "key")
if err != nil {
    return nil, fmt.Errorf("memory get failed: %w", err)
}
if val == nil {
    // Key not found - use default
    val = defaultValue
}

Use Descriptive Keys

// ✅ Good - hierarchical and descriptive
app.Memory.Set(ctx, "customer_123_preferences", prefs)
app.Memory.Set(ctx, "order_456_status", status)

// ❌ Bad - ambiguous
app.Memory.Set(ctx, "data", prefs)
app.Memory.Set(ctx, "x", status)