Shared memory
Distributed key-value and vector memory with four isolation scopes for cross-agent state sharing.
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,existsacross all scopes. - Vector storage -- store embeddings and run similarity search for RAG patterns.
- Hierarchical lookup -- Python and TypeScript memory clients search
workflow -> session -> actor -> globalwhen you callget()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_scopefor explicit scope targeting. - Go default scope --
memory.Get()reads session scope (or the current run when no session is present). UseGlobalScope(),SessionScope(),UserScope(), andWorkflowScope()for explicit access.
Memory scopes
| Scope | Lifetime | Use For | Python/TS Name | Go Name |
|---|---|---|---|---|
| Global | Until explicitly deleted | Shared config, knowledge bases, cross-agent state | global | global |
| Session | Until session ends | Conversation context, per-session preferences | session | session |
| Actor | Persists across sessions | User-specific learned data, per-actor configuration | actor | user |
| Workflow | Until workflow run completes | Intermediate results, per-run computation state | workflow | workflow |
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
| Operation | Python | TypeScript | Go |
|---|---|---|---|
| Set | await memory.set(key, data) | await memory.set(key, data) | memory.Set(ctx, key, value) |
| Get | await memory.get(key, default=None) | await memory.get(key) | memory.Get(ctx, key) |
| Get with default | await memory.get(key, default={}) | -- | memory.GetWithDefault(ctx, key, default) |
| Delete | await memory.delete(key) | await memory.delete(key) | memory.Delete(ctx, key) |
| Exists | await memory.exists(key) | await memory.exists(key) | -- |
| List keys | await memory.global_scope.list_keys() | await memory.listKeys() | memory.List(ctx) |
Vector Methods
| Operation | Python | TypeScript | Go |
|---|---|---|---|
| Set vector | await memory.set_vector(key, embedding, metadata=) | await memory.setVector(key, embedding, metadata?) | memory.SetVector(ctx, key, embedding, metadata) |
| Search | await memory.similarity_search(query, top_k=, filters=) | await memory.searchVector(query, { topK, scope }) | memory.SearchVector(ctx, embedding, opts) |
| Delete vector | await 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
| Scope | Python | TypeScript | Go |
|---|---|---|---|
| Global | memory.global_scope | memory.globalScope | memory.GlobalScope() |
| Session | memory.session(id) | memory.session(id) | memory.SessionScope() |
| Actor / User | memory.actor(id) | memory.actor(id) | memory.UserScope() |
| Workflow | memory.workflow(id) | memory.workflow(id) | memory.WorkflowScope() |
| Explicit | -- | -- | memory.Scoped(scope, id) |
Event Methods
| Feature | Python | TypeScript | Go |
|---|---|---|---|
| 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) errorGo -- 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:
| Backend | Constructor | Use For |
|---|---|---|
InMemoryBackend | agent.NewInMemoryBackend() | Unit tests, local dev, ephemeral state |
ControlPlaneMemoryBackend | agent.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
),
)| Field | Type | Description |
|---|---|---|
auto_inject | list[str] | Memory keys to automatically inject into execution context |
memory_retention | str | Retention policy for memory data |
cache_results | bool | Whether to cache memory results locally |