TnsAI

SPI Reference

TnsAI.Core uses Java's ServiceLoader mechanism extensively for cross-module extensibility. SPI interfaces define contracts in the core module; implementations live in optional modules and are discovered at runtime via META-INF/services/ registration.

How SPI Works in TnsAI

  1. Core defines an interface (e.g., CheckpointerProvider)
  2. An optional module implements it (e.g., PostgresCheckpointerProvider)
  3. The implementation is registered in META-INF/services/<interface-fqcn>
  4. At runtime, ServiceLoader.load() discovers all implementations
  5. Core uses the implementation without compile-time dependency on the module

Many SPI interfaces also use the Factory.discover() pattern where a nested Factory interface has a static discover() method that returns null when no implementation is on the classpath.

Core SPI Interfaces

CheckpointerProvider

com.tnsai.spi.CheckpointerProvider -- pluggable storage backends for agent state checkpointing.

public interface CheckpointerProvider {
    String name();
    default String description() { return name() + " checkpointer"; }
    boolean isAvailable();
    Checkpointer create(Map<String, Object> config);
    default int priority() { return 0; }
    default Optional<String> validateSpec(Map<String, Object> config) { return Optional.empty(); }
}
MethodDescription
name()Unique provider name (e.g., "memory", "postgres", "sqlite")
isAvailable()Check if dependencies are present (e.g., JDBC driver)
create(Map<String, Object> config)Create a Checkpointer with config (url, path, username, etc.)
priority()Higher = preferred when multiple providers available
validateSpec(config)Validate config without creating -- returns error message or empty

Registration: META-INF/services/com.tnsai.spi.CheckpointerProvider

Example implementation:

public class PostgresCheckpointerProvider implements CheckpointerProvider {
    @Override
    public String name() { return "postgres"; }

