Record Class AgentState

java.lang.Object
java.lang.Record
dev.agenor.core.persistence.AgentState
Record Components:
agentId - unique identifier for the agent, must not be null
agentName - human-readable display name, may be null
agentType - logical type/category of the agent (e.g., "processor", "monitor"), defaults to "unknown" if null
status - current operational status, defaults to UNKNOWN if null
data - application-specific state data with Object values, never null (empty map if not provided)
metadata - system/technical metadata with String values, never null (empty map if not provided)
version - optimistic locking version number, incremented on each save
savedAt - timestamp when this state was persisted, defaults to current time if null

public record AgentState(String agentId, String agentName, String agentType, AgentStatus status, Map<String,Object> data, Map<String,String> metadata, long version, Instant savedAt) extends Record
Immutable record representing the complete persisted state of an agent.

This record contains all information necessary to restore an agent to its previous state after a restart, failure, or migration. It serves as the primary data structure for agent persistence operations.

State Components:

Agent state components and properties
Component Purpose Example
Identity (agentId, agentName, agentType) Identify the agent id: "order-proc-1", name: "Order Processor"
Status Current operational state RUNNING, IDLE, ERROR
Data Business state (flexible Map) orders processed, current order, counters
Metadata Technical state (String-only) configuration, timestamps, flags
Version Optimistic concurrency control Incremented on each save
Timestamp (savedAt) When state was persisted Used for audit and debugging

Data vs Metadata:

  • data: Business state with any Object values (String, Integer, List, custom POJOs, etc.). This is the primary application state that agents need to preserve.
  • metadata: Technical/system state with String values only. Used for configuration, feature flags, timestamps, etc.

Versioning: The version field enables optimistic locking to prevent lost updates when multiple processes try to save state concurrently:


 // Load current state
 AgentState current = persistence.load("agent-1").join();

 // Modify state
 AgentState modified = AgentState.builder(current.agentId())
     .data(current.data())
     .data("counter", current.getData("counter", Integer.class) + 1)
     .version(current.version() + 1)  // Increment version
     .build();

 // Save - will fail if another process saved in the meantime
 try {
     persistence.save(modified).join();
 } catch (ConcurrentModificationException e) {
     // Retry with fresh state
 }
 

Serialization: This record is designed to be easily serializable to various formats:

  • JSON - Via Jackson annotations (default)
  • Binary - Via Java serialization (Serializable)
  • Database - Map to columns/documents

Immutability: All collections (data, metadata) are defensively copied to ensure immutability. Use the builder pattern to create modified copies.

Example Usage:


 // Create initial state
 AgentState state = AgentState.builder("order-processor-1")
     .agentName("Order Processor #1")
     .agentType("processor")
     .status(AgentStatus.RUNNING)
     .data("ordersProcessed", 0)
     .data("currentBatch", List.of())
     .metadata("startTime", Instant.now().toString())
     .metadata("environment", "production")
     .build();

 // Save state
 persistenceService.save(state).join();

 // Later, load and update state
 AgentState loaded = persistenceService.load("order-processor-1").join();
 int processed = loaded.getData("ordersProcessed", Integer.class);

 AgentState updated = AgentState.builder(loaded.agentId())
     .agentName(loaded.agentName())
     .agentType(loaded.agentType())
     .status(loaded.status())
     .data(loaded.data())  // Copy existing data
     .data("ordersProcessed", processed + 10)  // Update counter
     .metadata(loaded.metadata())  // Copy existing metadata
     .version(loaded.version() + 1)  // Increment version
     .build();

 persistenceService.save(updated).join();
 

Recovery Pattern:


 // On agent startup, restore previous state
 public class OrderProcessorAgent extends BaseAgent implements Stateful {

     @Override
     public CompletableFuture<Void> start() {
         return persistenceService.load(getAgentId())
             .thenCompose(state -> {
                 if (state != null) {
                     restoreState(state);
                     log.info("Restored state from {}", state.savedAt());
                 }
                 return super.start();
             });
     }

     private void restoreState(AgentState state) {
         this.ordersProcessed = state.getData("ordersProcessed", Integer.class);
         this.currentBatch = state.getData("currentBatch", List.class);
         // Restore other fields...
     }
 }
 
