TramAI - governed AI workflows for Java and Kotlin

Sovereign Runtime Quickstart

The sovereign runtime is a runtime profile of the unified TramAI Spring Boot starter, not a separate starter. You add one starter, select tramai.profile: sovereign in configuration, and declare an explicit allowlist block. The same application code (@AiService interfaces, constructor injection, @AiTool methods) runs unchanged under either profile.

This page gets you to a startable sovereign application. For the security model behind it, see Sovereign Mode.

Maturity: the sovereign modules (dev.tramai:tramai-sovereign, tramai-spring-sovereign) are published preview surface in 0.6.0. Preview means the API can change in a minor release — 0.6.0 is not a stable 1.0 surface.


Add Dependencies

BOM-first, as with every other TramAI module:

dependencies {
    implementation(platform("dev.tramai:tramai-bom:0.6.0"))
    implementation("dev.tramai:tramai-spring-boot-starter")
    implementation("dev.tramai:tramai-spring-provider-ollama")   // or -openai / -anthropic
}

tramai-spring-boot-starter is an auto-configuration aggregator over tramai-spring-core (standard runtime) and tramai-spring-sovereign (sovereign runtime). The profile decides which one activates.

Sovereign subsystems are opt-in add-ons — add the ones matching the configuration you enable:

Add-onWhat it activates
tramai-spring-boot-starter-sovereign-persistence-fileEncrypted file-backed stores (approvals, continuations, audit, outbox)
tramai-spring-boot-starter-sovereign-persistence-jdbcJDBC-backed stores, versioned schema migrations, worker leases, resume credentials
tramai-spring-boot-starter-sovereign-opsAudit outbox, recovery, dispatch, background workers, observer SPI
tramai-spring-boot-starter-sovereign-ops-actuatorRead-only worker status endpoint and health component
tramai-spring-boot-starter-sovereign-ops-micrometerMicrometer metrics for the ops workers
tramai-spring-boot-starter-sovereign-ops-observabilityOpenTelemetry metrics for the ops workers
tramai-spring-boot-starter-sovereign-ops-restApproval inbox and decision/resume REST control plane (preview)

Secrets resolve through the same mechanism as any other Spring property: tramai-spring-secrets-file, tramai-spring-secrets-vault, tramai-spring-secrets-aws.

The former standalone sovereign starter coordinate (tramai-spring-boot-starter-sovereign) no longer exists in 0.6.0. If you are migrating, replace it with the unified starter plus tramai.profile — see Migrating to 0.6.0.

Module reference: tramai-spring-boot-starter, tramai-spring-sovereign, tramai-sovereign, tramai-persistence-jdbc.


Minimal Configuration

tramai:
  profile: sovereign
  providers:
    ollama:
      base-url: http://localhost:11434
  sovereign:
    allowed-models: [local-invoice-model]
    allowed-providers: [ollama]
    provider-zones:
      ollama: LOCAL
    models:
      local-invoice-model: ollama

Four things must be true, or startup fails:

  1. tramai.profile is sovereign (or standard, which skips all of this).
  2. The provider adapter is on the classpath and configured — for Ollama, base-url is required; without it the adapter registers no provider.
  3. Every allowed provider has an explicit trust zone.
  4. Every allowed model has a route to an allowed provider.
@AiService
interface InvoiceService {
    @Operation(model = "local-invoice-model")
    fun analyse(input: InvoiceInput): InvoiceAnalysisResult
}

No @EnableTramai is needed — Spring Boot auto-configuration is sufficient. The annotation does not select a runtime profile in 0.6.0; tramai.profile is the sole authority.


The tramai.sovereign.* Block

Bound by SovereignTramaiProperties (tramai-spring-sovereign):

PropertyRequiredMeaning
allowed-modelsYesModels whose invocation is permitted. Non-empty. Every entry must appear as a key in models.
allowed-providersYesProviders that may be used. Non-empty. Every entry must have a provider-zones entry.
allowed-toolsNoTools whose execution is permitted.
allowed-permissionsNoTool permissions that are granted.
provider-zonesYesExplicit trust zone per allowed provider — LOCAL, EU_CLOUD, or GLOBAL_CLOUD. Keys must be a subset of allowed-providers.
modelsYesMaps each logical model name to its provider. Keys must be a subset of allowed-models; values must be allowed providers.
enabledNoCompatibility switch. Defaults to true.

There are no defaults and no wildcards: an empty allowlist denies everything, and a missing route is a startup error rather than a runtime surprise.

Fail-fast validation rules

Validation runs while the Spring context is starting. Each failure names the exact problem:

RuleFailure
enabled must not be falsetramai.sovereign.enabled=false is not supported: tramai.profile is the sole runtime selector
allowed-models non-emptytramai-sovereign-spring-missing-allowed-models
allowed-providers non-emptytramai-sovereign-spring-missing-allowed-providers
Zone value must be a ProviderTrustZonetramai-sovereign-spring-invalid-provider-zone
Every allowed provider needs a zonetramai-sovereign-spring-provider-zone-missing
No zones for unknown providerstramai-sovereign-spring-provider-zone-unknown-provider
Every allowed model needs a routetramai-sovereign-spring-missing-model-route
No routes for unknown modelstramai-sovereign-spring-model-route-unknown-model
Route target must be an allowed providertramai-sovereign-spring-model-route-unknown-provider
Unsupported profile valueUnsupported tramai.profile '<value>'. Supported values: standard, sovereign.

