Semantic Annotation Framework
What Does It Do?
The Semantic Annotation Framework allows you to define TnsAI role and agent metadata using semantically grouped annotations. Annotations are organized by purpose; the framework provides:
- BDI Model support: Beliefs, Desires, Intentions
- Gaia Methodology compatibility: Role, Responsibility definitions
- Framework Portability: JSON, YAML, JASON, JADE export
- Type-Safe Configuration: Type-safe configuration with nested annotations
- Channel & Messaging: Declarative channel adapters and message routing
- Skills & Commands: Slash commands and NL trigger patterns
- Pipeline & Orchestration: Processing pipelines and agent delegation
- Observability & Quality: Tracing, metrics, audit logging, quality gates
- Security & Safety: Pairing, input sanitization, content filtering
When to Use?
| Scenario | Solution |
|---|---|
| When you want to define your agent's knowledge and goals | BDI model with @RoleSpec |
| When you want to make web service calls | @ActionSpec + @WebService nested |
| When you want to use LLM tool calling | @ActionSpec(type = LLM) + agent-level .builtInTools(...) / .toolPojos(...) |
| When you want to call an MCP server tool | @ActionSpec + @MCPTool nested |
| When you want to add Retry/Circuit Breaker | @Resilience annotation |
| When you want multi-agent coordination | @Coordination annotation |
| When you want agent memory management | @Memory annotation |
| When you want to export a role to another framework | Use RoleExporter |
| When you want to bridge an external messaging platform | @Channel adapter |
| When you want to define skills with NL routing | @SkillSpec (its triggers) |
| When you want a recovery handler when an action fails | @Fallback annotation |
| When you want observability (tracing, metrics) | @Traced + @Metered |
| When you want quality evaluation on output | @QualityGate annotation |
| When you want audit logging | @AuditLog annotation |
| When you want input/output guardrails | @InputGuardrail + @OutputGuardrail |
How Does It Work?
Annotation Groups
Annotations in TnsAI are organized into logical groups, each handling a different aspect of agent behavior. Most groups support nested annotations, so you can configure complex behavior in a single, readable declaration.
@RoleSpec → role identity, BDI goals, LLM + comms config
└── @LLMSpec → LLM configuration (nested)
@ActionSpec → Action definition
├── @WebService → HTTP call config (nested, for WEB_SERVICE)
└── @MCPTool → MCP server config (nested, for MCP_TOOL)
(LLM type uses agent-level tool registration via
AgentBuilder.builtInTools() / .toolPojos();
per-action overrides go on @ActionSpec.llmSystemPrompt
and @ActionSpec.llmTemperature)
@Communication → Communication style
├── @Style → Tone, formality (nested)
└── @Messaging → Channels, topics (nested)
@Coordination → Multi-agent coordination
└── @Consensus → Voting mechanism (nested)
@Memory → Memory management
├── @Conversation → Conversation memory (nested)
├── @Vector → Vector memory (nested)
└── @Persistence → Persistence (nested)
@Resilience → Resilience
├── @Retry → Retry policy (nested)
├── @CircuitBreaker → Circuit breaker (nested)
└── @RateLimit → Rate limiting (nested)
@Security → Security
@Contract → Design by Contract
@Lifecycle → Lifecycle callbacks
@Channel → Channel adapter definition
@SkillSpec → Skill definition with NL triggers
@Fallback → Fallback / recovery handler
@Traced → Trace span
@Metered → Metrics collection
@QualityGate → Evaluation threshold
@AuditLog → Audit trailUsage
This section walks through each annotation with concrete examples. You can mix and match these annotations on your role and action classes as needed.
1. Role Definition (@RoleSpec)
@RoleSpec is the main annotation for defining an agent's identity and cognitive model. It brings together the agent's identity, its BDI goals (named targets with satisfaction conditions), and its LLM and communication configuration into a single declaration on your role class.
@RoleSpec(
name = "ResearchAgent",
description = "Agent that performs academic research",
version = "1.0.0",
priority = 5,
domains = {"research", "academia"},
// BDI goals — each a named target with a satisfaction condition
goals = {
@Goal(
name = "find_papers",
condition = "relevant_papers_found",
description = "Locate relevant academic papers"
)
},
// LLM Configuration
llm = @LLMSpec(
provider = Provider.OLLAMA,
model = "llama3",
temperature = 0.7f,
maxTokens = 4096
)
)
public class ResearchRole extends Role {
// ...
}2. Action Definition
Actions are the things your agent can actually do. Each action has a type that determines how it executes -- locally in your JVM, via an HTTP call, through LLM tool calling, or by connecting to an MCP server. You annotate a method with @ActionSpec and specify the type along with any nested configuration it needs.
LOCAL Action (Direct Code)
A LOCAL action runs your own Java code directly. This is the simplest action type -- the method body is the implementation.
@ActionSpec(
type = ActionType.LOCAL,
description = "Calculates the sum of numbers"
)
public double calculateSum(List<Double> numbers) {
return numbers.stream().mapToDouble(d -> d).sum();
}WEB_SERVICE Action (REST API)
A WEB_SERVICE action makes an HTTP request to an external REST API. You configure the endpoint, method, timeout, and authentication in the nested @WebService annotation, and TnsAI handles the HTTP call automatically -- the method body is not executed.
@ActionSpec(
type = ActionType.WEB_SERVICE,
description = "Fetches weather data",
webService = @WebService(
endpoint = "https://api.weather.com/v1/current",
method = HttpMethod.GET,
timeout = 5000,
auth = AuthType.BEARER,
authTokenEnv = "WEATHER_API_KEY"
)
)
public WeatherData getWeather(String city) {
return null; // HTTP call is made automatically
}LLM Action (Tool Calling)
An LLM action delegates work to an LLM with access to the agent's registered tools. The method's return value becomes the prompt sent to the LLM, which can then call any tool the agent was built with. Tools are configured at the agent level via AgentBuilder.builtInTools(...) and .toolPojos(...) — there is no per-action tool list.
Optional per-action overrides on @ActionSpec adjust the LLM call without touching the agent's global config:
@ActionSpec(
type = ActionType.LLM,
description = "Performs research from academic sources",
llmSystemPrompt = "You are a careful, citation-driven research assistant.",
llmTemperature = 0.3f
)
public String researchTopic(String topic) {
return "Please research: " + topic;
}Then register the tools at the agent level:
Agent agent = AgentBuilder.create()
.llm(llmClient)
.role(researchRole)
.builtInTools(BuiltInTool.ACADEMIC_TOOLS, BuiltInTool.WEB_SEARCH_TOOLS)
.build();MCP_TOOL Action (MCP Server)
An MCP_TOOL action calls a tool hosted on a remote MCP (Model Context Protocol) server. You specify the server URL and tool name, and TnsAI handles the protocol communication for you.
@ActionSpec(
type = ActionType.MCP_TOOL,
description = "Fetches cryptocurrency data",
mcpTool = @MCPTool(
serverUrl = "https://mcp.coingecko.com/mcp",
toolName = "get_coin_price",
apiKeyEnv = "COINGECKO_API_KEY"
)
)
public CoinPrice getCoinPrice(String coinId) {
return null; // Called via MCP
}3. Resilience
@Resilience adds fault-tolerance to any action. You can configure automatic retries with exponential backoff, a circuit breaker that stops calling a failing service, timeouts, and a fallback method to call when everything else fails.
@Resilience(
retry = @Retry(
maxAttempts = 3,
backoffMs = 1000,
multiplier = 2.0
),
circuitBreaker = @CircuitBreaker(
enabled = true,
failureThreshold = 5,
resetTimeoutMs = 30000
),
timeout = 10000,
fallback = "fallbackMethod"
)
public Result fetchData(String query) {
// ...
}4. Communication
@Communication controls how an agent expresses itself and interacts with channels. You can set the tone, formality level, verbosity, language, response format, and which messaging channels/topics the agent participates in.
@Communication(
style = @Style(
tone = Tone.PROFESSIONAL,
formality = Formality.FORMAL,
verbosity = Verbosity.CONCISE
),
messaging = @Messaging(
channels = {"research-updates"},
topics = {"papers", "findings"}
),
language = "tr",
responseFormat = ResponseFormat.MARKDOWN
)
public class ResearchRole extends Role { }5. Coordination
@Coordination sets up multi-agent teamwork. One agent acts as the orchestrator (assigning tasks, gathering results), while others are workers with specific capabilities. You can also define a consensus strategy for decisions that require agreement among agents.
// Orchestrator Agent
@Coordination(
role = CoordinationRole.ORCHESTRATOR,
workers = {"analyst-1", "analyst-2", "writer-1"},
consensus = @Consensus(
strategy = Strategy.MAJORITY,
threshold = 0.6
),
taskDistribution = TaskDistribution.CAPABILITY_BASED
)
public class TeamLeaderRole extends Role { }
// Worker Agent
@Coordination(
role = CoordinationRole.WORKER,
orchestrator = "team-leader",
capabilities = {"analysis", "summarization"}
)
public class AnalystRole extends Role { }6. Memory
@Memory configures how an agent retains information across interactions. You can enable conversation memory (recent message history), vector memory (semantic search over past knowledge), and persistence (saving state across restarts).
@Memory(
conversation = @Conversation(
enabled = true,
maxMessages = 100,
strategy = Strategy.SLIDING_WINDOW
),
vector = @Vector(
enabled = true,
dimensions = 1536,
provider = "pinecone",
topK = 5
),
persistent = true,
namespace = "research-agent"
)
public class ResearchRole extends Role { }7. Security
@Security protects sensitive actions by requiring user approval, enforcing permissions, and enabling audit logging. Use it on any action that modifies critical data or accesses restricted resources.
@Security(
approvalRequired = true,
approvalReason = "Critical data deletion operation",
audit = AuditLevel.DETAILED,
sensitive = true,
requiredPermissions = {"admin", "data-manager"}
)
@ActionSpec(type = ActionType.LOCAL)
public void deleteUserData(String userId) {
// ...
}8. Contract (Design by Contract)
@Contract enforces preconditions, postconditions, and invariants on an action method. Preconditions are checked before execution, postconditions after, and invariants must hold at all times. This catches bugs early by validating assumptions at runtime.
@Contract(
preconditions = {
"userId != null",
"userId.length() > 0"
},
postconditions = {
"result != null",
"result.isValid()"
},
invariants = {
"this.connectionPool.isHealthy()"
}
)
@ActionSpec(type = ActionType.LOCAL)
public UserData fetchUser(String userId) {
// ...
}9. Lifecycle
@Lifecycle lets you hook into an agent's startup, shutdown, and error-handling phases. Annotate methods with the appropriate phase, and TnsAI calls them at the right time. Use the order parameter to control execution order when multiple methods share the same phase.
public class ResearchAgent extends Agent {
@Lifecycle(phase = Phase.INIT)
public void loadResources() {
// Load resources
}
@Lifecycle(phase = Phase.START, order = 1)
public void connectToDatabase() {
// Connect to database
}
@Lifecycle(phase = Phase.STOP)
public void cleanup() {
// Release resources
}
@Lifecycle(phase = Phase.ERROR)
public void handleError(Throwable error) {
// Error handling
}
}10. Channels
@Channel marks a class as a channel adapter for SPI-based auto-discovery, bridging an external surface (CLI, Discord, …) to the agent runtime. (Rate limiting is configured with @Resilience(rateLimit = @RateLimit(...)) — see section 3.)
@Channel (Channel Adapter)
@Channel declares a channel adapter's stable id and an optional human-readable displayName. The annotated class implements the channel SPI; the runtime discovers it via META-INF/services and wires its message I/O.
Target: TYPE
@Channel(id = "discord", displayName = "Discord")
public final class DiscordChannel {
// implements the channel SPI: connect / receive / send
}11. Skills & Commands
Skills are self-contained units of functionality matched by natural-language triggers. Use @SkillSpec on a class to register a skill along with its routing keywords; the matching is driven by the annotation's triggers attribute.
@SkillSpec (Skill Definition)
@SkillSpec marks a class as a skill with metadata for routing and discovery. The triggers array provides keywords for natural language matching, and priority controls selection when multiple skills match.
Target: TYPE
@SkillSpec(
name = "weather",
description = "Check weather for any location",
triggers = {"weather", "forecast", "temperature"},
priority = 10,
requiresSession = true
)
public class WeatherSkill implements Skill { }12. Fallback & Recovery
@Fallback designates a recovery handler invoked when a primary action fails.
@Fallback (Fallback Handler)
@Fallback designates a method as the recovery handler when the primary action fails. Use forAction to target a specific action, or leave it empty for a global fallback. The maxRetries parameter controls how many attempts are made before the fallback is invoked.
Target: METHOD
@Fallback(forAction = "searchWeb")
public String searchFallback(String query, Exception error) {
return "Search unavailable: " + error.getMessage();
}
@Fallback(maxRetries = 3) // global fallback, retries 3 times first
public String globalFallback(String input, Exception error) {
return "Something went wrong. Please try again.";
}13. Observability & Quality
Observability annotations add tracing, metrics, quality evaluation, and audit logging to methods without changing business logic. They integrate with TnsAI's SPI-based observability backend.
@Traced (Trace Span)
@Traced adds trace ID propagation and structured logging to a method. Each invocation creates a trace span with optional argument and result capture. The operation name defaults to class.method if not specified.
Target: METHOD
@Traced
public String chat(String message) {
// Automatic trace span: "MyAgent.chat"
}
@Traced(operationName = "tool-execution", includeArgs = true, includeResult = true)
public Object executeTool(String name, Map<String, Object> params) {
// Custom span name, args and result logged
}@Metered (Metrics Collection)
@Metered collects latency, call count, and error rate metrics for a method or all methods of a class. You can add custom tags and control whether a latency histogram is recorded.
Target: METHOD, TYPE
@Metered
public String chat(String message) {
// Automatic metrics: latency, count, error rate
}
@Metered(name = "tool.execution", tags = {"module=core"}, histogram = true)
public Object executeTool(String name) {
// Custom metric name with tags
}@QualityGate (Evaluation Threshold)
@QualityGate defines a minimum quality score for a method's output. The evaluator (resolved via SPI) scores the result, and the onFail strategy determines what happens when the score is below the threshold.
Target: METHOD
@QualityGate(minScore = 0.8, evaluator = "relevance")
public String chat(String message) {
// Logs warning if relevance < 0.8
}
@QualityGate(
minScore = 0.9,
evaluator = "factuality",
onFail = QualityGate.OnFail.RETRY,
maxRetries = 3
)
public String answerQuestion(String question) {
// Retries up to 3 times if factuality < 0.9
}@AuditLog (Audit Trail)
@AuditLog records method invocations to an audit trail. Each entry includes the action name, timestamp, caller identity, and optionally the method arguments and return value.
Target: METHOD
@AuditLog(action = "user.approve")
public void approveUser(String userId) {
// Audit entry: "user.approve" with timestamp
}
@AuditLog(action = "config.change", includeArgs = true, includeResult = true)
public void updateConfig(String key, String value) {
// Audit entry includes args (key, value) and result
}Export System
TnsAI can export your annotated role definitions to other agent frameworks and formats. This is useful for interoperability -- you define your agent once using TnsAI annotations, then export it to whichever target platform you need.
JSON Export
Exports the role as a JSON document. You can get it as a string or write it directly to a file.
RoleExporter exporter = new JsonRoleExporter();
String json = exporter.export(myRole);
exporter.exportToFile(myRole, Path.of("role.json"));YAML Export
Exports the role as YAML, which is often easier to read and edit by hand than JSON.
RoleExporter exporter = new YamlRoleExporter();
String yaml = exporter.export(myRole);JASON Export (BDI)
Exports the role as AgentSpeak code for the JASON BDI interpreter. This lets you run your TnsAI-defined agent logic in a traditional BDI execution environment.
RoleExporter exporter = new JasonExporter();
String asl = exporter.export(myRole);
// Generates AgentSpeak codeJADE Export
Exports the role as a JADE agent descriptor XML file. Use this when you need to deploy your agent in a JADE multi-agent platform.
RoleExporter exporter = new JadeExporter();
String xml = exporter.export(myRole);
// Generates JADE agent descriptorAPI Reference
Complete parameter reference for every annotation in the framework. Use these tables when you need to know the exact parameter names, types, and defaults.
@RoleSpec
Defines the agent's identity, BDI goals, and LLM + communication settings. This is the top-level annotation you place on a role class.
| Parameter | Type | Default | Description |
|---|---|---|---|
| name | String | "" | Role name |
| description | String | "" | Description |
| version | String | "1.0.0" | Version |
| priority | int | 5 | Selection priority |
| shared | boolean | true | Whether the role instance is shared |
| domains | String[] | {} | Working domains |
| goals | @Goal[] | {} | BDI goals (named target + satisfaction condition) |
| llm | @LLMSpec | @LLMSpec() | LLM configuration (nested) |
| communication | @Communication | @Communication() | Communication style (nested) |
| requires | LLMCapability[] | {} | Required LLM capabilities |
@LLMSpec
Configures which LLM provider and model the agent uses, along with generation parameters. This is nested inside @RoleSpec.
| Parameter | Type | Default | Description |
|---|---|---|---|
| provider | Provider (enum) | Provider.OLLAMA | LLM provider — enum: OLLAMA, OPENAI, ANTHROPIC, AZURE_OPENAI, etc. |
| model | String | "" | Model name (gpt-4o, claude-3-opus, llama3) |
| temperature | float | 0.7f | Creativity (0.0-2.0) |
| maxTokens | int | 4096 | Max token count |
| topP | float | 1.0f | Nucleus sampling |
| systemPrompt | String | "" | Custom system prompt |
@ActionSpec
Marks a method as an executable action. The type parameter is required and determines how the action runs. Depending on the type, you provide a corresponding nested annotation for configuration.
| Parameter | Type | Default | Description |
|---|---|---|---|
| type | ActionType | - | REQUIRED: LOCAL, WEB_SERVICE, LLM, MCP_TOOL |
| description | String | "" | Description |
| excludeFromLLM | boolean | false | If true, hidden from the LLM tool list (programmatic use only) |
| webService | @WebService | - | WEB_SERVICE config (nested) |
| mcpTool | @MCPTool | - | MCP_TOOL config (nested) |
| llmSystemPrompt | String | "" | LLM-action-only: per-action system prompt override (empty = use LLM client default) |
| llmTemperature | float | -1.0f | LLM-action-only: per-action temperature override (negative = use LLM client default) |
@WebService
Configures HTTP call details for WEB_SERVICE actions. Nested inside @ActionSpec.
| Parameter | Type | Default | Description |
|---|---|---|---|
| endpoint | String | - | HTTP endpoint URL |
| method | HttpMethod | GET | HTTP method |
| timeout | int | 30000 | Timeout (ms) |
| auth | AuthType | NO_AUTH | Authentication type |
| authTokenEnv | String | "" | Bearer token env var |
@MCPTool
Configures the connection to an MCP server for MCP_TOOL actions. Nested inside @ActionSpec.
| Parameter | Type | Default | Description |
|---|---|---|---|
| serverUrl | String | - | MCP server URL |
| toolName | String | "" | Tool name |
| apiKeyEnv | String | "" | API key env var |
| timeout | int | 30000 | Timeout (ms) |
@Channel
Marks a class as a channel adapter for SPI-based auto-discovery (META-INF/services). Placed on channel implementation classes.
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | String | - | REQUIRED: stable channel identifier |
| displayName | String | "" | Human-readable channel name |
@SkillSpec
Marks a class as a skill with metadata for routing and discovery.
| Parameter | Type | Default | Description |
|---|---|---|---|
| name | String | - | REQUIRED: Unique skill name (lowercase) |
| description | String | "" | Description for skill selection by router |
| triggers | String[] | {} | Keywords for NL routing |
| priority | int | 0 | Selection priority (higher takes precedence) |
| requiresSession | boolean | true | Whether an active session is required |
@Fallback
Fallback handler invoked when the primary action fails.
| Parameter | Type | Default | Description |
|---|---|---|---|
| forAction | String | "" | Action name to cover (empty = global fallback) |
| maxRetries | int | 0 | Max retries before invoking fallback |
@Traced
Adds trace ID propagation and structured logging to a method.
| Parameter | Type | Default | Description |
|---|---|---|---|
| operationName | String | "" | Custom span name (defaults to class.method) |
| includeArgs | boolean | false | Log method arguments in span |
| includeResult | boolean | false | Log return value in span |
@Metered
Collects latency, call count, and error rate metrics.
| Parameter | Type | Default | Description |
|---|---|---|---|
| name | String | "" | Custom metric name (defaults to class.method) |
| tags | String[] | {} | Static tags in key=value format |
| histogram | boolean | true | Record latency histogram |
@QualityGate
Defines an evaluation quality threshold for method output.
| Parameter | Type | Default | Description |
|---|---|---|---|
| minScore | double | - | REQUIRED: Minimum score (0.0-1.0) |
| evaluator | String | - | REQUIRED: Evaluator name (resolved via SPI) |
| onFail | OnFail | LOG | Failure action: LOG, RETRY, or REJECT |
| maxRetries | int | 2 | Max retries when onFail = RETRY |
@AuditLog
Records method invocations to an audit trail.
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | String | - | REQUIRED: Action name for audit entry |
| includeArgs | boolean | false | Log method arguments |
| includeResult | boolean | false | Log return value |
Things to Note
These are common pitfalls to avoid when using the annotation framework, especially around how action types and nested annotations must match.
Correct Usage
Always use the nested annotation that matches your action type. Configuration details go inside the nested annotation, not on @ActionSpec itself.
// CORRECT: Using nested annotations
@ActionSpec(
type = ActionType.WEB_SERVICE,
webService = @WebService(endpoint = "https://api.example.com")
)Incorrect Usage
Do not put configuration properties directly on @ActionSpec -- this flat style is not supported and will cause a compile error.
// INCORRECT: Using flat properties (old style, no longer supported)
@ActionSpec(
type = ActionType.WEB_SERVICE,
endpoint = "https://api.example.com" // ERROR!
)Type Compatibility
Each action type uses different @ActionSpec fields:
LOCAL→ No nested annotation; the method body runs.WEB_SERVICE→ Use the nested@WebService(...)for endpoint, method, auth, etc.LLM→ No nested annotation; tool exposure is configured at the agent level viaAgentBuilder.builtInTools(...)/.toolPojos(...). Optional per-action overrides go on@ActionSpec.llmSystemPromptand@ActionSpec.llmTemperature.MCP_TOOL→ Use the nested@MCPTool(serverUrl = ..., toolName = ..., ...)for server connection.
Related Topics
For deeper coverage of individual features mentioned in this guide, see these pages.
Annotations Reference
TnsAI ships its declarative annotation surface grouped by purpose — BDI (Belief-Desire-Intention), Gaia (roles + environment), channels, pipelines, security, observability, and core dispatch. The full catalog below documents every annotation, its fields, and example usage. Counts drift between releases; the catalog is the authoritative list.
Tool Catalog Reference
Next Page