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 external messaging platforms | @ChannelSpec + @OnMessage |
| When you want to define skills with NL routing | @SkillSpec + @Trigger |
| When you want to build processing pipelines | @Pipeline + @PipelineStep |
| When you want to delegate work to other agents | @Delegate + @Fallback |
| When you want declarative workspace setup | @WorkspaceSpec + @ConfigProperty |
| 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 to restrict access to paired contacts | @RequiresPairing annotation |
| When you want input sanitization or output filtering | @Sanitize + @ContentFilter |
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 → BDI + Gaia metadata
├── @Responsibility → Role responsibilities (nested)
└── @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
@ChannelSpec → Channel adapter definition
├── @OnMessage → Message handler with filtering
├── @OnConnect → Connection established
├── @OnDisconnect → Connection lost
└── @RateLimited → Rate limiting with strategies
@SkillSpec → Skill definition with routing
├── @SlashCommand → Slash command handler
└── @Trigger → NL trigger pattern
@Pipeline → Processing pipeline
├── @PipelineStep → Pipeline stage
├── @Delegate → Agent delegation
└── @Fallback → Fallback handler
@WorkspaceSpec → Declarative workspace
├── @SystemPrompt → System prompt definition/injection
└── @ConfigProperty → Configuration binding
@Traced → Trace span
@Metered → Metrics collection
@QualityGate → Evaluation threshold
@AuditLog → Audit trail
@RequiresPairing → Restrict to approved contacts
@Sanitize → Input sanitization
@ContentFilter → Output filteringUsage
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 BDI model (what the agent knows, wants, and plans to do), Gaia responsibilities (what it is accountable for), and LLM configuration into a single declaration on your role class.
@RoleSpec(
name = "ResearchAgent",
description = "Agent that performs academic research",
version = "1.0.0",
// BDI Model
beliefs = {"research_context", "available_sources"},
desires = {"find_relevant_papers", "synthesize_findings"},
intentions = {"search_databases", "analyze_papers"},
// Gaia Responsibilities
responsibilities = {
@Responsibility(
name = "Paper Search",
description = "Search in academic databases",
actions = {"searchPapers", "filterResults"}
)
},
// 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. Channel & Messaging
Channel annotations let you build adapters for external messaging platforms and route messages declaratively. Use @ChannelSpec to register an adapter class, then @OnMessage, @OnConnect, and @OnDisconnect to handle events. @RateLimited protects any method from excessive calls.
@ChannelSpec (Channel Adapter)
@ChannelSpec marks a class as a channel adapter for SPI-based auto-discovery. It declares the channel name, any required configuration keys, and whether the channel supports bidirectional communication.
Target: TYPE
@ChannelSpec(
name = "telegram",
description = "Telegram Bot API adapter",
requiredConfig = {"bot_token"},
bidirectional = true
)
public class TelegramChannel implements Channel { }@OnMessage (Message Handler)
@OnMessage handles incoming messages with optional filtering by channel, sender, or content pattern. When multiple handlers match, the priority value determines execution order (higher first).
Target: METHOD
@OnMessage(channel = "telegram", contentPattern = "^/help.*")
public void handleHelp(InboundMessage message) {
// Handle help command from Telegram
}
@OnMessage // matches all messages on all channels
public void handleAll(InboundMessage message) {
// Global message handler
}@OnConnect / @OnDisconnect (Connection Lifecycle)
@OnConnect is called when a channel connection is established. @OnDisconnect is called when a connection is lost or closed. Both can filter by channel name.
Target: METHOD
@OnConnect(channel = "telegram")
public void onTelegramReady(String channelId) {
log.info("Telegram connected: {}", channelId);
}
@OnDisconnect(channel = "slack")
public void onSlackDown(String channelId, String reason) {
log.warn("Slack disconnected: {} - {}", channelId, reason);
}@RateLimited (Rate Limiting)
@RateLimited applies rate limiting to a method or all methods of a class. You configure the maximum invocations per time window, an optional key for per-caller limiting, and a strategy for what happens when the limit is exceeded.
Target: METHOD, TYPE
@RateLimited(max = 10, per = TimeUnit.MINUTES)
public void handleMessage(InboundMessage msg) {
// Max 10 calls per minute (global)
}
@RateLimited(
max = 100,
per = TimeUnit.HOURS,
key = "senderId",
onLimit = RateLimited.Strategy.QUEUE
)
public Object executeAction(String action, Map<String, Object> params) {
// 100 calls/hour per sender, excess queued
}11. Skills & Commands
Skill annotations define self-contained units of functionality that can be activated by slash commands or natural language triggers. Use @SkillSpec on a class to register a skill, then annotate handler methods with @SlashCommand or @Trigger for routing.
@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 { }@SlashCommand (Slash Command Handler)
@SlashCommand registers a method as a handler for a specific slash command. The command string must include the leading slash. Set hidden = true to exclude it from help listings.
Target: METHOD
@SlashCommand("/tools")
public String listTools() {
return toolRegistry.describe();
}
@SlashCommand(value = "/model", description = "Switch LLM model")
public String switchModel(String modelName) {
// Switch the active model
}
@SlashCommand(value = "/debug", description = "Debug internals", hidden = true)
public String debug() {
return agent.dumpState();
}@Trigger (Natural Language Trigger)
@Trigger defines a natural language pattern that activates a method. Patterns use {placeholder} syntax for argument extraction, or full regex when regex = true. The confidence threshold controls how strictly NL matching is applied.
Target: METHOD
@Trigger("remind me to {task} at {time}")
public void setReminder(String task, String time) {
// Extracted from: "remind me to buy milk at 5pm"
}
@Trigger(value = "translate .+ to \\w+", regex = true, confidence = 0.8)
public String translate(String input) {
// Regex-based trigger with higher confidence threshold
}12. Pipeline & Orchestration
Pipeline annotations let you define multi-step processing flows and delegate work to other agents. @Pipeline and @PipelineStep define ordered stages on a class. @Delegate forwards execution to another agent. @Fallback provides a recovery handler when things fail.
@Pipeline (Pipeline Definition)
@Pipeline defines a processing pipeline on a class. Methods within the class are marked as stages with @PipelineStep. By default, steps execute sequentially; set sequential = false to allow parallelization.
Target: TYPE
@Pipeline(name = "ingest", description = "Message ingestion pipeline")
public class IngestPipeline {
@PipelineStep(order = 1, name = "validate")
public Message validate(Message msg) {
// Validation logic
}
@PipelineStep(order = 2, condition = "message.length() > 0")
public Message enrich(Message msg) {
// Enrichment logic
}
@PipelineStep(order = 3)
public String route(Message msg) {
// Routing logic
}
}@PipelineStep (Pipeline Stage)
@PipelineStep marks a method as a stage within a @Pipeline class. The order value determines execution sequence (lower first). An optional condition expression (SpEL-style) causes the step to be skipped when it evaluates to false.
Target: METHOD
| Parameter | Type | Default | Description |
|---|---|---|---|
| order | int | - | REQUIRED: Execution order (lower first) |
| name | String | "" | Step name for logging (defaults to method name) |
| condition | String | "" | SpEL condition; step skipped when false |
@Delegate (Agent Delegation)
@Delegate forwards execution of a method to another agent. You specify the target agent by ID or name. An optional when condition controls whether delegation occurs. The operation times out after timeoutMs milliseconds.
Target: METHOD
@Delegate(agent = "research-agent", when = "topic == 'academic'")
public String handleResearch(String query) {
// Delegated to research-agent when topic is academic
}
@Delegate(agent = "support-agent", timeoutMs = 60000)
public String handleSupport(String query) {
// Always delegated, 60s timeout
}@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. Workspace & Configuration
Workspace annotations provide declarative setup for agent environments. @WorkspaceSpec ties together channels, tools, and system prompts into a named workspace. @SystemPrompt defines or injects the system prompt. @ConfigProperty binds fields to configuration values.
@WorkspaceSpec (Declarative Workspace)
@WorkspaceSpec configures a complete workspace declaratively. It specifies which channels the workspace listens to, which tools are available, and an optional inline system prompt.
Target: TYPE
@WorkspaceSpec(
name = "support",
channels = {"telegram", "slack"},
systemPrompt = "You are a customer support agent.",
tools = {"search", "ticket-create", "knowledge-base"}
)
public class SupportWorkspace { }@SystemPrompt (System Prompt)
@SystemPrompt defines or injects a system prompt. On a class, it provides a static prompt with optional {placeholder} template variables. On a field, it marks the field for prompt injection. On a method, the return value supplies the prompt dynamically.
Target: TYPE, FIELD, METHOD
// On class — static prompt with template variable
@SystemPrompt("You are a research assistant specializing in {domain}.")
public class ResearchAgent extends Agent { }
// On field — injectable prompt
@SystemPrompt
private String customPrompt;
// On method — dynamic prompt supplier
@SystemPrompt
public String buildPrompt() {
return "Dynamic prompt based on " + currentState();
}@ConfigProperty (Configuration Binding)
@ConfigProperty binds a field to a configuration value by dot-separated key path. If the key is missing at startup and no defaultValue is provided, the application fails to start when required = true.
Target: FIELD
@ConfigProperty("sona.workspace.default.model")
private String defaultModel;
@ConfigProperty(value = "sona.max_tokens", defaultValue = "4096")
private int maxTokens;
@ConfigProperty(value = "sona.debug", defaultValue = "false", required = false)
private boolean debugMode;14. 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
}15. Security & Safety
Security annotations enforce access control, sanitize inputs, and filter outputs. They complement the @Security annotation (section 7) with fine-grained pairing requirements, parameter-level sanitization, and content-aware output filtering.
@RequiresPairing (Access Restriction)
@RequiresPairing restricts access to approved/paired contacts only. Place it on a class to protect all methods, or on individual methods for selective protection. Unapproved callers receive the configured rejection message.
Target: TYPE, METHOD
@RequiresPairing
public class AdminSkill implements Skill {
// All methods require pairing
}
@RequiresPairing(message = "You need to be approved first. Use /pair to request access.")
public void handleSensitiveAction(InboundMessage msg) {
// Custom rejection message for unapproved users
}@Sanitize (Input Sanitization)
@Sanitize sanitizes a method parameter before processing. Rules are applied in order to prevent injection attacks. Place it directly on the parameter you want to sanitize.
Target: PARAMETER
Available rules: TRIM, STRIP_HTML, SHELL_ESCAPE, SQL_ESCAPE, STRIP_CONTROL, NORMALIZE_UNICODE
public void executeShell(@Sanitize(rules = {Rule.SHELL_ESCAPE}) String command) {
// Shell metacharacters escaped
}
public void storeData(@Sanitize(rules = {Rule.STRIP_HTML, Rule.TRIM}) String input) {
// HTML stripped, then trimmed
}
public void processInput(
@Sanitize(rules = {Rule.STRIP_CONTROL, Rule.NORMALIZE_UNICODE, Rule.TRIM}) String text
) {
// Control chars removed, unicode normalized, whitespace trimmed
}@ContentFilter (Output Filtering)
@ContentFilter applies content filtering to a method's output. Filters run in order and can detect PII, toxicity, prompt injection attempts, and factual grounding issues. The onViolation strategy controls whether offending content is redacted, the entire response is blocked, or a warning is logged.
Target: METHOD
@ContentFilter(filters = {Filter.PII_REDACTION})
public String chat(String message) {
// PII automatically redacted from output
}
@ContentFilter(
filters = {Filter.TOXICITY, Filter.PII_REDACTION, Filter.PROMPT_INJECTION},
onViolation = ContentFilter.OnViolation.BLOCK
)
public String generateResponse(String prompt) {
// Response blocked if any filter detects a violation
}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 model, responsibilities, and LLM 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 |
| beliefs | String[] | {} | BDI beliefs |
| desires | String[] | {} | BDI desires |
| intentions | String[] | {} | BDI intentions |
| responsibilities | @Responsibility[] | {} | Responsibilities |
| capabilities | String[] | {} | Role capabilities |
| domains | String[] | {} | Working domains |
| llm | @LLMSpec | @LLMSpec() | LLM configuration |
@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) |
@ChannelSpec
Marks a class as a channel adapter for SPI-based auto-discovery. Placed on channel implementation classes.
| Parameter | Type | Default | Description |
|---|---|---|---|
| name | String | - | REQUIRED: Unique channel name (lowercase) |
| description | String | "" | Human-readable description |
| requiredConfig | String[] | {} | Required config keys for startup validation |
| bidirectional | boolean | true | Supports bidirectional communication |
@OnMessage
Handles incoming messages with optional filtering by channel, sender, or content pattern.
| Parameter | Type | Default | Description |
|---|---|---|---|
| channel | String | "" | Channel name filter (empty = all channels) |
| senderPattern | String | "" | Sender ID regex (empty = all senders) |
| contentPattern | String | "" | Message content regex (empty = all content) |
| priority | int | 0 | Processing priority (higher executes first) |
@OnConnect
Called when a channel connection is established.
| Parameter | Type | Default | Description |
|---|---|---|---|
| channel | String | "" | Channel name filter (empty = all channels) |
@OnDisconnect
Called when a channel connection is lost or closed.
| Parameter | Type | Default | Description |
|---|---|---|---|
| channel | String | "" | Channel name filter (empty = all channels) |
@RateLimited
Applies rate limiting to a method or all methods of a class.
| Parameter | Type | Default | Description |
|---|---|---|---|
| max | int | - | REQUIRED: Max invocations per time window |
| per | TimeUnit | SECONDS | Time unit for the rate window |
| key | String | "" | Key expression for per-caller limiting (empty = global) |
| onLimit | Strategy | REJECT | Overflow strategy: REJECT, QUEUE, or DROP |
@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 |
@SlashCommand
Registers a method as a slash command handler.
| Parameter | Type | Default | Description |
|---|---|---|---|
| value | String | - | REQUIRED: Command string with leading slash |
| description | String | "" | Description shown in command listings |
| hidden | boolean | false | Whether excluded from help/listings |
@Trigger
Defines a natural language trigger pattern for skill activation.
| Parameter | Type | Default | Description |
|---|---|---|---|
| value | String | - | REQUIRED: Trigger pattern (template or regex) |
| regex | boolean | false | Whether pattern is regex (true) or template (false) |
| confidence | double | 0.7 | Minimum NL matching confidence (0.0-1.0) |
@Pipeline
Defines a processing pipeline on a class.
| Parameter | Type | Default | Description |
|---|---|---|---|
| name | String | - | REQUIRED: Pipeline name |
| description | String | "" | Human-readable description |
| sequential | boolean | true | Whether steps execute sequentially |
@PipelineStep
Marks a method as a pipeline stage with execution order.
| Parameter | Type | Default | Description |
|---|---|---|---|
| order | int | - | REQUIRED: Execution order (lower first) |
| name | String | "" | Step name for logging (defaults to method name) |
| condition | String | "" | SpEL condition; step skipped when false |
@Delegate
Delegates execution to another agent with routing rules.
| Parameter | Type | Default | Description |
|---|---|---|---|
| agent | String | - | REQUIRED: Target agent ID or name |
| when | String | "" | Condition expression (empty = always delegate) |
| timeoutMs | long | 30000 | Timeout in milliseconds |
@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 |
@WorkspaceSpec
Declarative workspace definition with channels, tools, and system prompt.
| Parameter | Type | Default | Description |
|---|---|---|---|
| name | String | - | REQUIRED: Workspace name (unique identifier) |
| channels | String[] | {} | Channels this workspace accepts messages from |
| systemPrompt | String | "" | Inline system prompt text |
| tools | String[] | {} | Tool names available in this workspace |
@SystemPrompt
Defines or injects a system prompt. Supports {placeholder} template variables.
| Parameter | Type | Default | Description |
|---|---|---|---|
| value | String | "" | Prompt template text (empty when on field/method) |
@ConfigProperty
Binds a field to a configuration value by key path.
| Parameter | Type | Default | Description |
|---|---|---|---|
| value | String | - | REQUIRED: Config key path (dot-separated) |
| defaultValue | String | "" | Default if key missing (empty = required) |
| required | boolean | true | Fail startup if missing and no default |
@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 |
@RequiresPairing
Restricts access to approved/paired contacts only.
| Parameter | Type | Default | Description |
|---|---|---|---|
| message | String | "Access denied. Pairing required." | Custom rejection message |
@Sanitize
Sanitizes input parameters before processing.
| Parameter | Type | Default | Description |
|---|---|---|---|
| rules | Rule[] | {TRIM} | Sanitization rules applied in order |
Rule enum values: TRIM (trim whitespace), STRIP_HTML (strip HTML tags), SHELL_ESCAPE (escape shell metacharacters), SQL_ESCAPE (escape SQL special characters), STRIP_CONTROL (remove null bytes and control characters), NORMALIZE_UNICODE (normalize to NFC form)
@ContentFilter
Applies content filtering to method output.
| Parameter | Type | Default | Description |
|---|---|---|---|
| filters | Filter[] | - | REQUIRED: Content filters applied in order |
| onViolation | OnViolation | REDACT | Violation action: REDACT, BLOCK, or WARN |
Filter enum values: PII_REDACTION (redact personally identifiable information), TOXICITY (check for toxic/harmful content), PROMPT_INJECTION (detect prompt injection attempts), GROUNDING (ensure factual grounding against sources)
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