TramAI - governed AI workflows for Java and Kotlin

Spring Boot Integration

What it is: tramai-spring-boot-starter is the canonical Spring Boot dependency. It aggregates tramai-spring-core (profile-neutral Spring integration) and tramai-spring-sovereign (sovereign runtime integration) into one artifact. @AiService interfaces become Spring beans with no manual wiring, and tramai.profile — not a different dependency and not an annotation — selects which runtime the application runs on.

When to use it: You have a Spring Boot application and want tramai services injected as standard beans with YAML-driven configuration.

Property-driven provider configuration comes from separate adapter artifacts (tramai-spring-provider-openai, -anthropic, -ollama). The raw tramai-openai / tramai-anthropic / tramai-ollama modules are provider implementations, not Spring property adapters.

Minimum Setup

Step 1: Add the dependency

dependencies {
    implementation(platform("dev.tramai:tramai-bom:0.6.0"))
    implementation("dev.tramai:tramai-spring-boot-starter")     // standard + sovereign
    implementation("dev.tramai:tramai-spring-provider-openai")  // provider adapter for Spring
}
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>dev.tramai</groupId>
      <artifactId>tramai-bom</artifactId>
      <version>0.6.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>dev.tramai</groupId>
    <artifactId>tramai-spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>dev.tramai</groupId>
    <artifactId>tramai-spring-provider-openai</artifactId>
  </dependency>
</dependencies>

Depend on the starter only. tramai-spring-core and tramai-spring-sovereign arrive transitively — do not compose them by hand.

Step 2: Configure in YAML

tramai:
  default-provider: openai
  models:
    gpt-4o: openai
  providers:
    openai:
      api-key: ${OPENAI_API_KEY}

Step 3: Inject and use

@Service
class BillingService(
    private val invoiceAnalyzer: InvoiceAnalyzer,  // injected Spring bean
) {
    suspend fun process(invoiceText: String) =
        invoiceAnalyzer.analyze(invoiceText)
}

No annotation is required. Under Spring Boot the starter's auto-configuration discovers tramai automatically: it binds tramai.*, builds the runtime, registers every @AiService interface as a bean, and scans @AiTool methods.

@EnableTramai is not required. In 0.6.0 the annotation exists for annotation-driven, non-Boot Spring contexts, and it selects the Spring programming model — not a runtime profile. tramai.profile remains the only thing that selects a runtime. Leaving @EnableTramai on a Boot application is harmless (it coexists with auto-configuration without duplicate beans); it is simply not part of the setup.

Runtime Profiles

tramai.profile selects the runtime. Application code is identical under both profiles.

tramai.profileRuntimeBehavior
missingstandardconfigurable general-purpose runtime
standardstandardsame as missing
sovereignsovereignfail-closed governed execution, model/provider allowlists, tool metadata enforcement
  • The value is case-insensitive. Any other value fails at startup.
  • There is no implicit sovereign selection.
  • Exactly one runtime authority is active — never both.

Provider Adapters

Adapter artifactBound property namespace
tramai-spring-provider-openaitramai.providers.openai.*, tramai.providers.openai-compatible.*
tramai-spring-provider-anthropictramai.providers.anthropic.*
tramai-spring-provider-ollamatramai.providers.ollama.*

Add the adapter matching the provider you configure. Adapters contribute the provider to the runtime under both profiles with identical semantics; you can also register a ModelProvider bean directly, and a bean-backed provider wins over a property-defined one with the same name.

InvoiceAnalyzer Example

import dev.tramai.core.annotations.AiService
import dev.tramai.core.annotations.Operation

// 1. Define your typed result
data class InvoiceVerdict(
    val status: String,          // "approved" | "needs_review" | "rejected"
    val reason: String,
    val missingFields: List<String>,
)

// 2. Define your AI service interface
@AiService
interface InvoiceAnalyzer {
    @Operation(
        prompt = "Analyze the invoice. Return status, reason, and any missing required fields.",
        model = "gpt-4o",
    )
    suspend fun analyze(invoiceText: String): InvoiceVerdict
}

// 3. Inject and use it anywhere
@Service
class BillingService(
    private val analyzer: InvoiceAnalyzer,  // auto-wired by Spring
) {
    suspend fun process(invoiceText: String): InvoiceVerdict =
        analyzer.analyze(invoiceText)
}

