AgentFieldbuild

Shared memory

Distributed key-value and vector memory with four isolation scopes for cross-agent state sharing.

Four memory scopes: global, session, actor, workflow

Distributed state that agents share, scoped to exactly the right audience.

Shared memory is not just a storage API. It is the distributed state fabric that lets agents coordinate without standing up Redis, manual namespacing, or pub/sub wiring first.

One agent can write workflow state, another can read it, and a third can react to changes. The scopes make that state visible to exactly the right audience.

# Agent A writes workflow context
await app.memory.set("ticket:T-123.sentiment", {"mood": "angry", "urgency": "high"})

# Agent B reads it later in the same workflow
sentiment = await app.memory.get("ticket:T-123.sentiment")

# Agent C reacts to changes automatically
@app.on_change("ticket:*:sentiment")
async def on_ticket_sentiment(event):
    await app.call("notifications.alert", key=event.key, change=event.data)

# Vector memory is available too for RAG and retrieval
await app.memory.set_vector("doc:chunk-1", embedding, metadata={"source": "contracts.pdf"})
hits = await app.memory.similarity_search(query_embedding, top_k=5)

What just happened

  • One write became shared workflow context for multiple agents
  • A watcher turned memory changes into reactive behavior without extra queue setup
  • Vector memory remained available on the same interface for retrieval patterns

Example change event:

{
  "key": "ticket:T-123.sentiment",
  "scope": "workflow",
  "scope_id": "wf-123",
  "action": "set",
  "data": { "mood": "angry", "urgency": "high" },
  "previous_data": null
}
What you get
  • Four isolation scopes -- global, session, actor, and workflow, from widest to narrowest.
  • Key-value storage -- set, get, delete, list_keys, exists across all scopes.
  • Vector storage -- store embeddings and run similarity search for RAG patterns.
  • Hierarchical lookup -- Python and TypeScript memory clients search workflow -> session -> actor -> global when you call get() without an explicit scope.
  • Reactive events -- subscribe to memory changes with pattern matching (Python and TypeScript).
  • Scoped accessors -- memory.session(id), memory.actor(id), memory.workflow(id), memory.global_scope for explicit scope targeting.
  • Go default scope -- memory.Get() reads session scope (or the current run when no session is present). Use GlobalScope(), SessionScope(), UserScope(), and WorkflowScope() for explicit access.
Memory scopes
ScopeLifetimeUse ForPython/TS NameGo Name
GlobalUntil explicitly deletedShared config, knowledge bases, cross-agent stateglobalglobal
SessionUntil session endsConversation context, per-session preferencessessionsession
ActorPersists across sessionsUser-specific learned data, per-actor configurationactoruser
WorkflowUntil workflow run completesIntermediate results, per-run computation stateworkflowworkflow

Hierarchical lookup order (most specific first):

workflow -> session -> actor -> global

Python and TypeScript clients check each scope in order and return the first match when you call memory.get("key") without specifying a scope. Values in narrower scopes override broader scopes. Go Memory.Get() reads session scope by default; use explicit scoped accessors for other scopes.

Key-value operations
from agentfield import Agent

app = Agent(node_id="my-agent")

@app.reasoner()
async def chat(message: str) -> dict:
    # Automatic scope (uses current execution context)
    await app.memory.set("last_message", message)
    last = await app.memory.get("last_message")

    # Check existence
    has_history = await app.memory.exists("conversation.history")

    # Delete a key
    await app.memory.delete("temp_data")

    # Explicit scope access
    await app.memory.global_scope.set("config", {"temperature": 0.2})
    config = await app.memory.global_scope.get("config", default={})
    keys = await app.memory.global_scope.list_keys()

    # Scoped to a session
    session_mem = app.memory.session("session-123")
    await session_mem.set("context", {"topic": "billing"})
    context = await session_mem.get("context")
    session_keys = await session_mem.list_keys()

    # Scoped to an actor (persists across sessions)
    actor_mem = app.memory.actor("user-456")
    await actor_mem.set("preferences", {"tone": "concise"})

    # Scoped to current workflow run
    wf_mem = app.memory.workflow("wf-789")
    await wf_mem.set("step1_output", {"ok": True})

    return {"processed": True}
