LangChain4j Agentic Framework provides a comprehensive Java library for building multi-agent AI systems with support for workflow orchestration, supervisor agents, planning-based execution, declarative configuration, agent-to-agent communication, and human-in-the-loop workflows.
Persist and restore AgenticScope state across application restarts for long-running workflows and stateful agent systems.
Persistence enables:
interface AgenticScopeStore {
void save(AgenticScopeKey key, DefaultAgenticScope scope);
DefaultAgenticScope load(AgenticScopeKey key);
void delete(AgenticScopeKey key);
Set<AgenticScopeKey> getAllKeys();
}Implement this interface to provide custom persistence backends.
enum AgenticScopePersister {
INSTANCE;
public void setStore(AgenticScopeStore store);
}Configuration:
// Configure at application startup
AgenticScopeStore store = new RedisAgenticScopeStore(redisClient);
AgenticScopePersister.INSTANCE.setStore(store);
// Now all agents persist their scopes
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.build();
// Scope persisted automatically
agent.invoke("user-123", "Hello");
// Later, scope restored
agent.invoke("user-123", "What did I say before?");record AgenticScopeKey(
String agentId,
Object memoryId
) {}Composite key identifying a persisted scope:
AgenticScopeKey key = new AgenticScopeKey("my-agent-id", "user-123");
DefaultAgenticScope scope = store.load(key);
if (scope != null) {
System.out.println("State: " + scope.state());
}
store.delete(key);class AgenticScopeSerializer {
public static String toJson(DefaultAgenticScope scope);
public static DefaultAgenticScope fromJson(String json);
}Usage:
// Serialize
DefaultAgenticScope scope = // ... get scope
String json = AgenticScopeSerializer.toJson(scope);
// Save to file
Files.writeString(Path.of("scope.json"), json);
// Load from file
String loadedJson = Files.readString(Path.of("scope.json"));
DefaultAgenticScope restoredScope = AgenticScopeSerializer.fromJson(loadedJson);
// Access restored state
Map<String, Object> state = restoredScope.state();class FileSystemAgenticScopeStore implements AgenticScopeStore {
private final Path storageDir;
public FileSystemAgenticScopeStore(Path storageDir) {
this.storageDir = storageDir;
Files.createDirectories(storageDir);
}
@Override
public void save(AgenticScopeKey key, DefaultAgenticScope scope) {
String json = AgenticScopeSerializer.toJson(scope);
Path file = getFilePath(key);
Files.writeString(file, json);
}
@Override
public DefaultAgenticScope load(AgenticScopeKey key) {
Path file = getFilePath(key);
if (!Files.exists(file)) return null;
String json = Files.readString(file);
return AgenticScopeSerializer.fromJson(json);
}
@Override
public void delete(AgenticScopeKey key) {
Files.deleteIfExists(getFilePath(key));
}
@Override
public Set<AgenticScopeKey> getAllKeys() {
return Files.list(storageDir)
.filter(path -> path.toString().endsWith(".json"))
.map(this::pathToKey)
.collect(Collectors.toSet());
}
private Path getFilePath(AgenticScopeKey key) {
String fileName = key.agentId() + "_" + key.memoryId() + ".json";
return storageDir.resolve(fileName);
}
private AgenticScopeKey pathToKey(Path path) {
String fileName = path.getFileName().toString().replace(".json", "");
String[] parts = fileName.split("_", 2);
return new AgenticScopeKey(parts[0], parts[1]);
}
}
// Configure
AgenticScopePersister.INSTANCE.setStore(
new FileSystemAgenticScopeStore(Path.of("./scope-storage"))
);class RedisAgenticScopeStore implements AgenticScopeStore {
private final RedisClient redis;
public RedisAgenticScopeStore(RedisClient redis) {
this.redis = redis;
}
@Override
public void save(AgenticScopeKey key, DefaultAgenticScope scope) {
String json = AgenticScopeSerializer.toJson(scope);
redis.set(keyToString(key), json);
}
@Override
public DefaultAgenticScope load(AgenticScopeKey key) {
String json = redis.get(keyToString(key));
return json != null ? AgenticScopeSerializer.fromJson(json) : null;
}
@Override
public void delete(AgenticScopeKey key) {
redis.del(keyToString(key));
}
@Override
public Set<AgenticScopeKey> getAllKeys() {
return redis.keys("agentic:scope:*").stream()
.map(this::stringToKey)
.collect(Collectors.toSet());
}
private String keyToString(AgenticScopeKey key) {
return "agentic:scope:" + key.agentId() + ":" + key.memoryId();
}
private AgenticScopeKey stringToKey(String str) {
String[] parts = str.replace("agentic:scope:", "").split(":");
return new AgenticScopeKey(parts[0], parts[1]);
}
}class DatabaseAgenticScopeStore implements AgenticScopeStore {
private final DataSource dataSource;
public DatabaseAgenticScopeStore(DataSource dataSource) {
this.dataSource = dataSource;
initializeSchema();
}
private void initializeSchema() {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("""
CREATE TABLE IF NOT EXISTS agentic_scopes (
agent_id VARCHAR(255) NOT NULL,
memory_id VARCHAR(255) NOT NULL,
scope_data TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (agent_id, memory_id)
)
""");
}
}
@Override
public void save(AgenticScopeKey key, DefaultAgenticScope scope) {
String json = AgenticScopeSerializer.toJson(scope);
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("""
INSERT INTO agentic_scopes (agent_id, memory_id, scope_data)
VALUES (?, ?, ?)
ON CONFLICT (agent_id, memory_id)
DO UPDATE SET scope_data = EXCLUDED.scope_data,
updated_at = CURRENT_TIMESTAMP
""")) {
stmt.setString(1, key.agentId());
stmt.setString(2, key.memoryId().toString());
stmt.setString(3, json);
stmt.executeUpdate();
}
}
@Override
public DefaultAgenticScope load(AgenticScopeKey key) {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("""
SELECT scope_data FROM agentic_scopes
WHERE agent_id = ? AND memory_id = ?
""")) {
stmt.setString(1, key.agentId());
stmt.setString(2, key.memoryId().toString());
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String json = rs.getString("scope_data");
return AgenticScopeSerializer.fromJson(json);
}
return null;
}
}
@Override
public void delete(AgenticScopeKey key) {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("""
DELETE FROM agentic_scopes
WHERE agent_id = ? AND memory_id = ?
""")) {
stmt.setString(1, key.agentId());
stmt.setString(2, key.memoryId().toString());
stmt.executeUpdate();
}
}
@Override
public Set<AgenticScopeKey> getAllKeys() {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("""
SELECT agent_id, memory_id FROM agentic_scopes
""")) {
Set<AgenticScopeKey> keys = new HashSet<>();
while (rs.next()) {
keys.add(new AgenticScopeKey(
rs.getString("agent_id"),
rs.getString("memory_id")
));
}
return keys;
}
}
}interface AgenticScopeAccess {
AgenticScope getAgenticScope(Object memoryId);
void evictAgenticScope(Object memoryId);
}Usage:
// UntypedAgent implements AgenticScopeAccess
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.build();
// Access scope directly
AgenticScope scope = agent.getAgenticScope("user-123");
if (scope != null) {
Map<String, Object> state = scope.state();
System.out.println("Current state: " + state);
}
// Evict scope
agent.evictAgenticScope("user-123");class ScopeCleanupJob {
private final AgenticScopeStore store;
public void cleanupOldScopes(Duration maxAge) {
Set<AgenticScopeKey> allKeys = store.getAllKeys();
for (AgenticScopeKey key : allKeys) {
DefaultAgenticScope scope = store.load(key);
if (scope != null && isOlderThan(scope, maxAge)) {
store.delete(key);
}
}
}
private boolean isOlderThan(DefaultAgenticScope scope, Duration maxAge) {
// Check scope metadata for age
return false; // Implementation depends on metadata
}
}class ScopeBackup {
private final AgenticScopeStore store;
public void backupAllScopes(Path backupDir) throws IOException {
Files.createDirectories(backupDir);
Set<AgenticScopeKey> keys = store.getAllKeys();
for (AgenticScopeKey key : keys) {
DefaultAgenticScope scope = store.load(key);
if (scope != null) {
String json = AgenticScopeSerializer.toJson(scope);
String fileName = key.agentId() + "_" + key.memoryId() + ".json";
Files.writeString(backupDir.resolve(fileName), json);
}
}
}
public void restoreFromBackup(Path backupDir) throws IOException {
Files.list(backupDir)
.filter(path -> path.toString().endsWith(".json"))
.forEach(path -> {
try {
String json = Files.readString(path);
DefaultAgenticScope scope = AgenticScopeSerializer.fromJson(json);
String fileName = path.getFileName().toString().replace(".json", "");
String[] parts = fileName.split("_", 2);
AgenticScopeKey key = new AgenticScopeKey(parts[0], parts[1]);
store.save(key, scope);
} catch (IOException e) {
throw new RuntimeException("Failed to restore scope", e);
}
});
}
}interface AgenticScopeJsonCodec {
DefaultAgenticScope fromJson(String json);
String toJson(DefaultAgenticScope scope);
}Example with Gson:
class GsonAgenticScopeCodec implements AgenticScopeJsonCodec {
private final Gson gson = new Gson();
@Override
public DefaultAgenticScope fromJson(String json) {
return gson.fromJson(json, DefaultAgenticScope.class);
}
@Override
public String toJson(DefaultAgenticScope scope) {
return gson.toJson(scope);
}
}
// Register via ServiceLoader
// Create META-INF/services/dev.langchain4j.agentic.scope.persistence.AgenticScopeJsonCodec
// Add: com.mycompany.GsonAgenticScopeCodecpublic class PersistentAgentApp {
public static void main(String[] args) {
// 1. Configure persistence
AgenticScopeStore store = new DatabaseAgenticScopeStore(dataSource);
AgenticScopePersister.INSTANCE.setStore(store);
// 2. Create agent
UntypedAgent agent = AgenticServices.agentBuilder()
.chatModel(chatModel)
.name("persistent-assistant")
.build();
// 3. First conversation
String userId = "user-123";
String response1 = agent.invoke(userId, "My name is Alice").toString();
System.out.println(response1);
// 4. Simulate restart (scope persisted to storage)
// ... application restarts ...
// 5. Continue conversation
String response2 = agent.invoke(userId, "What is my name?").toString();
System.out.println(response2); // Should remember "Alice"
// 6. Access scope directly
AgenticScope scope = agent.getAgenticScope(userId);
System.out.println("Conversation history: " + scope.agentInvocations());
// 7. Cleanup when done
agent.evictAgenticScope(userId);
}
}Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-agentic@1.11.0docs
declarative
A2AClientAgent
ActivationCondition
Agent
ConditionalAgent
ErrorHandler
ExitCondition
HumanInTheLoop
HumanInTheLoopResponseSupplier
LoopAgent
LoopCounter
Output
ParallelAgent
ParallelExecutor
PlannerAgent
SequenceAgent
SupervisorAgent
SupervisorRequest
quick-start
workflows