tramai.sovereign.enabled: false is rejected deliberately. Before 0.6.0 sovereign was a separate starter and the flag meant "sovereign features off"; now the flag has no coherent meaning because the profile is the selector. Remove the property, or set tramai.profile: standard to run the standard runtime.


Persistence and Ops

Encrypted file-backed stores:

implementation("dev.tramai:tramai-spring-boot-starter-sovereign-persistence-file")
tramai:
  sovereign:
    persistence:
      type: file
      base-dir: ./data/tramai-sovereign
      encryption:
        key-env: TRAMAI_SOVEREIGN_STORE_KEY

TRAMAI_SOVEREIGN_STORE_KEY must hold a base64-encoded 256-bit key.

JDBC-backed stores (multi-node safe, schema migrations required before startup):

implementation("dev.tramai:tramai-spring-boot-starter-sovereign-persistence-jdbc")
implementation("dev.tramai:tramai-spring-boot-starter-sovereign-ops")
runtimeOnly("org.postgresql:postgresql")
tramai:
  sovereign:
    persistence:
      type: jdbc
      jdbc:
        claim-lease-duration: 5m
        max-claim-limit: 500
      encryption:
        key-env: TRAMAI_SOVEREIGN_STORE_KEY
    ops:
      outbox:
        worker:
          enabled: true
          lease-enabled: true
          lease-name: sovereign-ops-audit-outbox-worker
          worker-id: ${HOSTNAME:node-1}
      approved-resume-worker:
        enabled: true

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/tramai
    username: ${DB_USER}
    password: ${DB_PASSWORD}

JDBC stores sanitize driver failures. A constraint violation or SQL syntax error surfaces as IllegalStateException with redacted details — never as a raw java.sql.SQLException carrying your query text, table names, or connection string. Do not write catch blocks for java.sql.SQLException; catch IllegalStateException (or RuntimeException) instead. Coroutine CancellationException and TramAI domain exceptions propagate unwrapped.


Non-Spring Usage

The same profile is available embedded, without Spring: SovereignTramai.builder() in dev.tramai.sovereign.

import dev.tramai.security.ProviderTrustZone
import dev.tramai.security.audit.InMemoryAuditStore
import dev.tramai.security.model.InMemoryModelRegistry
import dev.tramai.sovereign.SovereignDeploymentMode
import dev.tramai.sovereign.SovereignProfileConfiguration
import dev.tramai.sovereign.SovereignTramai

val profile = SovereignProfileConfiguration(
    allowedModels = setOf("llama3.2"),
    allowedProviders = setOf("ollama"),
    allowedTools = setOf("schedule-payment"),
    allowedPermissions = setOf("payment.schedule"),
    providerZones = mapOf("ollama" to ProviderTrustZone.LOCAL),
    deploymentMode = SovereignDeploymentMode.STANDARD,
)

val tramai = SovereignTramai.builder()
    .profile(profile)
    .modelRegistry(InMemoryModelRegistry.builder().register(registeredModel).build())
    .auditStore(InMemoryAuditStore())
    .provider(ollamaProvider, name = "ollama", default = true)
    .model("llama3.2", "ollama")
    .build()

try {
    val service = tramai.create(InvoiceService::class)
    // ...
} finally {
    tramai.close()   // SovereignTramai : AutoCloseable — close the owned engine
}

SovereignProfileConfiguration validates in its constructor and rejects what the Spring properties reject plus a few extras:

  • empty allowedModels / allowedProviders
  • blank entries, or entries with surrounding whitespace
  • wildcard entries
  • a providerZones key that is not in allowedProviders
  • an allowed provider without a zone
  • allowedFallbackProviders that is not a subset of allowedProviders

SovereignDeploymentMode.OFFLINE additionally requires every registered provider, primary route, fallback route, and default provider to be LOCAL; violating configurations fail with offline-profile-non-local-provider-rejected (and the route-specific variants). See Offline Deployment.

To run approval-gated tools and resume suspended executions:

val runtime = tramai.runtime()

runtime.registerService(InvoiceService::class)   // required after a restart, before resume
runtime.resumeApprovalTyped<InvoiceAssessment>(command)
runtime.close()

Verify the Runtime You Built

Generate a deployment evidence pack — deterministic JSON with no secrets, tokens, prompts, or filesystem paths:

import dev.tramai.sovereign.evidence.SovereignEvidencePackWriter
import java.nio.file.Path

val pack = tramai.evidencePack()   // dev.tramai.sovereign.evidence.SovereignEvidencePackV1
SovereignEvidencePackWriter.write(pack, Path.of("build/sovereign-evidence/sovereign-evidence-pack-v1.json"))

The pack records the deployment mode, the allowed models and providers, the provider zones, and the artifact-verification receipts from build time. Optional subsections cover zero-egress probes, audit-chain validity, SBOM linkage, release-bundle digests, and CI attestation. See Evidence Packs.


What This Quickstart Does Not Cover

  • Key rotation and secrets lifecycle
  • Applying the JDBC schema migrations (required before starting against a real database)
  • Infrastructure-level network isolation for offline deployments — firewall, NetworkPolicy, sandbox, or a physical air gap
  • A stable 1.0 API — types annotated @ExperimentalTramaiInternalApi and @ExperimentalProviderTransportApi are internal cross-module contracts and may change without notice

Next Steps