Semantic Annotation Framework
What Does It Do?
The Semantic Annotation Framework allows you to define TnsAI role and agent metadata using semantically grouped annotations. This 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
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 | @Action + @WebService nested |
| When you want to use LLM tool calling | @Action + @LLMTool nested |
| When you want to call an MCP server tool | @Action + @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 |
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)
└── @LLMConfig → LLM configuration (nested)
@Action → Action definition
├── @WebService → HTTP call config (nested)
├── @LLMTool → Tool calling config (nested)
└── @MCPTool → MCP server config (nested)
@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 callbacksUsage
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 = @LLMConfig(
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 @Action 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.
@Action(
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.
@Action(
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_TOOL Action (Tool Calling)
An LLM_TOOL action delegates work to an LLM with access to built-in or custom tools. The method's return value becomes the prompt sent to the LLM, which then uses the specified tools to complete the task.
@Action(
type = ActionType.LLM_TOOL,
description = "Performs research from academic sources",
llmTool = @LLMTool(
tools = {BuiltInTool.ARXIV, BuiltInTool.GOOGLE_SCHOLAR},
maxToolCalls = 5,
parallelToolCalls = true,
temperature = 0.3f
)
)
public String researchTopic(String topic) {
return "Please research: " + topic;
}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.
@Action(
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"}
)
@Action(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()"
}
)
@Action(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
}
}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 | @LLMConfig | @LLMConfig() | LLM configuration |
@LLMConfig
Configures which LLM provider and model the agent uses, along with generation parameters. This is nested inside @RoleSpec.
| Parameter | Type | Default | Description |
|---|---|---|---|
| provider | String | "ollama" | LLM provider (openai, anthropic, ollama, groq, gemini) |
| 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 |
@Action
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_TOOL, MCP_TOOL |
| description | String | "" | Description |
| internal | boolean | false | Visible to LLM? |
| webService | @WebService | - | WEB_SERVICE config |
| llmTool | @LLMTool | - | LLM_TOOL config |
| mcpTool | @MCPTool | - | MCP_TOOL config |
@WebService
Configures HTTP call details for WEB_SERVICE actions. Nested inside @Action.
| 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 |
@LLMTool
Configures tool calling for LLM_TOOL actions, including which tools the LLM can use and how many calls it can make. Nested inside @Action.
| Parameter | Type | Default | Description |
|---|---|---|---|
| tools | BuiltInTool[] | {} | Built-in tools |
| customTools | String[] | {} | Custom tool names |
| maxToolCalls | int | 10 | Max tool calls |
| parallelToolCalls | boolean | false | Parallel calls |
| temperature | float | -1.0 | LLM temperature |
@MCPTool
Configures the connection to an MCP server for MCP_TOOL actions. Nested inside @Action.
| Parameter | Type | Default | Description |
|---|---|---|---|
| serverUrl | String | - | MCP server URL |
| toolName | String | "" | Tool name |
| apiKeyEnv | String | "" | API key env var |
| timeout | int | 30000 | Timeout (ms) |
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 @Action itself.
// CORRECT: Using nested annotations
@Action(
type = ActionType.WEB_SERVICE,
webService = @WebService(endpoint = "https://api.example.com")
)Incorrect Usage
Do not put configuration properties directly on @Action -- this flat style is not supported and will cause a compile error.
// INCORRECT: Using flat properties (old style, no longer supported)
@Action(
type = ActionType.WEB_SERVICE,
endpoint = "https://api.example.com" // ERROR!
)Type Compatibility
Each action type requires a specific nested annotation. LOCAL and LLM_ROLE actions are self-contained and do not need one.
WEB_SERVICE→ Use@WebServiceLLM_TOOL→ Use@LLMToolMCP_TOOL→ Use@MCPToolLOCALandLLM_ROLE→ No nested annotation required
Related Topics
For deeper coverage of individual features mentioned in this guide, see these pages.
Agent Variants
Agent variants let you trade off between response quality, execution speed, and token cost. A single agent can switch variants at runtime -- per task or per action.
Tool Catalog
The Tools module provides 152 ready-to-use tools across 37 categories. All tools extend `AbstractTool`, are discoverable via SPI, and can be registered with any agent using `AgentBuilder.tool()`.