- [Overview](#overview)
Remote Memory System
Table of Contents
- Overview
- Architecture
- When to Use Remote Memory
- Server Setup
- Client Configuration
- Operations
- Versioning and Conflict Resolution
- Security
- Performance Considerations
- Complete Example
Overview
TPipe’s remote memory system enables distributed agents to share context windows, todo lists, and locks across network boundaries. The system consists of two components:
- MemoryServer: REST API server exposing ContextBank operations
- MemoryClient: Client library for accessing remote memory
This architecture allows multiple TPipe instances to coordinate through a centralized memory service, enabling multi-agent systems, distributed workflows, and persistent shared state.
Architecture
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Agent A │ │ Agent B │ │ Agent C │
│ (TPipe) │ │ (TPipe) │ │ (TPipe) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ MemoryClient │ MemoryClient │
└───────────┬───────────┴───────────┬───────────┘
│ │
▼ ▼
┌──────────────────────────────────┐
│ MemoryServer (REST API) │
│ ┌────────────────────────────┐ │
│ │ ContextBank │ │
│ │ - Context Windows │ │
│ │ - TodoLists │ │
│ │ - ContextLocks │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
Key Features:
- RESTful HTTP API for all ContextBank operations
- Automatic retry logic with exponential backoff
- Lock state caching to reduce network overhead
- Version-based conflict detection
- Authentication via bearer tokens
- Introspection tools for remote lorebook queries
When to Use Remote Memory
Use Remote Memory When:
- Multiple agents need to share context across processes or machines
- Building distributed AI systems with coordinated state
- Implementing persistent memory that survives process restarts
- Coordinating parallel agents working on shared tasks
- Centralizing memory management for monitoring and control
Use Local Memory When:
- Single-agent systems with no coordination needs
- Performance-critical applications (local is faster)
- Offline or air-gapped environments
- Simple prototypes and development
Server Setup
Configuration
Configure the memory server in your TPipe application:
import com.TTT.Config.TPipeConfig
import com.TTT.Context.MemoryServer
import com.TTT.P2P.P2PRegistry
import io.ktor.server.application.*
import io.ktor.server.routing.*
fun Application.configureMemoryServer()
{
// Set authentication mechanism
P2PRegistry.globalAuthMechanism = { authHeader ->
val expectedToken = "Bearer ${System.getenv("MEMORY_AUTH_TOKEN")}"
authHeader == expectedToken
}
// Enable versioning for conflict detection
TPipeConfig.enforceMemoryVersioning = true
// Configure routing
routing {
MemoryServer.configureMemoryRouting(this)
}
}
Endpoints
The MemoryServer exposes the following REST endpoints:
Context Bank:
GET /context/bank/keys- List all context window keysGET /context/bank/{key}- Retrieve context windowPOST /context/bank/{key}- Store/update context windowDELETE /context/bank/{key}- Delete context windowGET /context/bank/{key}/query- Query lorebook entriesGET /context/bank/{key}/simulate- Simulate lorebook triggers
TodoList:
GET /context/todo/keys- List all todo list keysGET /context/todo/{key}- Retrieve todo listPOST /context/todo/{key}- Store/update todo list
ContextLock:
GET /context/lock/keys- List all lock keysGET /context/lock/{key}/state- Check if key is lockedGET /context/lock/page/{pageKey}/state- Check if page is lockedPOST /context/lock/- Add lockDELETE /context/lock/- Remove lock
Client Configuration
Basic Setup
Configure clients to connect to the remote memory server:
import com.TTT.Config.TPipeConfig
// Set remote memory URL
TPipeConfig.remoteMemoryUrl = "http://memory-server:8080"
// Set authentication token
TPipeConfig.remoteMemoryAuthToken = "Bearer your-secret-token"
Environment Variables
export TPIPE_REMOTE_MEMORY_URL="http://memory-server:8080"
export MEMORY_AUTH_TOKEN="your-secret-token"
Operations
Context Window Operations
import com.TTT.Context.MemoryClient
import com.TTT.Context.ContextWindow
suspend fun contextOperations()
{
// List all keys
val keys = MemoryClient.getPageKeys()
println("Available keys: $keys")
// Retrieve context window
val context = MemoryClient.getContextWindow("agent-memory")
// Modify and store
if (context != null)
{
context.addText("New information discovered")
MemoryClient.emplaceContextWindow("agent-memory", context)
}
// Delete context
MemoryClient.deleteContextWindow("old-key")
}
TodoList Operations
import com.TTT.Context.MemoryClient
import com.TTT.Context.TodoList
suspend fun todoOperations()
{
// Retrieve todo list
val todo = MemoryClient.getTodoList("project-tasks") ?: TodoList()
// Add task
todo.addTask("Implement feature X", "Details about feature X")
// Store updated list
MemoryClient.emplaceTodoList("project-tasks", todo)
// List all todo keys
val todoKeys = MemoryClient.getTodoListKeys()
}
Lock Operations
import com.TTT.Context.MemoryClient
import com.TTT.Context.LockRequest
suspend fun lockOperations()
{
// Check lock state
val isLocked = MemoryClient.isKeyLocked("critical-key")
if (!isLocked)
{
// Acquire lock
val lockRequest = LockRequest(
key = "critical-key",
lockState = true,
isPageKey = false
)
MemoryClient.addLock(lockRequest)
// Perform critical operation
// ...
// Release lock
lockRequest.lockState = false
MemoryClient.addLock(lockRequest)
}
}
Remote Lorebook Queries
import com.TTT.Context.MemoryClient
suspend fun lorebookOperations()
{
// Query lorebook entries
val results = MemoryClient.queryLorebook(
key = "agent-memory",
query = "database",
minWeight = 50,
requiredKeys = listOf("technical"),
aliasKeys = listOf("db", "sql")
)
results.forEach { result ->
println("Entry: ${result.entry.text}")
println("Weight: ${result.weight}")
}
// Simulate triggers
val triggered = MemoryClient.simulateLorebookTrigger(
key = "agent-memory",
text = "We need to query the database"
)
println("Triggered entries: $triggered")
}
Versioning and Conflict Resolution
The remote memory system uses version numbers to detect conflicts when multiple clients modify the same resource.
How Versioning Works
- Each
ContextWindowandTodoListhas aversionfield - Server increments version on every write
- Client writes with older versions are rejected
- Clients must fetch latest version, merge changes, and retry
Handling Conflicts
import com.TTT.Context.MemoryClient
import com.TTT.Context.ContextWindow
suspend fun handleConflict()
{
var success = false
var retries = 0
val maxRetries = 3
while (!success && retries < maxRetries)
{
// Fetch latest version
val context = MemoryClient.getContextWindow("shared-memory") ?: ContextWindow()
// Make modifications
context.addText("Agent contribution")
// Attempt to save
success = MemoryClient.emplaceContextWindow("shared-memory", context)
if (!success)
{
retries++
kotlinx.coroutines.delay(100L * retries)
}
}
}
Configuration
import com.TTT.Config.TPipeConfig
// Enable version enforcement (recommended for production)
TPipeConfig.enforceMemoryVersioning = true
// Disable for development (allows overwrites)
TPipeConfig.enforceMemoryVersioning = false
Security
Authentication
The memory server uses bearer token authentication:
// Server-side: Set auth mechanism
P2PRegistry.globalAuthMechanism = { authHeader ->
val expectedToken = "Bearer ${System.getenv("MEMORY_AUTH_TOKEN")}"
authHeader == expectedToken
}
// Client-side: Set auth token
TPipeConfig.remoteMemoryAuthToken = "Bearer your-secret-token"
Best Practices
- Use HTTPS in production - Never send tokens over unencrypted connections
- Rotate tokens regularly - Implement token rotation policies
- Network isolation - Run memory server in private network
- Rate limiting - Implement rate limits at reverse proxy level
- Audit logging - Log all memory access for security monitoring
- Least privilege - Use different tokens for different agent roles
ContextLock Integration
Remote memory respects ContextLock restrictions:
import com.TTT.Context.ContextLock
import com.TTT.Context.MemoryClient
suspend fun secureAccess()
{
// Lock a key remotely
val lockRequest = LockRequest(
key = "sensitive-data",
lockState = true,
isPageKey = false
)
MemoryClient.addLock(lockRequest)
// Attempts to access locked keys will fail
val context = MemoryClient.getContextWindow("sensitive-data")
// Returns null if locked
}
Performance Considerations
Caching
MemoryClient implements caching for lock states:
- Cache TTL: 1 second (configurable)
- Reduces network calls during lorebook selection
- Automatic invalidation on write operations
Retry Logic
Built-in retry mechanism for transient failures:
- Max retries: 3 attempts
- Backoff: 100ms * retry_count
- Handles: Network timeouts, temporary server unavailability
Optimization Tips
- Batch operations - Group multiple reads/writes when possible
- Use skipRemote flag - Server-side operations use
skipRemote=trueto avoid loops - Cache locally - Keep frequently accessed context in local memory
- Minimize writes - Only write when context actually changes
- Use appropriate storage modes - Configure
StorageModebased on persistence needs
Complete Example
Multi-Agent Coordination System
import com.TTT.Config.TPipeConfig
import com.TTT.Context.MemoryClient
import com.TTT.Context.ContextWindow
import com.TTT.Context.TodoList
import bedrockPipe.BedrockPipe
// Agent configuration
data class AgentConfig(
val agentId: String,
val memoryKey: String,
val todoKey: String
)
class DistributedAgent(private val config: AgentConfig)
{
private val pipe = BedrockPipe()
.setRegion("us-east-1")
.setModel("anthropic.claude-3-sonnet-20240229-v1:0")
.setSystemPrompt("You are agent ${config.agentId}")
suspend fun initialize()
{
// Configure remote memory
TPipeConfig.remoteMemoryUrl = System.getenv("MEMORY_SERVER_URL")
TPipeConfig.remoteMemoryAuthToken = "Bearer ${System.getenv("MEMORY_TOKEN")}"
TPipeConfig.enforceMemoryVersioning = true
}
suspend fun processTask()
{
// Fetch shared context
val sharedContext = MemoryClient.getContextWindow("shared-knowledge")
?: ContextWindow()
// Fetch agent-specific context
val agentContext = MemoryClient.getContextWindow(config.memoryKey)
?: ContextWindow()
// Fetch tasks
val tasks = MemoryClient.getTodoList(config.todoKey) ?: TodoList()
if (tasks.tasks.isNotEmpty())
{
val task = tasks.tasks.first { !it.completed }
// Process with AI
pipe.setUserPrompt("""
Shared Knowledge: ${sharedContext.text}
Your Memory: ${agentContext.text}
Task: ${task.description}
""".trimIndent())
val result = pipe.execute("")
// Update agent memory
agentContext.addText("Completed: ${task.description}\nResult: ${result.text}")
MemoryClient.emplaceContextWindow(config.memoryKey, agentContext)
// Update shared knowledge
sharedContext.addText("${config.agentId}: ${result.text}")
MemoryClient.emplaceContextWindow("shared-knowledge", sharedContext)
// Mark the task as completed in local state and persist to remote memory
task.completed = true
MemoryClient.emplaceTodoList(config.todoKey, tasks)
}
}
}
// Usage
suspend fun main()
{
val agent = DistributedAgent(
AgentConfig(
agentId = "agent-1",
memoryKey = "agent-1-memory",
todoKey = "team-tasks"
)
)
agent.initialize()
while (true)
{
agent.processTask()
kotlinx.coroutines.delay(5000)
}
}
See Also
- ContextBank API - Complete ContextBank API reference
- ContextLock API - Lock management and security
- Memory Introspection - Agent memory access control
- P2P Overview - Agent-to-agent communication
Next Steps
- Memory Introspection - Continue with memory inspection and editing tools.