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
Tramaicloses 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
Tramaiis immutable: builder state is frozen atbuild(), including the resolved tools and the diagnostic observer snapshot. Mutating a builder afterbuild()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,
),
)
hardMaxTokensPerAttemptfails a single provider attempt when reported usage is too high.hardMaxTokensPerOperationfails the logical operation when cumulative usage across retries and tool loops exceeds the budget.softMaxTokensPerOperationemits 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.
| Member | Purpose |
|---|---|
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()).
| Member | Purpose |
|---|---|
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.