// 4. Expose it
@RestController
class InvoiceController(
    private val billingService: BillingService,
) {
    @PostMapping("/invoices/analyze")
    suspend fun analyze(@RequestBody text: String): InvoiceVerdict =
        billingService.process(text)
}

The model = "gpt-4o" name must appear as a key under tramai.models (or tramai.sovereign.models in the sovereign profile). Tramai handles the LLM call, JSON extraction, parsing, validation, and retries; your code never touches HTTP clients or JSON parsers.

Java Callers

Kotlin suspend methods are the normal contract. For Java, declare an explicit blocking method — the generated proxy wraps non-suspend methods in runBlocking:

@AiService
public interface BlockingInvoiceAnalyzer {
    @Operation(
        prompt = "Analyze the invoice and return a short status.",
        model = "gpt-4o"
    )
    String analyze(String invoiceText);
}

Keep blocking Java contracts explicit; tramai does not generate *Blocking companion methods.

Multiple Providers and Fallback Routing

tramai:
  default-provider: openai
  models:
    gpt-4o: openai
    gpt-4o-mini: openai
    llama3.2: ollama
  fallbacks:
    gpt-4o:
      - provider: openai
        model: gpt-4o-mini
      - provider: ollama
        model: llama3.2
  providers:
    openai:
      api-key: ${OPENAI_API_KEY}
    ollama:
      base-url: http://localhost:11434

Fallback order is explicit and taken from tramai.fallbacks. The engine does not guess. Operations reference models by name in @Operation(model = "gpt-4o-mini").

YAML Reference

Settings bound by tramai-spring-core:

PropertyDescriptionDefault
tramai.default-providerProvider used when no model mapping matchesnone
tramai.models.<model>Model-to-provider mappingnone
tramai.fallbacks.<model>[].providerFallback provider for a modelnone
tramai.fallbacks.<model>[].modelFallback model namenone
tramai.resilience.circuit-breaker.enabledEnable circuit breakingfalse
tramai.resilience.circuit-breaker.failure-thresholdFailures before the circuit opens3
tramai.resilience.circuit-breaker.open-duration-millisHow long the circuit stays open30000
tramai.resilience.retry.max-retry-after-millisCap on provider-requested retry delay30000
tramai.resilience.retry.jitter-ratioRetry jitter ratio0.2
tramai.cost.token-budget.hard-max-tokens-per-attemptHard per-attempt token capnone
tramai.cost.token-budget.hard-max-tokens-per-operationHard per-operation token capnone
tramai.cost.token-budget.soft-max-tokens-per-operationSoft per-operation token capnone
tramai.cache.in-memory.enabledIn-memory cache for cacheable operationsfalse
tramai.cache.in-memory.max-entriesCache entry cap1000
tramai.security.model-registry.enabledEnforce the approved model registryfalse
tramai.security.classification.enabledEnable data-classification rulesfalse
tramai.security.classification.default-classificationClassification applied when no rule matchesINTERNAL
tramai.security.classification.max-text-lengthMaximum text length examined for classification100000
tramai.security.classification.rules[]Rules with id, classification, priority, pattern, metadata-equalsnone

Provider settings come from the adapter modules:

PropertyModule
tramai.providers.openai.api-key / .api-key-secret-ref / .bearer-token / .base-url / .organization / .projecttramai-spring-provider-openai
tramai.providers.openai-compatible.provider-name / .api-key / .bearer-token / .base-urltramai-spring-provider-openai
tramai.providers.anthropic.api-key / .api-key-secret-ref / .base-urltramai-spring-provider-anthropic
tramai.providers.ollama.base-urltramai-spring-provider-ollama

Resilience, Cost, Cache, Security

tramai:
  resilience:
    circuit-breaker:
      enabled: true
      failure-threshold: 3
      open-duration-millis: 30000
    retry:
      max-retry-after-millis: 20000
      jitter-ratio: 0.1
  cost:
    token-budget:
      hard-max-tokens-per-attempt: 4000
      hard-max-tokens-per-operation: 12000
      soft-max-tokens-per-operation: 8000
  cache:
    in-memory:
      enabled: true
      max-entries: 1000

