Class SessionRegistry

java.lang.Object
ai.singlr.runtime.SessionRegistry

public final class SessionRegistry extends Object
In-memory registry of live sessions. The HTTP service holds one per process; route handlers lookup sessions by id to dispatch send / interrupt / events / close.

Sessions are created via create(SessionOptions) — a Function factory wired at construction (typically AgentSession::create) builds the impl. The factory is injectable so tests can substitute a stub session.

Sessions remain in the registry until close(String) is called, even after they reach a terminal ResultMessage — keeping them around lets late SSE subscribers fetch the final LoopEnded event after termination, and lets the DELETE /sessions/{id} route be the explicit cleanup boundary.

Retention

The registry can keep terminal sessions indefinitely; for long-running services that creates a slow leak. Two opt-in eviction surfaces address this:
  • purgeTerminalOlderThan(Duration) — sweep terminal sessions older than the supplied age. Live sessions are never touched. Best called periodically (every minute or so) from the deployer's scheduler.
  • SessionRegistry.newBuilder().withMaxSessions(int) — cap on registered sessions. When create(SessionOptions) would push the count over the cap, the registry evicts the oldest terminal session first; if none is available, the create call throws IllegalStateException. Live sessions are never evicted.

Thread-safety

Thread-safe. All routes share one registry; concurrent create / get / close are common. Backed by ConcurrentHashMap; create(SessionOptions) rejects duplicate ids. The cap check is best-effort under contention — under a flood of concurrent creates the count may briefly exceed the cap before evictions catch up; the cap is an SLA hint, not a hard barrier.
  • Method Details

    • inMemory

      public static SessionRegistry inMemory()
      Registry that constructs sessions via AgentSession.create(SessionOptions) with system clock and no cap.
      Returns:
      a fresh registry
    • withFactory

      public static SessionRegistry withFactory(Function<ai.singlr.session.SessionOptions, ai.singlr.session.AgentSession> factory)
      Registry that constructs sessions via a custom factory, system clock, no cap. Intended for tests; production sessions use inMemory() or newBuilder().
      Parameters:
      factory - non-null function mapping options to a fresh session
      Returns:
      a fresh registry
      Throws:
      NullPointerException - if factory is null
    • newBuilder

      Start building a registry. Set any of factory / clock / maxSessions; defaults are AgentSession::create, Clock.systemUTC(), and no cap.
      Returns:
      a fresh builder
    • create

      public ai.singlr.session.AgentSession create(ai.singlr.session.SessionOptions options)
      Create a new session from the given options and register it under its session id.
      Parameters:
      options - the composition record; non-null
      Returns:
      the freshly-created, unstarted session
      Throws:
      NullPointerException - if options is null
      IllegalStateException - if a session with the same id is already registered, or if the registry is at its configured maxSessions cap and no terminal session is available to evict
    • get

      public Optional<ai.singlr.session.AgentSession> get(String sessionId)
      Look up a registered session by id.
      Parameters:
      sessionId - non-null id
      Returns:
      the session if present
      Throws:
      NullPointerException - if sessionId is null
    • close

      public boolean close(String sessionId)
      Close and unregister the session. If no session is registered under sessionId this is a no-op.
      Parameters:
      sessionId - non-null id
      Returns:
      true if a session was found and closed; false if no session was registered
      Throws:
      NullPointerException - if sessionId is null
    • sessionIds

      Snapshot of currently-registered session ids. Stable point-in-time view; mutations after this call are not reflected.
      Returns:
      defensive snapshot
    • size

      public int size()
      Number of registered sessions.
      Returns:
      non-negative count
    • closeAll

      public void closeAll()
      Close and unregister every session. Idempotent.
    • purgeTerminalOlderThan

      public int purgeTerminalOlderThan(Duration age)
      Sweep every terminal session whose termination instant is older than age relative to the registry's Clock. Live sessions are not touched, even if the registry has held them far longer than age. Returns the count of sessions closed + unregistered.

      A session is "terminal" once its AgentSession.result() future has completed — the registry captures the wall-clock instant of completion when the future settles, and this method compares that instant against now - age.

      Parameters:
      age - non-null, non-negative; sessions terminated at-or-before now - age are purged
      Returns:
      number of sessions purged
      Throws:
      NullPointerException - if age is null
      IllegalArgumentException - if age is negative