Files
letta-server/fern/pages/agents/shared-memory-blocks-guide.mdx
Cameron Pfiffer 31cb8de606 feat: add shared memory block tutorial, update memory block guide (#5503)
feat: update documentation and add new tutorials for memory blocks and agent collaboration

- Updated navigation paths in docs.yml to reflect new tutorial locations.
- Added comprehensive guides on shared memory blocks and attaching/detaching memory blocks.
- Enhanced existing documentation for memory blocks with examples and best practices.
- Corrected API key references in prebuilt tools documentation.

These changes aim to improve user understanding and facilitate multi-agent collaboration through shared memory systems.
2025-10-24 15:12:24 -07:00

838 lines
23 KiB
Plaintext

---
title: Shared Memory Blocks Guide
subtitle: Complete guide to using shared memory for multi-agent coordination
slug: guides/agents/shared-memory-blocks
---
Shared memory blocks enable multiple agents to access and update the same memory, creating powerful multi-agent systems with seamless coordination.
## Overview
**Shared memory blocks** allow you to attach the same memory block to multiple agents. When one agent updates the block, all other agents with access immediately see the changes. This enables:
- **Real-time coordination** without explicit agent-to-agent messaging
- **Consistent information** across teams and departments
- **Hierarchical access control** based on roles and responsibilities
- **Privacy boundaries** for sensitive information
- **Knowledge sharing** across specialized agents
<Note>
Shared memory blocks are different from agent-to-agent messaging (like `send_message_to_agent_async`). With shared memory, agents coordinate **asynchronously** through shared state rather than direct communication.
</Note>
## Core Concepts
### What is a Shared Memory Block?
A memory block becomes "shared" when you attach it to multiple agents using the same `block_id`. All agents with access see the same content in real-time.
```python
from letta import Letta
client = Letta()
# Step 1: Create a memory block
shared_block = client.blocks.create(
label="team_knowledge",
description="Shared knowledge base for the team",
value="Team policies and procedures...",
limit=5000
)
# Step 2: Attach to multiple agents
agent1 = client.agents.create(
name="Agent_1",
block_ids=[shared_block.id], # Attach shared block
# ... other config
)
agent2 = client.agents.create(
name="Agent_2",
block_ids=[shared_block.id], # Same block ID = shared memory
# ... other config
)
# Now agent1 and agent2 share the same memory block!
```
### Block Types
| Type | Description | Use Case |
|---|---|---|
| **Read-Only** | Agents can read but not modify | Company policies, reference data |
| **Read/Write** | Agents can read and update | Task queues, shared notes |
| **Private** | Single agent only | Personal work logs, private notes |
### Access Patterns
Letta supports multiple access patterns for organizing shared memory:
1. **Hierarchical**: Tier 1 < Tier 2 < Tier 3 (access increases up the hierarchy)
2. **Team-Based**: All team members share the same blocks
3. **Overlapping**: Each agent has a unique combination of blocks
4. **Organizational**: Department → Cross-Department → Executive levels
## Architecture Patterns
### Pattern 1: Hierarchical Access (Support Tiers)
```
Tier 1 Agents → company_policies [R]
Tier 2 Agents → company_policies [R], escalation_procedures [R]
Tier 3 Agents → company_policies [R], escalation_procedures [R], team_metrics [R/W]
```
**Use Cases:**
- Customer support with tier levels
- Knowledge bases with sensitivity levels
- Organizations with clearance levels
**Example:** [Read-Only Organizational Knowledge Tutorial](/cookbooks/shared-memory-read-only)
### Pattern 2: Team Coordination (Shared Queues)
```
All Team Members → task_queue [R/W], completed_work [R/W]
Supervisor Only → team_metrics [R/W]
Each Worker → private_work_log [R/W]
```
**Use Cases:**
- Task coordination across workers
- Project management teams
- Shared deliverables tracking
**Example:** [Task Coordination Tutorial](/cookbooks/shared-memory-task-coordination)
### Pattern 3: Specialized Agents (Overlapping Access)
```
Coordinator → ALL blocks (user_profile, preferences, interaction_history, calendar, financial)
Email Agent → user_profile, preferences, interaction_history, calendar
Research Agent → user_profile, preferences, interaction_history
Calendar Agent → user_profile, preferences, calendar
Finance Agent → user_profile, preferences, financial
```
**Use Cases:**
- Personal AI assistant networks
- Specialized service agents
- Multi-domain customer support
**Example:** [Multi-Agent User Assistant Tutorial](/cookbooks/shared-memory-user-assistant)
### Pattern 4: Enterprise Hierarchy (Departments)
```
Company-Wide [R] → ALL agents (mission, policies)
Department Blocks [R/W] → Department members only
Cross-Dept Block [R/W] → All directors + CEO
Executive Dashboard [R/W] → CEO only
```
**Use Cases:**
- Enterprise organizations
- Multi-department companies
- Regulated industries with compliance requirements
**Example:** [Enterprise Multi-Team Tutorial](/cookbooks/shared-memory-enterprise)
## Best Practices
### 1. Use Read-Only Blocks for Critical Information
Protect policies, procedures, and reference data from accidental modification:
```python
company_policies = client.blocks.create(
label="company_policies",
value="Our company policies...",
# Read-only ensures consistency
)
```
<Warning>
Even though Letta doesn't currently enforce read-only at the API level, agents will respect read-only semantics and refuse to modify these blocks when instructed not to in their persona.
</Warning>
### 2. Implement the Principle of Least Privilege
Only give agents access to blocks they need:
```python
# ❌ Bad: Sales agent with access to HR data
sales_agent = client.agents.create(
block_ids=[sales_knowledge.id, hr_employee_data.id] # Too much access
)
# ✓ Good: Sales agent with only sales data
sales_agent = client.agents.create(
block_ids=[sales_knowledge.id] # Appropriate access
)
```
### 3. Use Clear Naming Conventions
Make block purposes obvious:
```python
# ✓ Good names
"company_policies" # Clear scope and content
"sales_team_knowledge" # Clear ownership
"cross_dept_projects" # Clear purpose
"ceo_executive_dashboard" # Clear access level
# ❌ Bad names
"data" # Too generic
"block1" # Not descriptive
"temp" # Purpose unclear
```
### 4. Set Appropriate Character Limits
Balance between enough space and memory constraints:
```python
# Reference data: smaller
company_policies = client.blocks.create(
limit=5000 # Policies don't change often
)
# Active coordination: larger
task_queue = client.blocks.create(
limit=10000 # Tasks accumulate over time
)
# Detailed logs: largest
interaction_history = client.blocks.create(
limit=12000 # Many interactions to track
)
```
<Tip>
If a block frequently hits its character limit, consider archiving old content or splitting into multiple blocks (e.g., `current_tasks` vs `completed_tasks`).
</Tip>
### 5. Document Block Access in Agent Personas
Make agents aware of their access:
```python
agent = client.agents.create(
memory_blocks=[{
"label": "persona",
"value": """I am a Sales Representative.
My access:
- company_policies (read-only): Company-wide policies
- sales_knowledge (read/write): Shared with sales team
- my_leads (private): My personal lead tracking
I do NOT have access to:
- engineering_specs (Engineering team only)
- hr_employee_data (HR team only)
"""
}]
)
```
### 6. Use Descriptive Block Descriptions
Help with debugging and management:
```python
block = client.blocks.create(
label="sales_knowledge",
description="Sales team knowledge base: pricing, playbooks, targets. Read/write access for Sales Director, Rep 1, Rep 2.",
# Good description includes: content, access, and purpose
)
```
## Common Use Cases
### Use Case 1: Customer Support with Tiers
**Problem:** Support agents at different levels need different information.
**Solution:** Hierarchical blocks with increasing access
```
support_tier1/ → Basic policies
support_tier2/ → Advanced troubleshooting + escalation procedures
support_tier3/ → Full system access + team metrics
```
**Benefits:**
- Consistent policy information across all tiers
- Sensitive escalation procedures protected
- Supervisors track team performance privately
### Use Case 2: Project Management Team
**Problem:** Multiple workers need to coordinate on tasks.
**Solution:** Shared task queue + completion log
```
task_queue (R/W) → All team members claim and update tasks
completed_work (R/W) → All team members share findings
team_metrics (R/W) → Supervisor only tracks performance
```
**Benefits:**
- Real-time task claiming without conflicts
- Knowledge sharing through completed work
- Supervisor oversight without micromanagement
### Use Case 3: Personal AI Assistant Network
**Problem:** User needs specialized agents that understand full context.
**Solution:** Overlapping block access with privacy boundaries
```
Universal: user_profile, user_preferences → All agents
Coordination: interaction_history → Coordinator, Email, Research
Domain-specific: calendar_data → Calendar, Email agents only
Restricted: financial_data → Finance agent only
```
**Benefits:**
- Seamless handoffs between specialists
- Consistent user experience
- Privacy protection for sensitive data
### Use Case 4: Enterprise Organization
**Problem:** Large company with departments needs coordination.
**Solution:** Multi-tier hierarchy with isolation
```
Company-wide (R) → All employees see mission/policies
Department (R/W) → Each dept has private knowledge
Cross-dept (R/W) → Directors coordinate projects
Executive (R/W) → CEO tracks company metrics
```
**Benefits:**
- Department autonomy and isolation
- Async cross-department coordination
- Executive oversight without bottlenecks
- Compliance with data privacy regulations
## API Reference
### Creating Shared Blocks
```python
block = client.blocks.create(
label="block_name", # Required: identifier
description="What this is", # Recommended: for management
value="Initial content", # Required: starting content
limit=5000 # Optional: character limit
)
```
### Attaching to Agents (at Creation)
```python
agent = client.agents.create(
name="Agent_Name",
block_ids=[block1.id, block2.id], # Attach existing blocks
memory_blocks=[ # Create new private blocks
{"label": "persona", "value": "..."}
],
# ... other config
)
```
### Attaching to Existing Agents
```python
# Attach a block to an existing agent
client.agents.blocks.attach(
agent_id=agent.id,
block_id=block.id
)
# Detach a block from an agent
client.agents.blocks.detach(
agent_id=agent.id,
block_id=block.id
)
```
### Listing Blocks
Find blocks across your project with optional filtering:
<CodeGroup>
```python Python
# List all blocks in project
all_blocks = client.blocks.list()
# Filter by label
team_blocks = client.blocks.list(label="team_knowledge")
# Search by label text
search_results = client.blocks.list(label_search="sales")
```
```typescript TypeScript
// List all blocks in project
const allBlocks = await client.blocks.list();
// Filter and search
const teamBlocks = await client.blocks.list({
label: "team_knowledge",
labelSearch: "sales"
});
```
</CodeGroup>
### Retrieving a Block
Get complete block information by ID:
<CodeGroup>
```python Python
block = client.blocks.retrieve(block.id)
print(f"Block: {block.label}")
print(f"Value: {block.value}")
```
```typescript TypeScript
const block = await client.blocks.retrieve(block.id);
console.log(`Block: ${block.label}`);
console.log(`Value: ${block.value}`);
```
</CodeGroup>
### Modifying Blocks Directly
Update block content without going through an agent. Useful for external scripts syncing data to agents:
<CodeGroup>
```python Python
# Update block content - completely replaces the value
client.blocks.modify(
block.id,
value="Updated team knowledge: New procedures..."
)
# Update multiple properties
client.blocks.modify(
block.id,
value="New content",
limit=8000,
description="Updated description"
)
# Make block read-only
client.blocks.modify(block.id, read_only=True)
```
```typescript TypeScript
// Update block content - completely replaces the value
await client.blocks.modify(block.id, {
value: "Updated team knowledge: New procedures..."
});
// Update multiple properties
await client.blocks.modify(block.id, {
value: "New content",
limit: 8000,
description: "Updated description"
});
// Make block read-only
await client.blocks.modify(block.id, { readOnly: true });
```
</CodeGroup>
<Warning>
**Setting `value` completely replaces the entire block content** - it is not an append operation. When you modify a shared block directly, all agents with access will see the changes immediately.
**Race condition risk**: If two processes (agents or external scripts) modify the same block concurrently, the last write wins and completely overwrites all earlier changes. To avoid data loss:
- Set blocks to **read-only** if you don't want agents or other processes to modify them
- Only allow direct modifications in controlled scenarios where overwriting is acceptable
- Ensure your application logic accounts for the fact that block updates are full replacements, not merges
</Warning>
### Deleting Blocks
Remove blocks when no longer needed. This detaches the block from all agents:
<CodeGroup>
```python Python
client.blocks.delete(block_id=block.id)
```
```typescript TypeScript
await client.blocks.delete(block.id);
```
</CodeGroup>
### Agent-Scoped Operations
#### List an Agent's Blocks
See all memory blocks attached to a specific agent:
<CodeGroup>
```python Python
# List all blocks for an agent
agent_blocks = client.agents.blocks.list(agent_id=agent.id)
# With pagination
agent_blocks = client.agents.blocks.list(
agent_id=agent.id,
limit=10,
order="asc"
)
for block in agent_blocks:
print(f"{block.label}: {block.value[:50]}...")
```
```typescript TypeScript
// List all blocks for an agent
const agentBlocks = await client.agents.blocks.list(agent.id);
// With pagination
const agentBlocksPaginated = await client.agents.blocks.list(agent.id, {
limit: 10,
order: "asc"
});
for (const block of agentBlocks) {
console.log(`${block.label}: ${block.value.slice(0, 50)}...`);
}
```
</CodeGroup>
#### Retrieve Agent's Block by Label
Get a specific block from an agent using its label instead of ID:
<CodeGroup>
```python Python
# Get agent's human block
human_block = client.agents.blocks.retrieve(
agent_id=agent.id,
block_label="human"
)
print(human_block.value)
# Get shared task queue from specific agent
task_queue = client.agents.blocks.retrieve(
agent_id=worker_agent.id,
block_label="task_queue"
)
```
```typescript TypeScript
// Get agent's human block
const humanBlock = await client.agents.blocks.retrieve(
agent.id,
"human"
);
console.log(humanBlock.value);
// Get shared task queue from specific agent
const taskQueue = await client.agents.blocks.retrieve(
workerAgent.id,
"task_queue"
);
```
</CodeGroup>
#### Modify Agent's Block by Label
Update a specific agent's block without needing the block ID:
<CodeGroup>
```python Python
# Update agent's knowledge about the human
client.agents.blocks.modify(
agent_id=agent.id,
block_label="human",
value="Updated user information: Alice, prefers email over chat"
)
# Update shared block via specific agent
client.agents.blocks.modify(
agent_id=worker.id,
block_label="task_queue",
value="Updated task queue with new items..."
)
```
```typescript TypeScript
// Update agent's knowledge about the human
await client.agents.blocks.modify(agent.id, "human", {
value: "Updated user information: Alice, prefers email over chat"
});
// Update shared block via specific agent
await client.agents.blocks.modify(worker.id, "task_queue", {
value: "Updated task queue with new items..."
});
```
</CodeGroup>
### Inspecting Block Usage
See which agents have a block attached:
<CodeGroup>
```python Python
# List all agents that use this block
agents_with_block = client.blocks.agents.list(block_id=block.id)
print(f"Used by {len(agents_with_block)} agents:")
for agent in agents_with_block:
print(f" - {agent.name}")
# With pagination
agents_page = client.blocks.agents.list(
block_id=block.id,
limit=10,
order="asc"
)
```
```typescript TypeScript
// List all agents that use this block
const agentsWithBlock = await client.blocks.agents.list(block.id);
console.log(`Used by ${agentsWithBlock.length} agents:`);
for (const agent of agentsWithBlock) {
console.log(` - ${agent.name}`);
}
// With pagination
const agentsPage = await client.blocks.agents.list(block.id, {
limit: 10,
order: "asc"
});
```
</CodeGroup>
### Updating Blocks via Agents
Agents update blocks using memory tools during conversations:
<CodeGroup>
```python Python
# Agent updates shared block content
client.agents.messages.create(
agent_id=agent.id,
messages=[{
"role": "user",
"content": "Update the task queue to mark task-001 as complete"
}]
)
# Agent uses core_memory_replace or core_memory_append tools
# Changes are immediately visible to all agents with access
```
```typescript TypeScript
// Agent updates shared block content
await client.agents.messages.create(agent.id, {
messages: [{
role: "user",
content: "Update the task queue to mark task-001 as complete"
}]
});
// Agent uses core_memory_replace or core_memory_append tools
// Changes are immediately visible to all agents with access
```
</CodeGroup>
## Troubleshooting
### Problem: Agent Can't See Block Updates
**Symptoms:** Agent reads old content after another agent updated it.
**Solutions:**
1. Verify both agents have the same `block_id` attached
2. Check that updates are being committed (agent finished its turn)
3. Ensure character limit hasn't been exceeded (updates may fail silently)
```python
# Debug: Check which agents share the block
block_info = client.blocks.retrieve(block_id=block.id)
print(f"Agents with access: {block_info.agent_ids}")
```
### Problem: Block Character Limit Exceeded
**Symptoms:** Updates not applying, content truncated.
**Solutions:**
1. Increase block limit: `client.blocks.update(block_id=block.id, limit=10000)`
2. Archive old content: Move completed items to a separate block
3. Summarize content: Have an agent periodically summarize and condense
```python
# Check current usage
block = client.blocks.retrieve(block_id=block.id)
print(f"Characters: {len(block.value)} / {block.limit}")
```
### Problem: Privacy Violation
**Symptoms:** Agent accessing data it shouldn't see.
**Solutions:**
1. Review block attachments: `client.agents.retrieve(agent_id).block_ids`
2. Detach inappropriate blocks: `client.agents.blocks.detach()`
3. Update agent persona to clarify access boundaries
4. Consider splitting one block into multiple with different access
### Problem: Race Conditions on Concurrent Updates
**Symptoms:** Two agents try to claim the same task, conflicts occur.
**Solutions:**
1. Design blocks to minimize conflicts (separate sections for each agent)
2. Use timestamps and agent IDs in updates
3. Implement retry logic for failed updates
4. Consider optimistic concurrency control
```python
# Good: Each agent updates their own section
"""
TASK-001:
Status: In Progress
Claimed by: Worker_1
Timestamp: 2024-10-08 14:30
TASK-002:
Status: In Progress
Claimed by: Worker_2
Timestamp: 2024-10-08 14:31
"""
```
## Performance Considerations
### Block Size and Agent Performance
- **Smaller blocks (<5K chars)**: Faster loading, more focused context
- **Larger blocks (>10K chars)**: More context but slower processing
- **Optimal**: Keep blocks focused on single purpose, split large blocks
### Number of Blocks per Agent
- **Recommended**: 3-7 blocks per agent
- **Each block adds**: Context in agent's working memory
- **Too many blocks**: May dilute agent focus
- **Too few blocks**: May limit coordination capabilities
### Update Frequency
- **High-frequency updates** (many agents, frequent changes): Consider separate blocks to reduce contention
- **Low-frequency updates** (policies, references): Larger consolidated blocks are fine
## Security and Compliance
### Data Privacy
Shared memory blocks enable compliance with data privacy regulations:
```python
# GDPR/HIPAA Example: Isolate sensitive data
hr_employee_data = client.blocks.create(
label="hr_employee_data",
description="CONFIDENTIAL - HR Department only. Contains PII."
)
# Only attach to authorized agents
hr_director = client.agents.create(
block_ids=[hr_employee_data.id] # Only HR has access
)
# Sales/Engineering agents do NOT get access
sales_agent = client.agents.create(
block_ids=[sales_knowledge.id] # No HR data access
)
```
### Audit Trail
Track block access for compliance:
```python
# Check block usage
block_info = client.blocks.retrieve(block_id=sensitive_block.id)
print(f"Accessed by: {block_info.agent_ids}")
# Log all agents with access
for agent_id in block_info.agent_ids:
agent = client.agents.retrieve(agent_id=agent_id)
print(f" {agent.name} - {agent.created_at}")
```
### Access Revocation
Remove access when no longer needed:
```python
# Employee leaves company - revoke agent access
client.agents.blocks.detach(
agent_id=former_employee_agent.id,
block_id=company_confidential.id
)
# Or delete the agent entirely
client.agents.delete(agent_id=former_employee_agent.id)
```
## Tutorials
Learn shared memory patterns through hands-on tutorials:
<CardGroup cols={2}>
<Card title="Part 1: Read-Only Knowledge" icon="lock" href="/cookbooks/shared-memory-read-only">
Build a hierarchical support team with shared company policies
</Card>
<Card title="Part 2: Task Coordination" icon="list-check" href="/cookbooks/shared-memory-task-coordination">
Create a data analysis team with shared task queues
</Card>
<Card title="Part 3: User Assistant Network" icon="users" href="/cookbooks/shared-memory-user-assistant">
Build specialized agents with overlapping block access
</Card>
<Card title="Part 4: Enterprise System" icon="building" href="/cookbooks/shared-memory-enterprise">
Implement a complete enterprise with departments and hierarchies
</Card>
</CardGroup>
## Related Guides
<CardGroup cols={2}>
<Card title="Memory Blocks Overview" icon="database" href="/guides/agents/memory-blocks">
Understanding memory blocks and core memory
</Card>
<Card title="Multi-Agent Systems" icon="diagram-project" href="/guides/agents/multi-agent">
Overview of multi-agent architectures in Letta
</Card>
<Card title="Agent-to-Agent Messaging" icon="comments" href="/cookbooks/multi-agent-async">
Alternative coordination pattern using async messaging
</Card>
<Card title="API Reference" icon="code" href="/api-reference/agents/core-memory">
Complete API documentation for blocks and memory
</Card>
</CardGroup>
## Key Takeaways
✓ **Shared memory blocks** enable seamless multi-agent coordination without explicit messaging
✓ **Access patterns** range from simple (all agents) to complex (hierarchical organizations)
✓ **Privacy boundaries** protect sensitive data while enabling collaboration
✓ **Real-time sync** ensures all agents see updates immediately
✓ **Scales** from 2 agents to enterprise systems with 10+ agents
✓ **Complements** agent-to-agent messaging for complete multi-agent systems
Shared memory blocks are a powerful primitive for building sophisticated multi-agent systems. Start with simple patterns (Tutorial 1) and progress to complex architectures (Tutorial 4) as your needs grow.