Vector operations

Store embeddings and run similarity search for RAG, semantic routing, and knowledge retrieval patterns.

@app.reasoner()
async def index_and_search(text: str) -> dict:
    # Store a vector with metadata
    embedding = [0.1, 0.2, 0.3, 0.4]  # from your embedding model
    await app.memory.set_vector(
        "doc-001",
        embedding,
        metadata={"source": "manual", "topic": "billing"},
    )

    # Similarity search
    query_embedding = [0.1, 0.2, 0.35, 0.4]
    results = await app.memory.similarity_search(
        query_embedding,
        top_k=5,
        filters={"topic": "billing"},
    )

    # Delete a vector
    await app.memory.delete_vector("doc-001")

    # Scoped vector operations
    global_mem = app.memory.global_scope
    await global_mem.set_vector("shared-embedding", embedding, metadata={"public": True})
    global_results = await global_mem.similarity_search(query_embedding, top_k=3)

    return {"results": results}
Memory events

Subscribe to memory changes with pattern matching. When a key matching your pattern is written in any scope, your handler fires. This enables reactive patterns where one agent's write triggers another agent's logic.

Availability: Python and TypeScript only. Go does not support memory events.

# Decorator-based subscription
@app.on_change("user.*")
async def on_user_change(event):
    print(f"Key changed: {event.key}")
    print(f"New data: {event.data}")
    print(f"Scope: {event.scope}")

# Scoped event subscription
@app.on_change("config.*")
async def on_config_change(event):
    print(f"Global config changed: {event.key}")

# Session-scoped events
session = app.memory.session("session-123")

@session.on_change("conversation.*")
async def on_conversation_change(event):
    print(f"Conversation updated: {event.key}")

Pattern matching uses wildcards: "user.*" matches "user.preferences", "user.history", etc.

Patterns

Conversation History

Use session-scoped memory to maintain conversation state across turns.

@app.reasoner()
async def chat(message: str) -> dict:
    history = await app.memory.get("conversation.history", default=[])
    history.append({"role": "user", "content": message})

    response = await app.ai(
        system="You are a helpful assistant.",
        user=message,
        context={"history": history},
    )

    history.append({"role": "assistant", "content": response})
    await app.memory.set("conversation.history", history)

    return {"response": response}

Cross-Agent State Sharing

Use global memory as a shared blackboard between agents.

# Agent A: Write results to global memory
@app.reasoner()
async def analyzer(data: dict) -> dict:
    result = await app.ai(system="Analyze this data.", user=str(data))
    await app.memory.global_scope.set(f"analysis.{data['id']}", result)
    return result

# Agent B: Read results from global memory
@app.reasoner()
async def reporter(analysis_id: str) -> dict:
    analysis = await app.memory.global_scope.get(f"analysis.{analysis_id}")
    if not analysis:
        return {"error": "Analysis not found"}
    report = await app.ai(system="Generate a report.", user=str(analysis))
    return {"report": report}

Reactive Processing with Events

Trigger processing when data arrives without polling.

@app.on_change("analysis.*")
async def on_analysis_complete(event):
    analysis_id = event.key.split(".")[-1]
    report = await app.call(
        "reporter.generate",
        analysis_id=analysis_id,
        data=event.data,
    )
    await app.memory.global_scope.set(f"report.{analysis_id}", report)
SDK reference

Key-Value Methods

OperationPythonTypeScriptGo
Setawait memory.set(key, data)await memory.set(key, data)memory.Set(ctx, key, value)
Getawait memory.get(key, default=None)await memory.get(key)memory.Get(ctx, key)
Get with defaultawait memory.get(key, default={})--memory.GetWithDefault(ctx, key, default)
Deleteawait memory.delete(key)await memory.delete(key)memory.Delete(ctx, key)
Existsawait memory.exists(key)await memory.exists(key)--
List keysawait memory.global_scope.list_keys()await memory.listKeys()memory.List(ctx)

Vector Methods

