Skip to content
AgentEnsemble AgentEnsemble
Get Started

Shared Memory

AgentEnsemble v3.0.0 introduces cross-ensemble shared memory with configurable consistency models. SharedMemory wraps a standard MemoryStore with consistency-aware read/write semantics, enabling multiple ensembles to safely share state across a network.


// Create shared memory with eventual consistency (default)
SharedMemory sharedMemory = SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.EVENTUAL)
.build();
// Store an entry
sharedMemory.store("inventory", MemoryEntry.builder()
.content("Wagyu beef: 12 portions remaining")
.storedAt(Instant.now())
.build());
// Retrieve entries
List<MemoryEntry> entries = sharedMemory.retrieve("inventory", "beef", 10);

Each consistency model defines coordination behavior for concurrent reads and writes to a shared memory scope across ensembles.

ModelBehaviorUse Case
EVENTUALLast-write-wins, no coordinationContext, preferences, notes
LOCKEDDistributed lock before each read/writeRoom assignments, exclusive access
OPTIMISTICCompare-and-swap with version check; retry on conflictCounters, inventory
EXTERNALFramework does not manage consistency; your tools handle itCustom coordination
SharedMemory sharedMemory = SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.EVENTUAL)
.build();

No coordination — all writes go directly to the backing store. Suitable when conflicts are unlikely or acceptable.

SharedMemory sharedMemory = SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.LOCKED)
.lockProvider(LockProvider.inMemory())
.build();

Every store() and retrieve() acquires an exclusive lock on the scope first. A LockProvider is required when using LOCKED consistency.

SharedMemory sharedMemory = SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.OPTIMISTIC)
.build();

Writes increment a per-scope version counter. Use retrieveVersioned() and the version-aware store() overload for conflict detection.

SharedMemory sharedMemory = SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.EXTERNAL)
.build();

The framework performs no coordination. Your application or tools are responsible for managing consistency.


The LockProvider interface provides distributed locking for LOCKED consistency mode.

public interface LockProvider {
AutoCloseable lock(String scope);
boolean tryLock(String scope, Duration timeout);
void unlock(String scope);
}
LockProvider lockProvider = LockProvider.inMemory();

Backed by ReentrantLock. Suitable for single-JVM development and testing.

For multi-process deployments, implement the LockProvider interface with your distributed lock infrastructure (e.g., Redis, ZooKeeper).


Optimistic mode uses version numbers to detect conflicting writes without locking.

SharedMemory sharedMemory = SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.OPTIMISTIC)
.build();
// 1. Read with version
VersionedResult result = sharedMemory.retrieveVersioned("inventory", "beef", 10);
long version = result.version();
List<MemoryEntry> entries = result.entries();
// 2. Modify and write with expected version
try {
sharedMemory.store("inventory", MemoryEntry.builder()
.content("Wagyu beef: 11 portions remaining")
.storedAt(Instant.now())
.build(), version);
} catch (ConcurrentMemoryStoreException e) {
// Another writer updated the scope -- retry from step 1
}
boolean stored = false;
while (!stored) {
VersionedResult result = sharedMemory.retrieveVersioned("inventory", "beef", 10);
MemoryEntry updated = MemoryEntry.builder()
.content("Updated inventory count")
.storedAt(Instant.now())
.build();
try {
sharedMemory.store("inventory", updated, result.version());
stored = true;
} catch (ConcurrentMemoryStoreException e) {
// Conflict -- loop will re-read and retry
}
}

The SharedMemoryRegistry holds named SharedMemory instances at the network level. Other components (e.g., ProfileApplier) look up shared memory by name.

SharedMemoryRegistry registry = new SharedMemoryRegistry();
registry.register("inventory", SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.OPTIMISTIC)
.build());
registry.register("guest-preferences", SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.EVENTUAL)
.build());
// Lookup
SharedMemory inventory = registry.get("inventory");
boolean exists = registry.contains("inventory");
Set<String> names = registry.names();

Register shared memory instances via NetworkConfig.builder() so they are available across the entire ensemble network:

NetworkConfig config = NetworkConfig.builder()
.ensemble("kitchen", "ws://kitchen:7329/ws")
.ensemble("front-desk", "ws://front-desk:7329/ws")
.sharedMemory("inventory", SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.OPTIMISTIC)
.build())
.sharedMemory("guest-preferences", SharedMemory.builder()
.store(MemoryStore.inMemory())
.consistency(Consistency.EVENTUAL)
.build())
.build();

Shared memory supports the same eviction policies as the underlying MemoryStore:

sharedMemory.evict("inventory", EvictionPolicy.keepLastEntries(10));

In LOCKED mode, eviction acquires the scope lock before proceeding. In other modes, eviction delegates directly to the backing store.