TramAI - governed AI workflows for Java and Kotlin

Standalone Builder Reference

dev.tramai.standalone.Tramai is the minimal, Spring-free composition module for TramAI. It wires tramai-core, tramai-engine, and tramai-structured into a usable runtime and nothing else — it does not pull observability or Spring transitively.

Use it when you want a TramAI runtime you create and close yourself, in any JVM application.

Entry Points

Kotlin DSL:

import dev.tramai.standalone.Tramai
import dev.tramai.openai.OpenAiProvider

val tramai = Tramai {
    provider(OpenAiProvider(System.getenv("OPENAI_API_KEY")), name = "openai", default = true)
    model("gpt-4o", "openai")
    tools(VendorLookupTool())
}

Explicit builder, for Java and for cases where you want the builder object:

val tramai = Tramai.builder()
    .provider(OpenAiProvider(System.getenv("OPENAI_API_KEY")), "openai", true)
    .model("gpt-4o", "openai")
    .build()

Service creation:

val service = tramai.create<MyService>()   // reified convenience
val same = tramai.create(MyService::class) // explicit form

Ownership And Lifecycle

Tramai implements AutoCloseable and owns the TramaiRuntime (and therefore the engine) it creates.

val tramai = Tramai {
    provider(OpenAiProvider(System.getenv("OPENAI_API_KEY")), name = "openai", default = true)
    model("gpt-4o", "openai")
}

tramai.use {
    val service = it.create<MyService>()
    // ...
}
  • Closing Tramai closes the runtime it owns. After close, create(...) fails.
  • Providers, stores, clients, executors, and observers you pass to the builder stay caller-owned. TramAI does not close them.
  • The built Tramai is immutable: builder state is frozen at build(), including the resolved tools and the diagnostic observer snapshot. Mutating a builder after build() cannot redirect what the built runtime does.

Providers

provider(provider, name = provider.providerId(), default = false)

Registers a ModelProvider under name. With default = true, that provider is used when no more specific mapping applies.

provider(OpenAiProvider(System.getenv("OPENAI_API_KEY")), name = "openai")
provider(OllamaProvider("http://localhost:11434"), name = "ollama")

Model Routing

model(modelName, providerName)

Maps a logical model name to a registered provider name. This is the main routing table.

model("gpt-4o", "openai")
model("llama3.2", "ollama")

Resolution order at call time: explicit @Operation(provider = ...), then the model-to-provider route, then the default provider. If nothing matches, the call fails with a configuration error rather than guessing.

fallbackModel(requestedModelName, fallbackModelName, providerName)

Adds an explicit fallback route for a requested model, optionally with a different effective model name.

fallbackModel("gpt-4o", "gpt-4o-mini", "openai")

fallbackProvider(modelName, providerName)

Adds a fallback route that keeps the model name and tries another provider.

fallbackProvider("gpt-4o", "compatible-gateway")

defaultProvider(providerName)

Selects the provider used when no explicit mapping applies.

defaultProvider("openai")

Routing is validated at build(): a model with fallback routes must have a primary route, a second primary route for the same model is an error, and every route must target a registered provider.

buildRoutingPlan()

Returns the frozen ProviderRoutingPlan for the current builder state — the same instance later installed in the runtime. Use it when you need to inspect or validate routing before building (for example, sovereign validation).

Tools

tools(vararg tools) / tools(tools: Iterable<TramaiTool<*, *>>)

Registers TramaiTool definitions.

tools(
    VendorLookupTool(),
    CurrencyLookupTool(),
)
  • Duplicate tool names fail fast with a ConfigurationException.
  • The tool input schema is generated from the declared input type.
  • Idempotent tools and non-idempotent tools are treated differently on transient failure paths.

Observation And Interception

observer(observer: OperationObserver)

Configures the observer for engine attempts. Use it for tracing, metrics, or recording test behaviour (RecordingOperationObserver from tramai-testing).

interceptor(interceptor: OperationInterceptor)

Configures request/response shaping hooks around execution.

engineEventObserver(observer: EngineEventObserver)

Receives engine-level security events (DLP rejection, inspection failure) independently of provider-attempt observations. Defaults to a no-op.

toolFailureDiagnosticObserver(observer: ToolFailureDiagnosticObserver)

Receives the original tool-failure throwable for internal diagnostics only. Observer data is never forwarded to model messages, public exceptions, or engine events. Defaults to a no-op.

structuredOutputFailureDiagnosticObserver(observer: StructuredOutputFailureDiagnosticObserver)

Receives structured-output failure diagnostics. Defaults to a no-op.

Cache

cache(cache: OperationResponseCache)

Configures the response cache.

cache(InMemoryOperationResponseCache(maxEntries = 1_000))

This affects only operations that opt in with @Operation(cacheable = true) and only successful non-streaming responses.

Resilience

circuitBreaker(settings: CircuitBreakerSettings)

Engine-owned provider health protection.