OperationPythonTypeScriptGo
Set vectorawait memory.set_vector(key, embedding, metadata=)await memory.setVector(key, embedding, metadata?)memory.SetVector(ctx, key, embedding, metadata)
Searchawait memory.similarity_search(query, top_k=, filters=)await memory.searchVector(query, { topK, scope })memory.SearchVector(ctx, embedding, opts)
Delete vectorawait memory.delete_vector(key)await memory.deleteVector(key)memory.DeleteVector(ctx, key)
Get vector----memory.GetVector(ctx, key)
Embed text--await memory.embedText(text)--
Embed and set--await memory.embedAndSet(key, text, metadata?)--

Scope Accessors

ScopePythonTypeScriptGo
Globalmemory.global_scopememory.globalScopememory.GlobalScope()
Sessionmemory.session(id)memory.session(id)memory.SessionScope()
Actor / Usermemory.actor(id)memory.actor(id)memory.UserScope()
Workflowmemory.workflow(id)memory.workflow(id)memory.WorkflowScope()
Explicit----memory.Scoped(scope, id)

Event Methods

FeaturePythonTypeScriptGo
Watch changes@app.on_change(pattern)agent.watchMemory(pattern, handler)Not supported
Scoped watch@app.on_change(pattern)agent.watchMemory(pattern, handler, { scope })Not supported

Go -- GetTyped() for Type-Safe Retrieval

The Go SDK provides GetTyped() to deserialize memory values directly into Go structs, avoiding manual type assertions.

type UserPrefs struct {
    Tone     string `json:"tone"`
    Language string `json:"language"`
    Theme    string `json:"theme"`
}

// GetTyped deserializes the stored value into the target struct
// Available on ScopedMemory (e.g., mem.SessionScope(), mem.GlobalScope())
var prefs UserPrefs
err := mem.SessionScope().GetTyped(ctx, "preferences", &prefs)
if err != nil {
    // key not found or deserialization error
}
fmt.Println(prefs.Tone) // "concise"

// Works with any scope
var config AppConfig
err = mem.GlobalScope().GetTyped(ctx, "app_config", &config)

Signature:

func (s *ScopedMemory) GetTyped(ctx context.Context, key string, dest any) error

Go -- MemoryBackend Pluggable Interface

The Go SDK uses a pluggable backend interface for memory storage. Swap backends by passing them in agent.Config.

// MemoryBackend is the interface all storage backends implement
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)
    GetVector(scope MemoryScope, scopeID, key string) (embedding []float64, metadata map[string]any, found bool, err error)
    SetVector(scope MemoryScope, scopeID, key string, embedding []float64, metadata map[string]any) error
    SearchVector(scope MemoryScope, scopeID string, embedding []float64, opts SearchOptions) ([]VectorSearchResult, error)
    DeleteVector(scope MemoryScope, scopeID, key string) error
}

Built-in backends:

BackendConstructorUse For
InMemoryBackendagent.NewInMemoryBackend()Unit tests, local dev, ephemeral state
ControlPlaneMemoryBackendagent.NewControlPlaneMemoryBackend(url, token, nodeID)Production -- delegates to the control plane's distributed memory API
// Testing: in-memory (default)
a, _ := agent.New(agent.Config{
    NodeID:        "test-agent",
    Version:       "1.0.0",
    MemoryBackend: agent.NewInMemoryBackend(),
})

// Production: control plane backend
a, _ := agent.New(agent.Config{
    NodeID:        "prod-agent",
    Version:       "1.0.0",
    MemoryBackend: agent.NewControlPlaneMemoryBackend(
        "http://localhost:8080",
        "bearer-token",
        "prod-agent",
    ),
})

MemoryConfig (Python)

Configure memory behavior in the Python SDK via MemoryConfig.

from agentfield import Agent, MemoryConfig

app = Agent(
    node_id="my-agent",
    memory_config=MemoryConfig(
        auto_inject=["conversation.history"],  # keys to auto-inject into context
        memory_retention="session",             # retention policy
        cache_results=True,                     # cache memory results locally
    ),
)
FieldTypeDescription
auto_injectlist[str]Memory keys to automatically inject into execution context
memory_retentionstrRetention policy for memory data
cache_resultsboolWhether to cache memory results locally