agent.Memory()

Distributed memory with automatic scoping, vectors, and pluggable backends

The agent.Memory() interface provides distributed state management across agents with automatic scoping to workflow, session, user, or global contexts.

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

Basic Operations

package main

import (
    "context"
    "github.com/anthropics/agentfield/sdk/go/agent"
)

func main() {
    app := agent.New(agent.Config{
        NodeID: "my-agent",
    })

    app.Reasoner("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
        settings, err := app.Memory().GetWithDefault(ctx, "settings", map[string]any{})
        if err != nil {
            return nil, err
        }

        // Delete a value
        err = app.Memory().Delete(ctx, "temp_data")
        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{
            "pref":     pref,
            "settings": settings,
            "keys":     keys,
        }, nil
    })
}

Core Methods

Prop

Type

Memory Scopes

The Go SDK provides four memory scopes with automatic scope ID resolution from the execution context.

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

// Get workflow-scoped memory
workflowMem := app.Memory().WorkflowScope()

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

// Read from any agent in the workflow
result, err := workflowMem.Get(ctx, "step_result")

Use for: Task coordination, intermediate results, cross-agent data sharing.

Default scope. Persists across multiple workflow executions within a session.

// Session scope is the default
err := app.Memory().Set(ctx, "preferences", prefs)

// Or explicitly via SessionScope()
sessionMem := app.Memory().SessionScope()
history, err := sessionMem.Get(ctx, "history")

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

User/actor-private state. Persists across sessions for the same user.

userMem := app.Memory().UserScope()

// Store user-specific data
err := userMem.Set(ctx, "api_cache", cachedData)

// Track rate limits per user
err = userMem.Set(ctx, "rate_limit_remaining", 950)

Use for: Caching, rate limit tracking, user configuration.

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

globalMem := app.Memory().GlobalScope()

// Store feature flags
err := globalMem.Set(ctx, "feature_flags", map[string]any{
    "newUI":        true,
    "betaFeatures": false,
})

// Get global config
config, err := globalMem.Get(ctx, "app_config")

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

Scoped Memory

Access memory in specific scopes using scope accessors. Each returns a *ScopedMemory with the same core methods.

// Workflow scope - isolated to current execution
workflowMem := app.Memory().WorkflowScope()

// Session scope - persists across workflows
sessionMem := app.Memory().SessionScope()

// User scope - persists across sessions
userMem := app.Memory().UserScope()

// Global scope - shared system-wide
globalMem := app.Memory().GlobalScope()

// Custom scope with explicit ID
customMem := app.Memory().Scoped(agent.ScopeSession, "custom-session-123")

ScopedMemory Methods

Prop

Type

Typed Retrieval

Use GetTyped to unmarshal values directly into Go structs:

type UserPreferences struct {
    Theme         string `json:"theme"`
    Notifications bool   `json:"notifications"`
}

func handler(ctx context.Context, input map[string]any) (any, error) {
    var prefs UserPreferences
    err := app.Memory().SessionScope().GetTyped(ctx, "preferences", &prefs)
    if err != nil {
        return nil, err
    }

    // prefs is now populated with the stored values
    return map[string]any{"theme": prefs.Theme}, nil
}

Vector Operations

Store and search embeddings for semantic similarity.

SetVector()

err := app.Memory().SetVector(ctx, "doc_1", embedding, map[string]any{
    "title":   "Introduction",
    "content": "Document content...",
})

Prop

Type

GetVector()

embedding, metadata, err := app.Memory().GetVector(ctx, "doc_1")
if err != nil {
    return nil, err
}
if embedding == nil {
    // Vector not found
}

SearchVector()

results, err := app.Memory().SearchVector(ctx, queryEmbedding, agent.SearchOptions{
    Limit:     10,
    Threshold: 0.7,
})

// results: []VectorSearchResult with Key, Score, Metadata, Scope, ScopeID
for _, r := range results {
    fmt.Printf("Key: %s, Score: %.2f\n", r.Key, r.Score)
}

Prop

Type

DeleteVector()

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

Scoped Vector Operations

All vector operations are also available on ScopedMemory:

globalMem := app.Memory().GlobalScope()

// Store vectors globally for cross-workflow search
err := globalMem.SetVector(ctx, "global_doc", embedding, metadata)

// Search globally
results, err := globalMem.SearchVector(ctx, queryEmbedding, agent.SearchOptions{
    Limit: 5,
})

Memory Backends

The Go SDK supports pluggable memory backends through the MemoryBackend interface.

InMemoryBackend (Default)

Thread-safe in-memory storage for development and testing:

// Uses InMemoryBackend by default
app := agent.New(agent.Config{
    NodeID: "my-agent",
})

// Or explicitly
backend := agent.NewInMemoryBackend()
app := agent.New(agent.Config{
    NodeID:        "my-agent",
    MemoryBackend: backend,
})

// Clear for testing
backend.Clear()
backend.ClearScope(agent.ScopeSession, "test-session")

InMemoryBackend data is lost when the process exits. Use ControlPlaneMemoryBackend for persistent, distributed storage in production.

ControlPlaneMemoryBackend

Distributed storage that delegates to the Agentfield control plane:

import "github.com/anthropics/agentfield/sdk/go/agent"