circuitBreaker(
    CircuitBreakerSettings(
        enabled = true,
        failureThreshold = 3,
        openDurationMillis = 30_000,
    ),
)

retryPolicy(settings: RetryPolicySettings)

Retry pacing for retryable provider failures.

retryPolicy(
    RetryPolicySettings(
        maxRetryAfterMillis = 30_000,
        jitterRatio = 0.2,
    ),
)

tokenBudget(settings: TokenBudgetSettings)

Token budget controls based on provider-reported usage. All three limits are nullable and unset by default.

tokenBudget(
    TokenBudgetSettings(
        hardMaxTokensPerAttempt = 4_000,
        hardMaxTokensPerOperation = 12_000,
        softMaxTokensPerOperation = 8_000,
    ),
)
  • hardMaxTokensPerAttempt fails a single provider attempt when reported usage is too high.
  • hardMaxTokensPerOperation fails the logical operation when cumulative usage across retries and tool loops exceeds the budget.
  • softMaxTokensPerOperation emits an engine event without failing the call.

toolResultFiltering(settings: ToolResultFilteringSettings)

Bounds the aggregate text a tool result may inject.

toolResultFiltering(
    ToolResultFilteringSettings(
        defaultMaxAggregateTextLength = 100_000,
        maxAggregateTextLengthByTool = mapOf("vendor_lookup" to 8_000),
    ),
)

Security And Governance

dlp(interceptor: DlpInterceptor)

Applies DLP to model output before the observer, parser, and caller can see it.

dlpRedactionAudit(emitter: DlpRedactionAuditEmitter)

Receives authoritative DLP redaction evidence. Defaults to a no-op.

promptSanitizer(promptSanitizer: PromptSanitizer?)

Sanitizes user-supplied operation arguments before prompt construction. Passing null removes a configured sanitizer.

policyEngine(engine: PolicyEngine)

Configures explicit policy enforcement. When unset, the engine uses LegacyPermissivePolicyEngine (all operations are allowed, with a migration warning logged) — set a real engine for anything governed.

policyDecisionAudit(emitter: PolicyDecisionAuditEmitter)

Emits a hash-chained audit event for every policy evaluation before the decision is enforced. Defaults to a no-op.

modelRegistry(registry: ModelRegistry) / modelRegistrySettings(settings: ModelRegistrySettings)

Configures approved-model registry verification and its enforcement settings (ModelRegistrySettings(enabled = false) by default).

Memory

memory(chatMemory: ChatMemory)

Configures conversational memory for multi-turn operations. When set, the engine injects conversation history into each request and persists the turn afterwards. Pair it with @ConversationId on a method parameter — see Annotations.

memory(MessageWindowChatMemory(maxMessages = 20))

Approval Suspension

These members configure replay-safe approval flows. Continuation store, arguments digester, and gate coordinator must be set together: configuring only some of them makes build() fail with an IllegalStateException.

MemberPurpose
suspendedInvocationStore(store)Suspended invocation metadata and sensitive context; defaults to the engine's in-memory store
approvalContinuationStore(store)Persistent tool arguments and binding metadata for continuations
toolArgumentsDigester(digester)Computes the deterministic hash bound into the approval challenge
approvalGateCoordinator(coordinator)Creates and authorizes approval requests
approvalLifecycleAudit(emitter)Audit emitter for approval lifecycle events (defaults to no-op)
clock(clock: java.time.Clock)Clock used for approval expiry and audit timestamps (default Clock.systemUTC())

Build And Create

build()

Builds the immutable Tramai instance from the current builder state. The Kotlin DSL helper calls it for you.

Tramai.create<T>() / create(serviceType)

Creates the AI-backed proxy for a service interface. The reified overload is the Kotlin convenience form.

val analyzer = tramai.create<InvoiceAnalyzer>()

Tramai.runtime()

Returns the TramaiRuntime for this instance — the object that owns the engine when you need approval-resume operations in addition to service creation.

TramaiRuntime

TramaiRuntime owns exactly one engine and delegates AutoCloseable to it. Obtain it with tramai.runtime() (or SovereignTramai.runtime()).

MemberPurpose
create(serviceType) / create<T>()Creates a service proxy
registerService(serviceType) / registerService<T>()Registers a service type without creating a proxy, so its operations can be resumed after a restart (idempotent; conflicting registration fails closed)
suspend resumeApproval(command: ResumeApprovalCommand): Any?Resumes an approval-suspended tool execution
suspend resumeApprovalTyped<R>(command)Typed convenience overload
close()Closes the engine
tramai.runtime().use { runtime ->
    runtime.registerService<InvoiceIntelligenceService>()
    val result: InvoiceAssessment = runtime.resumeApprovalTyped(command)
}

First Good Default

Most standalone applications should start exactly here:

val tramai = Tramai {
    provider(OpenAiProvider(System.getenv("OPENAI_API_KEY")), name = "openai", default = true)
    model("gpt-4o", "openai")
}

Then add, in this order as the need appears: structured output, tests, observability, fallback routing, token budgets, policy enforcement.