Class BaseAgent

java.lang.Object
dev.agenor.runtime.agent.BaseAgent
All Implemented Interfaces:
Agent, LifecycleHooks
Direct Known Subclasses:
LLMAgent, MessageSnifferAgent

public abstract class BaseAgent extends Object implements Agent, LifecycleHooks
Base implementation for all agents in the Agenor framework.

Provides core functionality including:

  • Lifecycle management (start/stop)
  • Message handling
  • Behavior scheduling
  • Agent directory integration
  • Memory management (since 0.6.0)

All concrete agents should extend this class and implement the required lifecycle methods.

Memory Support (since 0.6.0): Agents can optionally use memory features by calling memory methods. Memory features are only available if setMemoryStore(MemoryStore) is called during initialization.

Example usage:


 @Agent("my-agent")
 public class MyAgent extends BaseAgent {

     @Override
     protected void onStart() {
         // Use memory if available
         rememberShort("session-id", "abc123", Duration.ofMinutes(30));
         rememberLong("user-preference", "dark-mode");
     }

     @AgenorMessageHandler("process-request")
     public void handleRequest(Message message) {
         recall("session-id", MemoryScope.SHORT_TERM)
             .thenAccept(sessionId -> {
                 log.info("Processing for session: {}", sessionId);
             });
     }
 }
 