backend := agent.NewControlPlaneMemoryBackend(
    "https://your-control-plane.example.com",  // Control plane URL
    "your-api-token",                           // Authentication token
    "my-agent",                                 // Agent node ID
)

app := agent.New(agent.Config{
    NodeID:        "my-agent",
    MemoryBackend: backend,
})

This backend:

  • Persists data across agent restarts
  • Enables memory sharing between distributed agents
  • Supports vector operations with similarity search
  • Automatically propagates execution context headers

Custom Backends

Implement the MemoryBackend interface for custom storage:

type MemoryBackend interface {
    Set(scope MemoryScope, scopeID, key string, value any) error
    Get(scope MemoryScope, scopeID, key string) (any, bool, error)
    Delete(scope MemoryScope, scopeID, key string) error
    List(scope MemoryScope, scopeID string) ([]string, error)

    // Vector operations
    SetVector(scope MemoryScope, scopeID, key string, embedding []float64, metadata map[string]any) error
    GetVector(scope MemoryScope, scopeID, key string) ([]float64, map[string]any, bool, error)
    SearchVector(scope MemoryScope, scopeID string, embedding []float64, opts SearchOptions) ([]VectorSearchResult, error)
    DeleteVector(scope MemoryScope, scopeID, key string) error
}

Examples

Cross-Agent State Sharing

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

    analysis, err := analyzeTicket(ctx, ticketID)
    if err != nil {
        return nil, err
    }

    // Store in workflow memory for other agents
    workflowMem := app.Memory().WorkflowScope()
    err = workflowMem.Set(ctx, "ticket_analysis", map[string]any{
        "priority": analysis.Priority,
        "category": analysis.Category,
    })
    if err != nil {
        return nil, err
    }

    // Call specialist agent
    return app.Call(ctx, "specialist-agent.handle", map[string]any{
        "ticketId": ticketID,
    })
})

// Agent B: Specialist Agent (on different server)
app.Reasoner("handle", func(ctx context.Context, input map[string]any) (any, error) {
    // Access same workflow memory set by Agent A
    workflowMem := app.Memory().WorkflowScope()
    analysis, err := workflowMem.Get(ctx, "ticket_analysis")
    if err != nil {
        return nil, err
    }

    analysisMap := analysis.(map[string]any)
    return map[string]any{
        "handled":  true,
        "priority": analysisMap["priority"],
    }, nil
})

Session-Based Chat History

type Message struct {
    Role    string `json:"role"`
    Content string `json:"content"`
}

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

    // Use explicit session scope with user ID
    sessionMem := app.Memory().Scoped(agent.ScopeSession, userID)

    // Get history
    var history []Message
    _ = sessionMem.GetTyped(ctx, "history", &history)

    // Generate response with context
    response, err := app.AI(ctx, agent.AIRequest{
        System: "You are a helpful assistant.",
        User:   fmt.Sprintf("History: %v\nUser: %s", history[max(0, len(history)-5):], message),
    })
    if err != nil {
        return nil, err
    }

    // Update history
    history = append(history,
        Message{Role: "user", Content: message},
        Message{Role: "assistant", Content: response.Text},
    )
    err = sessionMem.Set(ctx, "history", history)
    if err != nil {
        return nil, err
    }

    return map[string]any{"response": response.Text}, nil
})
app.Reasoner("search_docs", func(ctx context.Context, input map[string]any) (any, error) {
    query := input["query"].(string)

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

    // Search for similar documents 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
    }

    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
})

Document Indexing

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

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

    // Store globally for cross-workflow search
    globalMem := app.Memory().GlobalScope()
    err = globalMem.SetVector(ctx, fmt.Sprintf("doc_%s", docID), embedding, map[string]any{
        "title":     title,
        "docId":     docID,
        "indexedAt": time.Now().Format(time.RFC3339),
    })
    if err != nil {
        return nil, err
    }

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

Best Practices

Key Naming Conventions

Use hierarchical, descriptive key names:

// Good - hierarchical and descriptive
app.Memory().Set(ctx, "customer_123.profile", profileData)
app.Memory().Set(ctx, "order_456.status", "shipped")

// Bad - flat and ambiguous
app.Memory().Set(ctx, "c123p", profileData)
app.Memory().Set(ctx, "status", "shipped")

Scope Selection

Choose the appropriate scope based on data lifetime:

// Workflow - task coordination (short-lived, multi-agent)
app.Memory().WorkflowScope().Set(ctx, "current_step", "processing")

// Session - user data (medium-lived, user-specific)
app.Memory().SessionScope().Set(ctx, "preferences", userPrefs)

// User - actor state (long-lived, user-private)
app.Memory().UserScope().Set(ctx, "rate_limit", remaining)

// Global - system config (permanent, system-wide)
app.Memory().GlobalScope().Set(ctx, "api_version", "2.1.0")

Error Handling

Always handle memory operation errors:

func safeMemoryOp(ctx context.Context, key string, data any) error {
    if err := app.Memory().Set(ctx, key, data); err != nil {
        // Log error and potentially use fallback
        log.Printf("Memory operation failed: %v", err)
        return err
    }
    return nil
}

func getWithFallback(ctx context.Context, key string, fallback any) any {
    val, err := app.Memory().Get(ctx, key)
    if err != nil || val == nil {
        return fallback
    }
    return val
}