Skip to content

Spring Boot Starter

agenor-spring-boot-starter provides zero-configuration auto-wiring of AgenorRuntime into any Spring Boot 4.0.x application. Add one dependency, configure application.yml, and Agenor starts with the Spring context.

Dependency

<dependency>
    <groupId>dev.agenor</groupId>
    <artifactId>agenor-spring-boot-starter</artifactId>
    <version>0.33.0</version>
</dependency>

All Spring Boot dependencies are declared optional=true — they do not appear on the transitive classpath of non-Spring consumers.

Quick Start

application.yml:

agenor:
  agents:
    base-package: com.example.agents

That is all. AgenorRuntime is created, started, and stopped automatically by the Spring lifecycle. Agents in the configured package are discovered and registered at startup.

Configuration Reference

All keys are under the agenor prefix. Every key is optional and falls back to a default.

agenor.runtime

Key Default Description
name agenor-runtime Runtime instance name
environment development Environment label (development, staging, production, test)
properties {} Arbitrary key/value pairs forwarded to RuntimeConfig

agenor.agents

Key Default Description
auto-discovery true Discover @Agent classes at startup
base-package Root package to scan
scan-packages [] Additional packages to scan
scan-paths [] Legacy alias for scan-packages (kept for compatibility with agenor.yml)

base-package and scan-packages are merged — both are scanned.

agenor.scheduler

Key Default Description
provider simple Scheduler implementation
thread-pool-size 10 Thread pool size

agenor.messaging

Key Default Description
provider inmemory Implementation: inmemory, redis
redis.uri redis://localhost:6379 Redis URI (only when provider=redis)
redis.consumer-group-prefix agenor Prefix for stream keys and consumer groups
redis.read-block-timeout-ms 2000 XREADGROUP BLOCK timeout (ms)
redis.max-stream-length 100000 Max entries per stream before trimming
redis.pending-entries-timeout-ms 30000 Idle time before redelivery of pending entries (ms)
redis.max-delivery-attempts 3 Attempts before moving to dead-letter

The redis.* sub-section is only read when provider=redis and agenor-adapters is on the classpath. @ConditionalOnMissingBean allows providing a custom RedisMessagingFactory bean to override all defaults.

Dead letters exist on both providers, and only their reach differs. The two redis.* keys above govern redelivery — how many times a message is retried and how long an unacknowledged one waits — not whether a message the framework gives up on is recorded. With provider=inmemory there is no redelivery, so a failing handler dead-letters on the first attempt into a bounded in-memory buffer that forgets on restart; with provider=redis it dead-letters after redis.max-delivery-attempts into a durable stream. Either way runtime.getDeadLetterQueue() answers, and the console's GET /api/deadletters shows it. See Messaging.

Redis example:

agenor:
  messaging:
    provider: redis
    redis:
      uri: redis://localhost:6379
      consumer-group-prefix: my-app

agenor.directory

Key Default Description
provider local Implementation: local, inmemory, jdbc
jdbc.url JDBC connection URL (required when provider=jdbc)
jdbc.username "" Database username
jdbc.password "" Database password
jdbc.pool-size 10 HikariCP connection pool size

The jdbc.* sub-section is only read when provider=jdbc and agenor-adapters-persistence is on the classpath. Flyway schema migration runs automatically on startup.

JDBC example:

agenor:
  directory:
    provider: jdbc
    jdbc:
      url: jdbc:postgresql://localhost:5432/mydb
      username: agenor
      password: ${DB_PASSWORD}

agenor.llm

Key Default Description
provider none LLM provider (none, openai, anthropic, ollama)
api-key API key (required for openai and anthropic)
model provider default Model name
base-url http://localhost:11434 Base URL (used by ollama only)

When provider=none (default), no LLMProvider bean is created. When a non-none provider is set, agenor-adapters must be on the classpath — the starter will fail fast with a clear error message otherwise.

Provider defaults:

Provider Default model
openai gpt-4o-mini
anthropic claude-3-haiku-20240307
ollama llama3.2

LLM Provider Bean and Agent Injection

When agenor.llm.provider is set, the auto-configuration creates a LLMProvider bean and registers it with the runtime. Agents that declare a LLMProvider constructor parameter receive it automatically via AgentFactory constructor injection:

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

    private final LLMProvider provider;

    // AgentFactory injects the configured LLMProvider automatically
    public MyLlmAgent(LLMProvider provider) {
        this.provider = provider;
    }

    @Behavior(type = AgenorBehaviorType.CYCLIC, interval = "30s")
    public void analyze() {
        LLMRequest req = LLMRequest.builder(provider.getProviderName())
                .userMessage("Summarize the current system status.")
                .maxTokens(200)
                .build();
        log.info("LLM: {}", provider.chat(req).join().content());
    }
}

For agents that should work both with and without LLM, provide a no-arg fallback constructor:

public MyLlmAgent(LLMProvider provider) { this.provider = provider; }
public MyLlmAgent()                     { this.provider = null; }   // fallback

AgentFactory prefers the most-parameter constructor. If LLMProvider is not available, it falls back to the no-arg constructor.

Lifecycle

The starter uses SmartLifecycle with phase = Integer.MAX_VALUE - 1. This means:

  • Start: fires after all infrastructure beans (data sources, messaging, etc.) — blocks until all agents are running
  • Stop: fires before infrastructure beans — blocks until all agents are stopped

This guarantees a clean startup/shutdown sequence and that the health endpoint reflects the true state of the runtime.

Actuator Health

When spring-boot-starter-actuator is on the classpath, the starter registers a HealthIndicator automatically:

curl http://localhost:8080/actuator/health
{
  "status": "UP",
  "components": {
    "agenor": {
      "status": "UP",
      "details": {
        "runtime.name": "my-system",
        "agents.total": 3,
        "agents.running": 3
      }
    }
  }
}

User Bean Override

Every auto-configured bean is guarded by @ConditionalOnMissingBean. Declare your own bean to override any default:

@Configuration
public class MyAgenorConfig {

    // Overrides the auto-configured AgenorRuntime
    @Bean(name = "agenorRuntime")
    public AgenorRuntime agenorRuntime() {
        return AgenorRuntime.builder()
                .fromClasspathConfig("my-agenor.yml")   // use a custom YAML file
                .build();
    }

    // Must also provide the lifecycle bean when overriding the runtime
    @Bean
    public SmartLifecycle AgenorRuntimeLifecycle(AgenorRuntime agenorRuntime) {
        return new SmartLifecycle() {
            private volatile boolean running = false;
            public void start() { agenorRuntime.start().join(); running = true; }
            public void stop()  { agenorRuntime.stop().join();  running = false; }
            public boolean isRunning() { return running; }
        };
    }
}

Relationship with agenor.yml

The starter configures the runtime exclusively from application.yml via @ConfigurationProperties. It does not load agenor.yml from the classpath.

If you need to load from a agenor.yml file, declare your own AgenorRuntime bean (which suppresses auto-configuration via @ConditionalOnMissingBean):

@Bean
public AgenorRuntime agenorRuntime() {
    return AgenorRuntime.builder()
            .fromClasspathConfig("agenor.yml")
            .build();
}

Spring Boot 4.x

The starter targets Spring Boot 4.0.x (adopted in 0.18.0, see ADR-016). The auto-configuration API (AutoConfiguration.imports, @ConditionalOnMissingBean, etc.) is stable across Spring Boot 4.x versions.

See Also