Agent State Persistence Guide¶
This guide covers Agenor's agent state persistence system: how to save and restore an agent's internal business fields across JVM restarts.
This is not the memory system. Key-value memory (
MemoryStore,rememberLong, etc.) is a separate subsystem documented in memory.md. The two systems are independent and do not share storage.
The persistence subsystem spans two modules:
- agenor-core (dev.agenor.core.persistence) — interfaces and records
- agenor-runtime-ext (dev.agenor.runtime.persistence) — implementations, split out of agenor-runtime per ADR-027
Package Overview¶
agenor-core / dev.agenor.core.persistence
├── Stateful.java # Mixin interface: agents with persistent state
├── AgentState.java # Serializable state snapshot (builder)
├── PersistenceService.java # Save/load interface
└── PersistenceStrategy.java # When to auto-save (enum)
agenor-runtime-ext / dev.agenor.runtime.persistence
├── FilePersistenceService.java # JSON file-based PersistenceService
└── PersistenceManager.java # Auto-save orchestrator, wired by AgenorRuntime
Core concept¶
An agent opts into persistence by implementing Stateful. The agent then decides which fields
to save (captureState) and how to restore them (restoreState). The runtime calls these methods
automatically based on @PersistenceConfig.
Agent fields ──captureState()──▶ AgentState ──FilePersistenceService──▶ <agentId>.json
Agent fields ◀──restoreState()── AgentState ◀──FilePersistenceService── <agentId>.json
Stateful interface¶
@Agent("order-processor")
@PersistenceConfig(
strategy = PersistenceStrategy.ON_STOP,
autoSnapshot = true,
snapshotInterval = "1h",
maxSnapshots = 24
)
public class OrderProcessorAgent extends BaseAgent implements Stateful {
private int ordersProcessed = 0;
private String currentOrderId;
@Override
public AgentState captureState() {
return AgentState.builder(getAgentId())
.agentName(getAgentName())
.agentType("processor")
.status(isRunning() ? AgentStatus.RUNNING : AgentStatus.STOPPED)
.data("ordersProcessed", ordersProcessed)
.data("currentOrderId", currentOrderId)
.version(getStateVersion() + 1)
.build();
}
@Override
public void restoreState(AgentState state) {
Integer saved = state.getData("ordersProcessed", Integer.class);
ordersProcessed = saved != null ? saved : 0;
currentOrderId = state.getData("currentOrderId", String.class);
}
}
Guidelines for captureState():
- Include all mutable fields that must survive a restart.
- Produce a point-in-time snapshot; avoid holding locks across the call.
- Increment or preserve version() for optimistic locking.
Guidelines for restoreState():
- Handle missing keys gracefully with defaults.
- Do not start behaviors or connect to external systems here; use onStart() for that.
- This method may be called before start().
@Persist¶
@Persist is a field- and method-level annotation that lets the runtime discover which
members to include in the persisted state automatically, without requiring a manual
captureState()/restoreState() implementation.
Field usage¶
@Agent("order-processor")
public class OrderProcessorAgent extends BaseAgent implements Stateful {
@Persist
private int ordersProcessed = 0;
@Persist("customer_id") // explicit key — stable across field renames
private String customerId;
@Persist(required = true) // restoration fails if key is absent in the saved document
private String sessionToken;
@Persist(encrypted = true) // value is encrypted at rest
private String apiKey;
}
Method usage¶
When placed on a getter, the framework calls the getter on save and the matching setter on restore.
Attribute reference¶
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
String |
"" (member name) |
Key used in the persisted document. Set explicitly to keep the schema stable across renames. |
required |
boolean |
false |
When true, restoration throws an exception if the key is missing. Use for fields that are essential to resume correct operation. |
encrypted |
boolean |
false |
When true, the value is encrypted before writing and decrypted transparently on restore. Suitable for secrets (API keys, tokens). |
@Persist vs manual captureState / restoreState¶
@Persist |
Manual captureState / restoreState |
|
|---|---|---|
| Boilerplate | Minimal — annotate fields | Explicit builder calls for each field |
| Control | Framework-driven | Full control over snapshot shape |
| Schema stability | Use value to pin keys |
Manage keys manually |
| Conditional logic | Not supported | Any logic in restoreState |
| Encrypted fields | encrypted = true |
Manual |
Use @Persist for straightforward persistence of individual fields. Implement
captureState / restoreState manually when you need custom logic, derived values, or
transformations during save/restore.
@PersistenceConfig¶
Sets the automatic-save policy at class level. When absent, the strategy defaults to MANUAL.
| Attribute | Default | Description |
|---|---|---|
strategy |
MANUAL |
When to save automatically |
interval |
"60s" |
Used by PERIODIC and DEBOUNCED |
autoSnapshot |
false |
Enable periodic snapshots |
snapshotInterval |
"1h" |
How often to create a snapshot |
maxSnapshots |
10 |
Max snapshots to retain (oldest purged) |
PersistenceStrategy values¶
| Value | Description |
|---|---|
MANUAL |
Agent must call persistState() explicitly |
IMMEDIATE |
Save on every state change |
PERIODIC |
Save at fixed intervals (interval attribute) |
ON_STOP |
Save when agent stops |
DEBOUNCED |
Save after changes with a debounce window |
SNAPSHOT |
Create periodic snapshots |
AgentState fields¶
| Field | Type | Notes |
|---|---|---|
agentId |
String |
Required |
agentName |
String? |
Display name |
agentType |
String |
Defaults to "unknown" |
status |
AgentStatus |
Defaults to UNKNOWN |
data |
Map<String,Object> |
Business state; any JSON-serializable value |
metadata |
Map<String,String> |
System/config state; String values only |
version |
long |
Optimistic locking counter |
savedAt |
Instant |
Auto-set when saving |
// Type-safe data access
int count = state.getData("ordersProcessed", Integer.class);
String id = state.getData("currentOrderId", String.class);
FilePersistenceService¶
The default PersistenceService implementation. Writes agent state as JSON (Jackson) to
<dataDirectory>/<agentId>.json using an atomic REPLACE_EXISTING move. Snapshots go to
<dataDirectory>/snapshots/<agentId>/<snapshotId>.json.
// Default directory: data/persistence
FilePersistenceService service = new FilePersistenceService();
// Custom directory
FilePersistenceService service = new FilePersistenceService(Path.of("var/agent-states"));
Available operations¶
service.saveState(agentId, state).join();
Optional<AgentState> loaded = service.loadState(agentId).join();
boolean exists = service.existsState(agentId).join();
service.deleteState(agentId).join(); // also removes all snapshots
String snapshotId = service.createSnapshot(agentId, null).join(); // auto-generated ID
String snapshotId = service.createSnapshot(agentId, "before-migration").join();
Optional<AgentState> snap = service.restoreSnapshot(agentId, snapshotId).join();
List<String> snapshots = service.listSnapshots(agentId).join();
PersistenceManager¶
PersistenceManager wires FilePersistenceService to the runtime. It automatically registers
agents that implement Stateful, schedules periodic saves or snapshots, and flushes all states
on shutdown.
Integration with AgenorRuntime (recommended)¶
var persistence = new FilePersistenceService(Path.of("data/agents"));
var manager = new PersistenceManager(persistence);
AgenorRuntime runtime = AgenorRuntime.builder()
.scanPackage("com.example.agents")
.service(PersistenceManager.class, manager)
.build();
runtime.start();
// PersistenceManager.registerAgent() called automatically for every Stateful agent
Manual usage¶
PersistenceManager manager = new PersistenceManager(new FilePersistenceService());
manager.start().join();
manager.registerAgent(myAgent);
manager.saveAgent(myAgent).join(); // manual save
manager.stop().join(); // cancels scheduled tasks, saves all agents
Decision Guide¶
| Scenario | Recommended approach |
|---|---|
| Agent business state (counters, queues, current IDs) | Stateful + FilePersistenceService |
| Save on restart/stop only | @PersistenceConfig(strategy=ON_STOP) |
| Scheduled auto-save | @PersistenceConfig(strategy=PERIODIC) |
| Point-in-time rollback | FilePersistenceService.createSnapshot + restoreSnapshot |
| Searchable/sharable knowledge base | Use MemoryStore instead — see memory.md |
See Also¶
- Memory Guide —
MemoryStore,rememberLong,InMemoryStore - Agent Development Guide —
@Persist,@PersistenceConfigannotations - Architecture Guide — module overview