Since:
0.1.0
See Also:
  • Constructor Details

    • AgentState

      public AgentState(String agentId, String agentName, String agentType, AgentStatus status, Map<String,Object> data, Map<String,String> metadata, long version, Instant savedAt)
      Canonical constructor with defensive copying and default values.

      This constructor ensures:

      • Collections are immutable (defensive copy)
      • Null values get sensible defaults
      • Invariants are maintained
      Throws:
      NullPointerException - if agentId is null
  • Method Details

    • builder

      public static AgentState.AgentStateBuilder builder(String agentId)
      Creates a new builder for constructing AgentState instances.

      The builder pattern is the recommended way to create state objects, especially when dealing with multiple fields or creating modified copies of existing state.

      Parameters:
      agentId - the required agent identifier
      Returns:
      a new builder instance
      Throws:
      NullPointerException - if agentId is null
    • getData

      public <T> T getData(String key, Class<T> type)
      Retrieves a typed data value by key.

      This is a convenience method that performs type casting for you. Use this instead of manually casting values from the data map.

      Type Safety: This method performs an unchecked cast. Ensure you're requesting the correct type, or a ClassCastException will be thrown at runtime.

      Example:

      
       Integer count = state.getData("ordersProcessed", Integer.class);
       List<String> batch = state.getData("currentBatch", List.class);
       CustomOrder order = state.getData("pendingOrder", CustomOrder.class);
       
      Type Parameters:
      T - the expected type of the value
      Parameters:
      key - the data key
      type - the class of the expected type (used for clarity, not actual type checking)
      Returns:
      the value cast to type T, or null if the key doesn't exist
      Throws:
      ClassCastException - if the value cannot be cast to type T
    • getMetadata

      public String getMetadata(String key)
      Retrieves a metadata value by key.

      This is a convenience method equivalent to calling state.metadata().get(key), but more readable.

      Parameters:
      key - the metadata key
      Returns:
      the metadata value, or null if the key doesn't exist
    • toString

      public final String toString()
      Returns a string representation of this record class. The representation contains the name of the class, followed by the name and value of each of the record components.
      Specified by:
      toString in class Record
      Returns:
      a string representation of this object
    • hashCode

      public final int hashCode()
      Returns a hash code value for this object. The value is derived from the hash code of each of the record components.
      Specified by:
      hashCode in class Record
      Returns:
      a hash code value for this object
    • equals

      public final boolean equals(Object o)
      Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. Reference components are compared with Objects::equals(Object,Object); primitive components are compared with '=='.
      Specified by:
      equals in class Record
      Parameters:
      o - the object with which to compare
      Returns:
      true if this object is the same as the o argument; false otherwise.
    • agentId

      public String agentId()
      Returns the value of the agentId record component.
      Returns:
      the value of the agentId record component
    • agentName

      public String agentName()
      Returns the value of the agentName record component.
      Returns:
      the value of the agentName record component
    • agentType

      public String agentType()
      Returns the value of the agentType record component.
      Returns:
      the value of the agentType record component
    • status

      public AgentStatus status()
      Returns the value of the status record component.
      Returns:
      the value of the status record component
    • data

      public Map<String,Object> data()
      Returns the value of the data record component.
      Returns:
      the value of the data record component
    • metadata

      public Map<String,String> metadata()
      Returns the value of the metadata record component.
      Returns:
      the value of the metadata record component
    • version

      public long version()
      Returns the value of the version record component.
      Returns:
      the value of the version record component
    • savedAt

      public Instant savedAt()
      Returns the value of the savedAt record component.
      Returns:
      the value of the savedAt record component