TramAI - governed AI workflows for Java and Kotlin

Module Architecture

TramAI 0.6.0 replaced informal module lists and hand-maintained dependency trees with two machine-readable authorities that are verified on every build. This page is for contributors and architects: it explains the layer model, the catalog fields, the boundary rules, and how a violation is caught.

If you only need to know which module to add to your application, read Module Design instead.

The Two Authorities

AuthorityFileWhat it owns
Module catalogconfig/quality/module-catalog.yml (schema v3)Every Gradle project exactly once, with maturity, visibility, owner, dependency policy, layer, publishability, API stability, release inclusion, and rationale
Module boundariesconfig/quality/module-boundaries.yml (schema v1)Forbidden dependency edges, plus the exact knownAllowedEdges exceptions to them

Project-setting ↔ catalog equality is verified by verifyModuleManifest; the generated module matrix (docs/reference/module-matrix.md) is checked for drift by verifyModuleMatrixDrift. Neither file is a document you edit to describe reality after the fact — both are inputs the build reads.

Not an authority: docs/architecture/module-dependency-graph.md is a v0.5.0-era snapshot (48 modules). Its generator stamps every output with the v0.5.0 baseline identity, so a regeneration cannot produce a current document. Treat the catalog and the boundaries file as the current policy, and verify060Architecture as the current verified topology.

The Ten Layers

The catalog assigns every module to exactly one layer. The module count in 0.6.0 is 60 Gradle projects:

LayerResponsibilityModules
core-contractsAnnotations, shared contracts, request/response models, exceptions. Near zero-dependency.tramai-core, tramai-bom
runtime-executionEngine, structured output, workflow orchestration, standalone runtime.tramai-engine, tramai-structured, tramai-orchestration, tramai-standalone
governance-securityPolicy, approval, audit, evidence, sovereign runtime profile.tramai-security, tramai-sovereign
persistenceStorage implementations for sovereign state.tramai-persistence-file, tramai-persistence-jdbc
provider-adaptersVendor providers behind the provider SPI.tramai-openai, tramai-anthropic, tramai-ollama, tramai-azure-openai, tramai-bedrock, tramai-gemini, tramai-deepseek
framework-integrationsSpring core, starters, provider starters, secrets.18 tramai-spring* modules
operations-observabilityOTel, platform wiring, and the server/MCP/dashboard surfaces.tramai-observability, tramai-platform, tramai-server, tramai-mcp, tramai-dashboard*
higher-capabilitiesMemory, RAG, embeddings, scheduling, vector stores.tramai-memory, tramai-memory-store*, tramai-rag, tramai-embedding, tramai-scheduler, tramai-vectorstore-spi, tramai-vectorstore-chroma, tramai-vectorstore-pgvector
applications-examplesExecutable examples. Excluded from the release.9 examples:* projects
testing-supportTCKs, fakes, consumer boundary tests.tramai-testing, tramai-spring-consumer-boundary, tramai-spring-consumer-selective

* internal / not published in 0.6.0. Publishability per module comes from the catalog — see the Modules Catalog for the consumer-facing view.

Conceptual Dependency Direction

The layer order is orientation, not a strict enforced hierarchy. Exact policy lives in each module's dependencyPolicy and in the boundaries file:

core-contracts
      ↓
runtime-execution
      ↓
governance-security / persistence / provider-adapters
      ↓
framework-integrations
      ↓
operations-observability / higher-capabilities
      ↓
applications-examples

testing-support → supports contracts without entering the runtime dependency flow

Dependency Policies

Each catalog entry names a dependencyPolicy, and each policy declares the layers a module may depend on:

PolicyAllowed layers
corecore-contracts, testing-support
runtimecore-contracts, runtime-execution, governance-security, testing-support
provider-adaptercore-contracts, runtime-execution, provider-adapters, testing-support
frameworkcore-contracts, runtime-execution, governance-security, persistence, provider-adapters, framework-integrations, operations-observability, higher-capabilities, testing-support
testingcore-contracts, framework-integrations, testing-support
example, bomAll layers

runtime allows governance-security because tramai-engine depends on tramai-security; framework allows operations-observability and higher-capabilities because the platform/server/RAG/vectorstore modules live there. The policy check runs in addition to the forbidden-edge check.

Key Boundary Rules

