Architecture Overview
Module Dependency Graph
Thirteen runtime modules on one lockstep version. Their Maven runtime dependencies form four layers, plus a BOM that pins the runtime artifacts.
ASCII fallback (for terminal / plain-text readers)
FOUNDATION
tnsai-core Agent · Role · Action · Events · SPI · ToolMethodRegistry
(no inter-module dependencies)
│
▼
CORE-BACKED — depend on tnsai-core and no other runtime module
tnsai-llm LLMClient impls for 31 providers · prompt caching
tnsai-coordination Group topologies · council · voting
tnsai-mcp Model Context Protocol client + server
tnsai-channels Telegram · CLI · Email · Slack · Discord · WhatsApp
adapters via adapter SPI
tnsai-payments x402 payments · wallet SPI · liability records
tnsai-integration SCOPBridge + framework adapters
tnsai-rag Retrieval strategies · document chunking ·
content extractors
│
▼
COMPOSED — add one or more runtime module dependencies beyond core
tnsai-intelligence → core, coordination (runtime scope)
Planning · reasoning · context
tnsai-quality → core, llm
Observability · security enforcement
tnsai-evaluation → core, quality
Benchmarks · quality gates · evaluators
tnsai-tools → core, quality, mcp
63 POJO toolkits · 209 @Tool methods · 29 categories
│
▼
APPLICATION
tnsai-server → core, llm, coordination (runtime scope), quality, rag
WebSocket backend · RAG service · tool execution
META
tnsai-bom Bill of Materials — pins all 13 runtime modules
to one versionCore Concepts
Agent Lifecycle
ASCII fallback (for terminal / plain-text readers)
User Message
│
▼
agent.chat(message)
│
├── Memory append to conversation history
├── System Prompt identity + roles + invariants + state
└── LLM call request with tool definitions
│
▼
LLM Response
│
┌───────────────┴───────────────┐
│ │
▼ ▼
Text reply Tool Call
│ │
│ ▼
│ ActionExecutor
│ │
│ │ ActionType:
│ ├── LOCAL Java method on Role
│ ├── WEB_SERVICE HTTP / REST endpoint
│ ├── LLM LLM dispatch via ToolMethodDispatcher
│ └── MCP_TOOL Model Context Protocol tool
│ │
│ ▼
│ Tool Result
│ │
│ ▼
│ back to LLM (multi-turn loop)
│
▼
return to userAction Routing
Actions are discovered from Roles via @ActionSpec annotations. The ActionExecutor routes each action to the correct executor based on ActionType:
| Type | Source | Example |
|---|---|---|
LOCAL | Java method on Role | @ActionSpec(type = ActionType.LOCAL) String greet(String name) |
WEB_SERVICE | HTTP / REST endpoint | @ActionSpec(type = ActionType.WEB_SERVICE) + @WebService(...) |
LLM | LLM dispatch using the agent's ToolMethodDispatcher | @ActionSpec(type = ActionType.LLM) + agent-level .builtInTools(...) / .toolPojos(...) |
MCP_TOOL | Model Context Protocol server tool | @ActionSpec(type = ActionType.MCP_TOOL) + @MCPTool(serverUrl = "...") |
Which of @ActionSpec, @Tool, and BuiltInTool to reach for is a
separate decision. See Actions vs Tools.
Extension Points (SPI)
TnsAI uses Java's ServiceLoader pattern for modular extensibility.
TnsAI 0.14.0 (TnsAI@b021c635, TAN-2903) caches most provider
lookups through com.tnsai.spi.SpiLoader. See
SPI Reference. 0.14.1 uses
that cached loader, while 0.13.0 called ServiceLoader.load() on every lookup.
| SPI Interface | Module | Purpose |
|---|---|---|
LLMClientProvider | Core | Register LLM providers |
CheckpointerProvider | Core | State persistence |
PlannerHandle.Factory | Intelligence | Planning algorithms |
ReasoningStrategyHandle.Factory | Intelligence | Reasoning patterns |
ContextManagerHandle.Factory | Intelligence | Decision tracing |
EvalHandle.Factory | Quality | Evaluation hooks |
SecurityEnforcerHandle.Factory | Quality | Security policies |
Register implementations in META-INF/services/<interface-name>.
Agent Internal Architecture
The Agent facade composes a small set of focused collaborators. Each
owns one slice of behavior, so extending or replacing one piece doesn't
ripple through the whole class. If you're subclassing Agent or
swapping out one of these collaborators via SPI, this is the map.
ASCII fallback (for terminal / plain-text readers)
Agent identity · lifecycle · template methods · facade
│
├── AgentOrchestrator chat · streaming hooks · tool-call loop · KB
│
├── AgentCapabilities planning · reasoning · eval · feedback ·
│ environment · variant · resilience
│
├── AgentCognitiveSupport
│ internal cognitive support (public for visibility)
│
├── AgentHierarchyManager
│ parent / child relationships
│
├── AgentStreamingSupport
│ streaming chat + ChatChunk events
│
├── AgentMessagingHandler
│ inter-agent messaging via communication SPI
│
└── AgentGroupManager group membershipWhat runs on a chat turn
agent.chat(message) uses the conversation history and the agent's tools. On each turn, AgentOrchestrator:
- Fails if the agent has no LLM client, then notifies the before-chat listeners.
- Adds the message to memory, and prunes memory to the token limit when one is set.
- Calls the model with the history, the tools and any retrieved knowledge. When a reasoning strategy is enabled, it runs that strategy instead.
- Runs the tools if the reply asks for them. Otherwise it stores the reply in memory.
- Notifies the after-chat listeners and records a decision trace when a context graph is on. A failed turn is traced too.
For the streaming and event-based variants of a turn, see Streaming.
Design Principles
- Annotation-first:
@ActionSpecand@AgentSpecover programmatic config, and channels areChannelAdapterimplementations - SPI for extensibility: modules register via
META-INF/services/ - Composition over inheritance: Agent delegates to focused managers
- Immutability: records for data,
List.of(),Map.of() - Thread safety:
ConcurrentHashMap,CopyOnWriteArrayList, virtual threads