    @Override
    public boolean isAvailable() {
        try {
            Class.forName("org.postgresql.Driver");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    @Override
    public Checkpointer create(Map<String, Object> config) {
        String url = (String) config.get("url");
        return new PostgreSQLCheckpointer(url);
    }
}

CheckpointerFactory

com.tnsai.spi.CheckpointerFactory is a singleton factory that discovers CheckpointerProvider implementations and provides a unified creation API.

CheckpointerFactory factory = CheckpointerFactory.getInstance();

// List providers
List<String> available = factory.availableProviders();

// Create by name
Checkpointer cp = factory.create("postgres", Map.of(
    "url", "jdbc:postgresql://localhost/mydb",
    "username", "user",
    "password", "pass"
));

// Auto-select best available
Checkpointer cp = factory.createBest(Map.of("path", "./data"));

// Default in-memory
Checkpointer cp = factory.createInMemory();

Built-in providers: "memory" (priority -100, always available) and "file" (priority -50, JSON files).

Tool registration is not an SPI

Since v0.6.0, tool discovery is no longer SPI-driven. There is no ToolProvider SPI, no global ToolRegistry, no auto-discovery. Tools are registered explicitly per agent via AgentBuilder.builtInTools(BuiltInTool...), AgentBuilder.toolPojos(Object...), or AgentBuilder.dynamicTool(DynamicToolMethod) — see Tool Integration.

The only "registry" left is each agent's per-instance ToolMethodRegistry, built at AgentBuilder.build() time from the explicitly-registered POJOs and dynamic tools. It is not a service, not a singleton, and not discoverable via ServiceLoader.

CognitiveModel

com.tnsai.spi.CognitiveModel -- unified API for agent cognitive architectures (BDI, Reactive, Hybrid).

public interface CognitiveModel {
    String getModelType();

    // Beliefs
    void addBelief(Belief belief);
    boolean removeBelief(String beliefContent);
    List<Belief> getBeliefs();
    List<Belief> queryBeliefs(String pattern);
    void clearBeliefs();

    // Goals
    void addGoal(Goal goal);
    boolean removeGoal(String goalId);
    List<Goal> getGoals();
    Optional<Goal> getTopGoal();

    // Intentions
    void addIntention(Intention intention);
    void completeIntention(String intentionId);
    List<Intention> getActiveIntentions();
    Optional<Intention> getCurrentIntention();

    // Reasoning cycle
    Optional<Action> reason(Map<String, Object> context);
    void reset();
    CognitiveState snapshot();
    void restore(CognitiveState state);

    // Factory methods
    static BDIModelBuilder bdi() { ... }
    static CognitiveModel reactive() { ... }
}

Inner records: Belief(content, confidence, timestamp, metadata), Goal(id, description, priority, status, parameters), Intention(id, goalId, planDescription, status, steps, currentStep), Action(type, description, parameters), CognitiveState(beliefs, goals, intentions, metadata).

CognitiveModel model = CognitiveModel.bdi()
    .withBelief("User prefers concise answers")
    .withGoal("Help the user effectively")
    .build();

model.addBelief(Belief.of("User is a developer", 0.9));
Optional<Action> next = model.reason(Map.of("input", "help me debug"));

MessageBroker

com.tnsai.spi.MessageBroker -- abstraction for agent-to-agent message passing (direct, pub/sub, request-reply, broadcast).

public interface MessageBroker {
    void send(String targetAgentId, Message message);
    void publish(String topic, Message message);
    String subscribe(String agentId, Consumer<Message> handler);
    String subscribeTopic(String topic, Consumer<Message> handler);
    void unsubscribe(String subscriptionId);
    CompletableFuture<Message> request(String targetAgentId, Message request);
    void broadcast(Message message);
    void close();
    static MessageBroker inMemory() { ... }
}

Message (record): id, from, to, topic, payload (Object), headers (Map), timestamp.

MessageBroker broker = MessageBroker.inMemory();

broker.subscribe("agent-1", msg -> System.out.println("Got: " + msg.payload()));
broker.publish("tasks", Message.of("Process data"));

CompletableFuture<Message> reply = broker.request("agent-1", Message.of("status?"));

ResilienceStrategy

com.tnsai.spi.ResilienceStrategy -- unified abstraction for resilience patterns (retry, circuit breaker, timeout, fallback).

public interface ResilienceStrategy {
    <T> T execute(Callable<T> operation) throws Exception;
    default <T> T executeUnchecked(Supplier<T> operation) { ... }
    default void execute(Runnable operation) throws Exception { ... }
    default ResilienceStrategy andThen(ResilienceStrategy after) { ... }
    default String name() { ... }
    default boolean isHealthy() { return true; }
    default void reset() { }
    static Builder builder() { ... }
    static ResilienceStrategy noOp() { ... }
}

The builder composes retry, circuit breaker, timeout, and fallback:

ResilienceStrategy strategy = ResilienceStrategy.builder()
    .retry(3, Duration.ofMillis(500))
    .circuitBreaker(5, Duration.ofSeconds(30))
    .timeout(Duration.ofSeconds(10))
    .fallback(() -> "default value")
    .build();

String result = strategy.execute(() -> riskyOperation());

Strategies can also be composed with andThen:

ResilienceStrategy combined = retryStrategy.andThen(timeoutStrategy);

Factory.discover() Pattern

Many SPI interfaces use an inner Factory interface with a static discover() method. Examples include:

  • EvalHandle.Factory.discover() -- returns null if tnsai-quality is absent
  • FeedbackCollector.Factory.discover() -- returns null if tnsai-intelligence is absent
  • ContextManagerHandle.Factory.discover() -- returns null if tnsai-intelligence is absent
  • SecurityEnforcerHandle.Factory.discover() -- returns NOOP if tnsai-security is absent

This pattern allows core code to initialize with no-op implementations when optional modules are not on the classpath:

EvalHandle.Factory factory = EvalHandle.Factory.discover();
this.evalHandle = factory != null ? factory.create() : EvalHandle.NOOP;

On this page