These rules are enforced by module-boundaries.yml and verified against the resolved dependency graph. New cycles and forbidden edges fail the gate:

  • tramai-core must stay as close to zero-dependency as practical.
  • tramai-engine depends on tramai-core and owns orchestration and retry policy.
  • tramai-structured owns schema generation, extraction, deserialization, and structured failure analysis.
  • tramai-observability is optional and must remain decoupled from the core happy path.
  • Provider modules plug into the engine through the provider SPI; they must not implement retry, fallback, or circuit-breaker logic.
  • tramai-standalone composes the minimal runtime for non-framework users.
  • Framework adapters such as tramai-spring-core are thin integration layers, not alternate runtimes.
  • tramai-orchestration owns workflow execution semantics and persistence boundaries.
  • tramai-scheduler sits above orchestration and must not backflow scheduling concerns into the engine.
  • tramai-server sits above orchestration and the scheduler, exposing operational APIs rather than redefining workflow semantics.
  • tramai-mcp is an adapter, not a second orchestration engine.
  • tramai-platform owns tenancy, governance, and plugin/runtime policy above the server layer.
  • tramai-dashboard is a UI packaging module and must remain optional at runtime.

On top of those rules, the boundaries file hard-codes these classes of forbidden edge:

# core-contracts may not depend on any implementation layer
- fromLayer: core-contracts
  toLayer: provider-adapters
  reason: "Core contracts must not depend on specific provider implementations"

# published modules may not depend on internal or excluded modules
- fromPublished: true
  toPublishability: internal
  reason: "Published modules must not depend on internal modules (consumers can't resolve them)"

Exceptions are exact (fromModule, toModule) pairs in knownAllowedEdges with a written reason — for example :tramai-platform → :tramai-server, and the BOM's inclusion references. There is no second exception mechanism: if you believe you need a new edge, add it there with a rationale or change the design.

How It Is Enforced

./gradlew verify060Architecture

The gate aggregates existing verifiers rather than reimplementing them, and writes a deterministic machine-readable report:

Check idWhat it proves
module-manifestCatalog ↔ Gradle project equality, catalog validity
publishing-topologyBOM membership and publishability derivation
dependency-boundariesForbidden layer edges and dependency-policy violations
dependency-cyclesNo new dependency cycle
global-stateNo new process-global mutable state finding
api-architecturePublic API dumps, compatibility policy, stability inversion
protocol-catalogNo stable runtime protocol contract removed
cancellation-safetyNo new cancellation finding, no worsened risk
provider-contractsProvider TCK enrollment guards
store-contractsStore TCK enrollment guards

The report lands at build/reports/tramai/architecture/architecture-report.json and is written before the task throws, so a failing run still leaves evidence. Two consecutive runs must produce a byte-identical report. The gate also fails closed when its evidence sources are unavailable: an unreadable baseline or a dependency-resolution failure fails every baseline-backed check rather than silently passing.

Structural metrics — file size, constructor parameter count, global mutable state, nondeterminism sources, broad cancellation catches — are held to a committed baseline (config/quality/0.6.0-baseline.json) with any accepted deviation recorded in config/quality/maintainability-deviations.yml as an MQ-#### entry carrying a measured baseline, a tolerated ceiling, a reason, an owner, and a target phase. A deviation ceiling may only be raised deliberately, with the measurement to justify it.

Enrolling a New Module

Adding a module is a catalog change first, code second:

  1. Add the project to the handwritten include(...) list in settings.gradle.kts. The settings list is deliberately not generated; verifyModuleManifest enforces exact-set equality between settings and the catalog in both directions.
  2. Add one - path: ":tramai-x" entry to config/quality/module-catalog.yml with layer, maturity, visibility, owner, dependencyPolicy, publishability, apiStability, releaseInclusion, a non-blank rationale, and a description. The catalog parser rejects invalid combinations outright (internal publishability with public visibility, stable API stability with experimental maturity, included release with excluded or internal publishability, blank owner).
  3. Keep every edge you add inside your policy's allowed layers, and inside the forbidden-edge rules. If an edge is genuinely required, add the exact pair to config/quality/module-boundaries.yml#knownAllowedEdges with a reason.
  4. Regenerate the module matrix (the generator task derives it from the catalog) so verifyModuleMatrixDrift stays green.
  5. Add a module card under docs/modules/ — coverage against the catalog inventory is gated by verifyModuleDocContract.
  6. If the module is published, capture its public API dump so apiCheck has a signature to freeze. See API and Binary Compatibility.
  7. Run ./gradlew verify060Architecture and ./gradlew verifyPr.

BOM membership and the publishable project set are both derived from the catalog (publishability == published, and published && releaseInclusion == included for the BOM), so you do not hand-maintain a second list of coordinates — but verifyModuleManifest does compare the derived sets against the real BOM constraints and fails on drift.