Since:
0.1.0
  • Field Details

  • Constructor Details

    • BaseAgent

      protected BaseAgent()
      Create an agent with auto-generated ID
    • BaseAgent

      protected BaseAgent(String agentId)
      Create agent with specific ID
    • BaseAgent

      protected BaseAgent(String agentId, String agentName)
      Create an agent with ID and name
      Parameters:
      agentId - the agent identifier
      agentName - the agent display name
    • BaseAgent

      protected BaseAgent(String agentId, AgentContext ctx)
      Create an agent with a specific ID, receiving all core services via AgentContext.

      Convenience for subclasses that want to accept an AgentContext in their own constructor and propagate it upward:

      
       public class MyAgent extends BaseAgent {
           public MyAgent(AgentContext ctx) {
               super("my-agent", ctx);
           }
       }
       
      Parameters:
      agentId - the agent identifier
      ctx - the aggregated core services, not null
      Since:
      0.10.0
    • BaseAgent

      protected BaseAgent(String agentId, String agentName, AgentContext ctx)
      Create an agent with ID, name, and all core services via AgentContext.
      Parameters:
      agentId - the agent identifier
      agentName - the agent display name
      ctx - the aggregated core services, not null
      Since:
      0.10.0
  • Method Details

    • getAgentId

      public String getAgentId()
      Description copied from interface: Agent
      Returns the unique identifier for this agent.

      The agent ID must be unique within the runtime environment and is used for agent discovery, message routing, and state management. Once assigned, the ID should never change during the agent's lifetime.

      Recommended format: lowercase alphanumeric with hyphens (e.g., "order-processor-1").

      Specified by:
      getAgentId in interface Agent
      Returns:
      the agent's unique identifier, never null or empty
    • getAgentName

      public String getAgentName()
      Description copied from interface: Agent
      Returns the human-readable name for this agent.

      The agent name is primarily for display and logging purposes. Unlike the agent ID, the name doesn't need to be unique and can be changed during the agent's lifetime (though this is discouraged).

      Names should be descriptive and help identify the agent's purpose (e.g., "Customer Support Agent", "Inventory Monitor").

      Specified by:
      getAgentName in interface Agent
      Returns:
      the agent's display name, never null or empty
    • isRunning

      public boolean isRunning()
      Description copied from interface: Agent
      Checks whether the agent is currently running.

      An agent is considered running if it has completed startup and is actively executing behaviors. This method returns:

      • true - Agent is in RUNNING state
      • false - Agent is in CREATED, STARTING, STOPPING, or STOPPED state

      State Transitions: During startup and shutdown, this method may temporarily return values that don't reflect the final state:

      For more detailed state information, use the agent's status in its AgentDescriptor.

      Specified by:
      isRunning in interface Agent
      Returns:
      true if the agent is currently running and processing behaviors, false otherwise
      See Also:
    • start

      public CompletableFuture<Void> start()
      Description copied from interface: Agent
      Starts the agent and all its registered behaviors asynchronously.

      This method initiates the agent's lifecycle, performing the following:

      1. Transitions the agent to the STARTING state
      2. Initializes all registered behaviors
      3. Starts behavior execution (cyclic, scheduled, etc.)
      4. Registers the agent with the directory (if enabled)
      5. Transitions to RUNNING state when complete

      Idempotency: Calling start() on an already running agent should be a no-op and return immediately.

      Concurrency: This method is asynchronous and non-blocking. Use the returned CompletableFuture to wait for startup completion or chain subsequent operations.

      Error Handling: If startup fails, the returned future completes exceptionally. The agent transitions to the ERROR state and set s running to false.

      Example:

      
       agent.start()
           .thenRun(() -> log.info("Agent started successfully"))
           .exceptionally(ex -> {
               log.error("Failed to start agent", ex);
               return null;
           });
       
      Specified by:
      start in interface Agent
      Returns:
      a CompletableFuture that completes when the agent is fully started, or completes exceptionally if startup fails
      See Also:
    • stop

      public CompletableFuture<Void> stop()
      Description copied from interface: Agent
      Stops the agent and all its running behaviors gracefully.

      This method initiates graceful shutdown, performing the following:

      1. Transitions the agent to the STOPPING state
      2. Stops all running behaviors (allowing current executions to complete)
      3. Unregisters the agent from the directory (if registered)
      4. Releases allocated resources (threads, connections, etc.)
      5. Transitions to STOPPED state when complete

      Graceful Shutdown: The agent attempts to stop cleanly, allowing behaviors to complete their current execution cycle. A timeout may be enforced to prevent indefinite waiting.

      Idempotency: Calling stop() on an already stopped agent should be a no-op and return immediately.

      Concurrency: This method is asynchronous and non-blocking. Multiple concurrent calls to stop() should be safe and result in a single shutdown sequence.

      Resource Cleanup: Implementations must ensure proper cleanup of:

      • Thread pools and executors
      • Open connections and file handles
      • Scheduled tasks
      • Message subscriptions

      Example:

      
       agent.stop()
           .thenRun(() -> log.info("Agent stopped gracefully"))
           .exceptionally(ex -> {
               log.warn("Error during shutdown (resources may leak)", ex);
               return null;
           });
       
      Specified by:
      stop in interface Agent
      Returns:
      a CompletableFuture that completes when the agent is fully stopped, or completes exceptionally if shutdown encounters errors
      See Also:
    • addBehavior

      public void addBehavior(Behavior behavior)
      Description copied from interface: Agent
      Adds a behavior to this agent's behavior set.

      Behaviors represent units of autonomous activity. When a behavior is added to a running agent, it should start executing immediately according to its type (one-shot, cyclic, triggered, etc.).

      Dynamic Addition: Behaviors can be added at any time:

      • Before Agent.start() - Behavior will start when agent starts
      • While running - Behavior starts immediately
      • After Agent.stop() - Behavior is added but won't execute until restart

      Behavior Identity: Each behavior must have a unique ID within the agent's scope. Adding a behavior with a duplicate ID may either replace the existing behavior or throw an exception, depending on the implementation.

      Thread Safety: This method must be thread-safe and can be called concurrently with other lifecycle operations.

      Example:

      
       // Runs every 5 seconds
       agent.addBehavior(CyclicBehavior.from("monitor", Duration.ofSeconds(5),
               () -> checkSystemHealth()));
      
       // Runs once; subclass when the factories do not cover what you need
       agent.addBehavior(new OneShotBehavior("warm-cache") {
           @Override
           protected void action() {
               preloadCatalogue();
           }
       });
       

      Reacting to a message is not a behavior: annotate a method with @AgenorMessageHandler("orders.created"), or subscribe through the agent's MessageDispatcher.

      Specified by:
      addBehavior in interface Agent
      Parameters:
      behavior - the behavior to add, must not be null
      See Also:
    • removeBehavior

      public void removeBehavior(String behaviorId)
      Description copied from interface: Agent
      Removes a behavior from this agent by its ID.

      If the agent is running, the behavior will be stopped gracefully:

      • For one-shot behaviors: current execution completes
      • For cyclic behaviors: current cycle completes, no new cycles start
      • For triggered behaviors: message subscriptions are cancelled

      Not Found: If no behavior exists with the given ID, this method is a no-op.

      Thread Safety: This method must be thread-safe and can be called concurrently with behavior execution and other lifecycle operations.

      Example:

      
       // Remove a behavior dynamically
       agent.removeBehavior("monitor");
       
      Specified by:
      removeBehavior in interface Agent
      Parameters:
      behaviorId - the unique identifier of the behavior to remove, must not be null
      See Also:
    • getBehaviors

      public List<Behavior> getBehaviors()
      Returns a snapshot of all behaviors currently registered on this agent.

      The returned list is a point-in-time copy; modifications to it do not affect the agent's behavior collection. Used by HitlAnnotationProcessor during agent registration.

      Returns:
      immutable snapshot of registered behaviors
      Since:
      0.13.0
    • setMessageDispatcher

      public void setMessageDispatcher(MessageDispatcher dispatcher)
    • getMessageDispatcher

      public MessageDispatcher getMessageDispatcher()
      Returns the message dispatcher for this agent.
      Specified by:
      getMessageDispatcher in interface Agent
      Returns:
      the message dispatcher for this agent, never null
      Since:
      0.20.0
      See Also:
    • setBehaviorScheduler

      public void setBehaviorScheduler(BehaviorScheduler behaviorScheduler)
      Set the behavior scheduler for this agent
    • setTelemetry

      public void setTelemetry(AgenorTelemetry telemetry)
      Sets the telemetry this agent's inbound path reports to.

      Set by the runtime at registration, alongside the dispatcher and the directory. An agent that is never registered keeps the no-op instance, so nothing here depends on having a telemetry backend.

      Parameters:
      telemetry - the telemetry instance; null restores the no-op
      Since:
      0.31.0
    • getTelemetry

      public AgenorTelemetry getTelemetry()
      Returns:
      the telemetry wired into this agent; never null
      Since:
      0.31.0
    • getStatus

      public AgentStatus getStatus()
      Get current agent status
    • setAgentDirectory

      public void setAgentDirectory(AgentDirectory agentDirectory)
      Set the agent directory for this agent
    • setAgentDescriptor

      public void setAgentDescriptor(AgentDescriptor descriptor)
      Method for AgentFactory to set descriptor
    • setMemoryStore

      public void setMemoryStore(MemoryStore memoryStore)
      Injects the memory store (optional). Called by the runtime during agent initialization.

      If not set, memory operations will throw IllegalStateException.

      Parameters:
      memoryStore - the memory store
      Since:
      0.6.0
    • onStartHook

      public void onStartHook(Runnable hook)
      Register a hook to be executed when the agent starts. Hooks are executed AFTER onStart() is called. This is useful for external components (like PersistenceManager) to perform actions during agent startup.
      Specified by:
      onStartHook in interface LifecycleHooks
      Parameters:
      hook - the runnable to execute on start
    • onStopHook

      public void onStopHook(Runnable hook)
      Register a hook to be executed when the agent stops. Hooks are executed BEFORE onStop() is called. This is critical for persistence - it ensures state is saved before the agent shuts down completely.
      Specified by:
      onStopHook in interface LifecycleHooks
      Parameters:
      hook - the runnable to execute on stop
    • removeStartHook

      public boolean removeStartHook(Runnable hook)
      Remove a previously registered start hook
      Parameters:
      hook - the hook to remove
      Returns:
      true if the hook was found and removed
    • removeStopHook

      public boolean removeStopHook(Runnable hook)
      Remove a previously registered stop hook
      Parameters:
      hook - the hook to remove
      Returns:
      true if the hook was found and removed
    • clearStartHooks

      public void clearStartHooks()
      Clear all registered start hooks
    • clearStopHooks

      public void clearStopHooks()
      Clear all registered stop hooks
    • initializeServices

      protected void initializeServices()
    • onStart

      protected void onStart()
      Lifecycle hook called when agent starts
    • onStop

      protected void onStop()
      Lifecycle hook called when agent stops
    • createMailbox

      protected AgentMailbox createMailbox(MessageHandler pushConsumer)
      Creates this agent's mailbox.

      The one place the runtime's own implementation is named. Override to hand the agent a different AgentMailbox — an instrumented one, or a different queueing strategy — without touching how the inbound path is wired. Called once, when the agent starts.

      Parameters:
      pushConsumer - receives every message the mailbox does not route to a dialogue consumer; must be passed to the mailbox unchanged
      Returns:
      a non-null mailbox, not yet started
      Since:
      0.27.0
    • mailboxConfig

      protected MailboxConfig mailboxConfig()
      Returns the bounds and overflow behaviour for this agent's mailbox.

      Override to give one agent a different capacity or overflow policy. Read once, when the agent starts.

      Returns:
      a non-null configuration; MailboxConfig.defaults() unless overridden
    • mailbox

      public Optional<AgentMailbox> mailbox()
      Returns this agent's mailbox, the owner of its inbound path.

      Present only while the agent is started and a message dispatcher is set. Intended for components that consume the agent's inbound traffic, such as the dialogue layer.

      Returns:
      the mailbox, or an empty optional if the agent is not started
      Since:
      0.27.0
    • registerDirectTopicHandler

      public void registerDirectTopicHandler(String topic, MessageHandler handler)
      Registers a handler to be invoked for direct (point-to-point) messages whose Message.topic() matches the given topic.

      Used internally by the runtime's annotation processor so that @AgenorMessageHandler-annotated methods also fire for messages sent via sendTo()/receiverId addressing, not just topic pub/sub.

      Parameters:
      topic - the exact topic to match against Message.topic()
      handler - the handler to invoke on a match
      Since:
      0.25.0
    • registerTopicSubscription

      public void registerTopicSubscription(Subscription subscription)
      Hands this agent the topic subscription created for one of its @AgenorMessageHandler methods, so that stopping the agent releases it.

      Used internally by the runtime's annotation processor. Without it the Subscription has no owner and nothing can ever cancel it: the subscription outlives the agent for the life of the process.

      Parameters:
      subscription - the subscription to release when this agent stops
      Since:
      0.29.0
    • handleDirectMessage

      protected void handleDirectMessage(Message message)
      Handle direct messages received by this agent. Override to customize handling, or use @AgenorMessageHandler annotations.
    • onDirectMessage

      protected void onDirectMessage(Message message)
      Called when agent receives a direct message. Override to handle direct messages in a centralized way. Note: This is only called if no @AgenorMessageHandler matches.
      Parameters:
      message - the received message
    • sendTo

      protected CompletableFuture<Void> sendTo(String receiverAgentId, Object content)
      Send a direct message to another agent (fire-and-forget).
      Parameters:
      receiverAgentId - the target agent ID
      content - the message content
      Returns:
      CompletableFuture that completes when sent
    • requestFrom

      protected CompletableFuture<Message> requestFrom(String receiverAgentId, Object content)
      Send a request to another agent and wait for response. Uses the request/response pattern with correlation ID.
      Parameters:
      receiverAgentId - the target agent ID
      content - the request content
      Returns:
      CompletableFuture with the response message
    • requestFrom

      protected CompletableFuture<Message> requestFrom(String receiverAgentId, Object content, long timeoutMillis)
      Send a request with custom timeout.
      Parameters:
      receiverAgentId - the target agent ID
      content - the request content
      timeoutMillis - timeout in milliseconds
      Returns:
      CompletableFuture with the response message
    • replyTo

      protected CompletableFuture<Void> replyTo(Message originalMessage, Object content)
      Reply to a received message. Automatically sets correlation ID and receiver.
      Parameters:
      originalMessage - the message to reply to
      content - the reply content
      Returns:
      CompletableFuture that completes when reply is sent
    • rememberShort

      protected CompletableFuture<Void> rememberShort(String key, String content)
      Stores a short-term memory (volatile, cleared on restart).

      Short-term memories are suitable for:

      • Temporary state during task execution
      • Caching of computed values
      • Session-specific information
      Parameters:
      key - the memory key (will be namespaced automatically)
      content - the memory content
      Returns:
      a future that completes when stored
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • rememberShort

      protected CompletableFuture<Void> rememberShort(String key, String content, Duration ttl)
      Stores a short-term memory with expiration.
      Parameters:
      key - the memory key (will be namespaced automatically)
      content - the memory content
      ttl - time-to-live (null = never expires)
      Returns:
      a future that completes when stored
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • rememberLong

      protected CompletableFuture<Void> rememberLong(String key, String content)
      Stores a long-term memory (persistent, survives restart).

      Long-term memories are suitable for:

      • Learned facts and knowledge
      • User preferences
      • Historical patterns
      Parameters:
      key - the memory key (will be namespaced automatically)
      content - the memory content
      Returns:
      a future that completes when stored
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • rememberLong

      protected CompletableFuture<Void> rememberLong(String key, String content, Map<String,Object> metadata)
      Stores a long-term memory with metadata.
      Parameters:
      key - the memory key
      content - the memory content
      metadata - additional metadata
      Returns:
      a future that completes when stored
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • shareMemory

      protected CompletableFuture<Void> shareMemory(String key, String content, String... agentIds)
      Shares a memory with other agents (for coordination).

      Shared memories allow multiple agents to access the same information, useful for orchestrated workflows and multi-agent collaboration.

      Parameters:
      key - the shared memory key (not namespaced)
      content - the memory content
      agentIds - the agent IDs to share with
      Returns:
      a future that completes when stored
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • recall

      protected CompletableFuture<Optional<String>> recall(String key, MemoryScope scope)
      Recalls a memory from the specified scope.
      Parameters:
      key - the memory key (will be namespaced automatically)
      scope - the memory scope to search in
      Returns:
      a future containing the memory content, or empty if not found
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • recallShared

      protected CompletableFuture<Optional<String>> recallShared(String key)
      Recalls a shared memory.
      Parameters:
      key - the shared memory key (not namespaced)
      Returns:
      a future containing the memory content, or empty if not found
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • searchMemory

      protected CompletableFuture<List<String>> searchMemory(String query, MemoryScope scope)
      Searches through agent's memories.
      Parameters:
      query - the search text
      scope - the memory scope to search in
      Returns:
      a future containing matching memory contents
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • searchMemory

      protected CompletableFuture<List<MemoryEntry>> searchMemory(MemoryQuery query)
      Searches with custom filters.
      Parameters:
      query - the memory query
      Returns:
      a future containing matching entries
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • forget

      protected CompletableFuture<Void> forget(String key, MemoryScope scope)
      Forgets a memory.
      Parameters:
      key - the memory key (will be namespaced automatically)
      scope - the memory scope
      Returns:
      a future that completes when deleted
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • clearMemory

      protected CompletableFuture<Void> clearMemory(MemoryScope scope)
      Clears all memories in the specified scope.

      Warning: This operation cannot be undone.

      Parameters:
      scope - the memory scope to clear
      Returns:
      a future that completes when cleared
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0
    • getMemoryStats

      public MemoryStats getMemoryStats()
      Gets memory statistics for this agent.
      Returns:
      memory usage statistics
      Throws:
      IllegalStateException - if memory store not configured
      Since:
      0.6.0