Class JdbcAgentPresence

java.lang.Object
dev.agenor.adapters.persistence.directory.JdbcAgentPresence
All Implemented Interfaces:
AgentPresence

public class JdbcAgentPresence extends Object implements AgentPresence
JDBC implementation of AgentPresence (ADR-028 Phase A).

Reuses the agenor_agents table the rest of the JDBC directory already writes: heartbeat(java.lang.String) touches only last_seen, which is lighter than JdbcAgentRegistry's status update, and getStatus(java.lang.String) reads status alongside last_seen. No new table, column or migration.

Staleness is evaluated at read time. A relational store has no key expiry, so getStatus(java.lang.String) compares now - last_seen against the configured window and answers AgentStatus.UNKNOWN when it is exceeded. Detection latency is therefore coarse — tens of seconds to a couple of minutes at the recommended cadence — and the answer is only as trustworthy as the clock skew between the writing and reading nodes allows. The default window is deliberately wide enough to absorb ordinary NTP drift.

Something has to send the heartbeats. Nothing in the framework calls heartbeat(java.lang.String) unless a heartbeat interval is configured, and with a bounded window a store nobody heartbeats reports UNKNOWN for every agent one window after start-up. Either enable the runtime's heartbeat driver, or construct this class with UNBOUNDED_STALENESS_WINDOW, which never expires a status and matches the in-memory backend's behaviour.

Since:
0.26.0
See Also:
  • Field Details

    • DEFAULT_STALENESS_WINDOW

      public static final Duration DEFAULT_STALENESS_WINDOW
      Default staleness window: three missed beats at the recommended 30-second cadence.
    • UNBOUNDED_STALENESS_WINDOW

      public static final Duration UNBOUNDED_STALENESS_WINDOW
      Staleness window meaning "never expire a status", the unbounded value AgentPresence.getStatus(java.lang.String) permits. Use it when no heartbeat driver is running, so getStatus(java.lang.String) simply reports what the registry last wrote.
  • Constructor Details

  • Method Details

    • stalenessWindow

      public Duration stalenessWindow()
      Returns the window after which getStatus(java.lang.String) reports AgentStatus.UNKNOWN.
      Returns:
      the staleness window; never null
    • heartbeat

      public CompletableFuture<Void> heartbeat(String agentId)
      Records a heartbeat for the given agent, refreshing its lastSeen timestamp.

      A heartbeat signals liveness and nothing else: it does not change the agent's AgentStatus. Use AgentRegistry.updateStatus(java.lang.String, dev.agenor.core.AgentStatus) for that. An implementation backed by key expiry has no status to promote, so a heartbeat that also wrote a status could not mean the same thing across backends.

      A heartbeat for an agent the implementation does not know is ignored rather than rejected — it is a race with unregistration, not an error.

      Issues a single-row UPDATE against the primary key. A heartbeat for an agent that is not registered updates no rows and is ignored, per the contract.

      Specified by:
      heartbeat in interface AgentPresence
      Parameters:
      agentId - the unique agent identifier, must not be null
      Returns:
      a future that completes when the heartbeat is recorded
    • getStatus

      public CompletableFuture<AgentStatus> getStatus(String agentId)
      Returns the current status of the given agent.

      The answer is AgentStatus.UNKNOWN when the agent is not registered, and also when it has not been seen within the implementation's staleness window. That window is a property of the implementation, not of this contract: an unbounded window is a legal value, and means the backend never expires a status.

      Answers AgentStatus.UNKNOWN when no row exists, and also when the row's last_seen is older than this instance's staleness window.

      Specified by:
      getStatus in interface AgentPresence
      Parameters:
      agentId - the unique agent identifier, must not be null
      Returns:
      a future containing the agent's status, or AgentStatus.UNKNOWN if it is unregistered or stale