Token budgets are enforced from provider-reported usage. Caching still requires @Operation(cacheable = true) on the operation itself; tool-enabled operations are never cached.

Secret References

Resolve credentials by reference instead of embedding them:

tramai:
  providers:
    openai:
      api-key-secret-ref: env:OPENAI_API_KEY

Built-in schemes:

  • env:NAME
  • file:/absolute/path/to/secret.txt
  • vault:path[#field] with tramai-spring-secrets-vault on the classpath (configured under tramai.secrets.vault.*)
  • aws-secretsmanager:secret-id[#field] with tramai-spring-secrets-aws on the classpath (configured under tramai.secrets.aws-secrets-manager.*)

You can also provide your own SecretValueResolver bean.

Sovereign Profile

tramai.profile: sovereign switches to the sovereign runtime: fail-closed governance, explicit provider/model allowlists, and tool metadata enforcement. The @AiService interfaces, constructor injection, and @AiTool methods do not change.

tramai:
  profile: sovereign
  sovereign:
    allowed-models: [local-invoice-model]
    allowed-providers: [deterministic-local-provider]
    allowed-tools: [schedule-payment]
    allowed-permissions: [payment.schedule]
    provider-zones:
      deterministic-local-provider: LOCAL
    models:
      local-invoice-model: deterministic-local-provider
PropertyDescription
tramai.sovereign.allowed-modelsModels that may be invoked; each must have a route in models
tramai.sovereign.allowed-providersProviders that may be used; each needs a trust zone
tramai.sovereign.allowed-toolsTools whose execution is permitted
tramai.sovereign.allowed-permissionsTool permissions granted
tramai.sovereign.provider-zones.<provider>Trust zone per provider: LOCAL, EU_CLOUD, or GLOBAL_CLOUD
tramai.sovereign.models.<model>Model-to-provider route; keys must be a subset of allowed-models

Configuration is validated at startup: allowed models and providers must be non-empty, every allowed provider needs an explicit zone, every zone must reference an allowed provider, and every allowed model needs a route to an allowed provider. Violations fail startup with a typed message rather than degrading to a permissive runtime.

tramai.sovereign.enabled: false is rejected at startup — tramai.profile is the sole runtime selector. To run standard, remove the key (or set tramai.profile: standard).

Optional sovereign add-ons, all separate artifacts:

ArtifactPurpose
tramai-spring-boot-starter-sovereign-persistence-file / -jdbcDurable approval and checkpoint persistence
tramai-spring-boot-starter-sovereign-ops (+ -rest, -actuator, -micrometer, -observability)Approval operations, control-plane endpoints, metrics
tramai-spring-secrets-file / -vault / -awsSecret backends
tramai-spring-boot-starter-local-provider-openaiConfigure OpenAI-compatible local endpoints under tramai.providers.<name>

The 0.4.0/0.5.0 coordinate tramai-spring-boot-starter-sovereign does not exist in 0.6.0. Migrating is the unified starter plus tramai.profile: sovereign.

What Auto-Configuration Provides

  • Runtime creation: Binds tramai.*, creates the Tramai runtime (or the sovereign runtime under tramai.profile: sovereign), and closes it with the context
  • Service bean registration: Scans for @AiService interfaces and registers proxy beans — no manual Tramai.create()
  • Tool discovery: Scans beans for @AiTool methods and registers them as tools
  • Provider wiring: Merges property-defined providers from adapter modules with explicit ModelProvider beans
  • Secret resolution: Resolves env: and file: references natively, plus Vault and AWS Secrets Manager through their modules
  • Resilience and cost: Applies circuit breaker, retry pacing, and token budgets from YAML
  • Cache: Optional in-memory response cache

Limitations

  • No advanced bean scopes: AI service beans are standard singleton proxies.
  • No per-bean provider overrides outside annotations: Provider selection is @Operation(provider = ...) or the model mapping registry.
  • No automatic test slices: @WebMvcTest and friends do not isolate tramai. Replace the provider with a deterministic ModelProvider bean, or use tramai-testing.
  • Thin adapter: The Spring integration focuses on wiring. It adds no Spring-specific abstraction on top of the engine.

Next Steps