Annotation Interface ActionSpec
Actions are the primary way for agents to perform tasks. They are automatically discovered via reflection on Role classes and exposed as tools to the LLM for function calling.
Action Types
| Type | Description | Configuration |
|---|---|---|
LOCAL | Direct Java method execution | - |
WEB_SERVICE | HTTP API call | WebService |
LLM | LLM-driven dispatch (optional per-action
llmTemperature() + llmSystemPrompt() overrides;
tool exposure is configured at the agent level via
AgentBuilder.builtInTools(...) / .toolPojos(...)) |
llmTemperature, llmSystemPrompt |
MCP_TOOL | Calls MCP server tool | MCPTool |
Optional Configurations
| Annotation | Purpose |
|---|---|
Utility | Cost/value config for Utility AI planning |
Quick Start Examples
LOCAL Action (Direct Code)
@ActionSpec(type = ActionType.LOCAL, description = "Calculates the sum of numbers")
public double sum(List<Double> numbers) {
return numbers.stream().mapToDouble(d -> d).sum();
}
WEB_SERVICE Action (REST API)
@ActionSpec(
type = ActionType.WEB_SERVICE,
description = "Fetches weather data for a city",
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
@ActionSpec(
type = ActionType.LLM,
description = "Researches a topic using the agent's configured tools",
llmSystemPrompt = "You are a careful, citation-driven research assistant.",
llmTemperature = 0.2f
)
public String researchTopic(String topic) {
return "Please research: " + topic;
}
Tool exposure is configured at the agent level — register POJO toolkits
via AgentBuilder.builtInTools(BuiltInTool...) or
.toolPojos(Object...); the LLMRoleExecutor then uses the
agent's ToolMethodDispatcher for tool dispatch on this action's
behalf.
MCP_TOOL Action (MCP Server)
@ActionSpec(
type = ActionType.MCP_TOOL,
description = "Access cryptocurrency data via CoinGecko",
mcpTool = @MCPTool(
serverUrl = "https://mcp.coingecko.com/mcp",
toolName = "get_coin_price",
apiKeyEnv = "COINGECKO_API_KEY"
)
)
public CoinPrice getCoinPrice(String coinId) {
return null; // Executed via MCP
}
Programmatic-Only Action (Not Exposed to LLM)
@ActionSpec(
type = ActionType.LOCAL,
description = "Registers a worker agent",
excludeFromLLM = true // Not visible to LLM
)
public void registerWorker(String workerId) {
workers.add(workerId);
}
Action Discovery
Actions are discovered from Role classes during initialization:
- Role class is scanned for
@Actionannotated methods - Spec is extracted (name, description, parameters)
- JSON Schema is generated for LLM tool definitions
- Actions become available via
agent.executeAction()
- Author:
- TnsAI Team
- See Also:
-
Required Element Summary
Required ElementsModifier and TypeRequired ElementDescriptionREQUIRED: The type of action execution. -
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionbooleanIf true, framework automatically applies @Effect annotations to state fields.Description of what this action does.Effect[]Structured effects for fine-grained planning.Examples demonstrating how to use this action/tool.booleanIf true, this action will NOT be exposed to the LLM as a tool.Optional access policy.String[]Goals that this action helps achieve.Knowledge this action sees, by name.Optional system prompt override forLLMactions.floatOptional temperature override forLLMactions.MCP tool configuration for MCP_TOOL actions.String[]Constraints describing what this action must always do (liveness properties).String[]Constraints describing what this action must never do (safety properties).Planning postcondition (effect) predicted after this action executes.Planning precondition that must be true before the planner selects this action.Utility-based planning configuration.Web service configuration for WEB_SERVICE actions.
-
Element Details
-
execution
ExecutionPolicy[] executionOptional access policy. An empty array declares no additional access restriction.- Default:
{}
-
type
ActionType typeREQUIRED: The type of action execution.Must be one of: LOCAL, WEB_SERVICE, LLM, LLM, MCP_TOOL
No default value - explicit declaration is MANDATORY!
- Returns:
- action type
-
description
String descriptionDescription of what this action does.Used in system prompts and tool definitions.
- Returns:
- action description
- Default:
""
-
excludeFromLLM
boolean excludeFromLLMIf true, this action will NOT be exposed to the LLM as a tool.Excluded actions can still be called programmatically via
agent.executeAction()but won't appear in the agent's system prompt or tool list.Use this for:
- Coordination actions (registerWorker, submitTasks)
- Internal state management
- Actions triggered by other agents or systems
- Returns:
- true to exclude from LLM tool list
- Default:
false
-
webService
WebService webServiceWeb service configuration for WEB_SERVICE actions.@ActionSpec( type = ActionType.WEB_SERVICE, description = "Fetches weather data", webService = @WebService( endpoint = "https://api.weather.com/v1/current", method = HttpMethod.GET, timeout = 5000 ) )- See Also:
- Default:
@com.tnsai.annotations.WebService(endpoint="")
-
llmSystemPrompt
String llmSystemPromptOptional system prompt override forLLMactions. When set, theLLMRoleExecutorprepends this string as the system message of the chat request, overriding the LLM client's default system prompt for the duration of this action.Empty string means "use the LLM client's default".
@ActionSpec( type = ActionType.LLM, description = "Summarise a document in two sentences", llmSystemPrompt = "You are a precise, terse summariser. No adjectives." )- Since:
- 0.8.0
- Default:
""
-
llmTemperature
float llmTemperatureOptional temperature override forLLMactions. When >= 0, theLLMRoleExecutorsets this temperature on the chat request. A negative value (the default-1.0f) means "fall back to the LLM client's default temperature".Use this to lock per-action determinism (e.g.
0.0ffor extraction actions,0.7f+for creative actions) without touching the agent's global LLM config.@ActionSpec( type = ActionType.LLM, description = "Extract entities from text — must be deterministic", llmTemperature = 0.0f )- Since:
- 0.8.0
- Default:
-1.0f
-
mcpTool
MCPTool mcpToolMCP tool configuration for MCP_TOOL actions.@ActionSpec( type = ActionType.MCP_TOOL, description = "Access crypto data", mcpTool = @MCPTool( serverUrl = "https://mcp.coingecko.com/mcp", toolName = "get_coin_price" ) )- See Also:
- Default:
@com.tnsai.annotations.MCPTool(serverUrl="")
-
examples
ToolExample[] examplesExamples demonstrating how to use this action/tool.Providing examples significantly improves LLM accuracy (72% → 90%).
@ActionSpec( type = ActionType.LLM, description = "Searches for academic papers", examples = { @ToolExample( description = "Basic search", input = "{\"query\": \"machine learning\", \"limit\": 5}", output = "Found 5 papers..." ) } )- See Also:
- Default:
{}
-
mustNever
String[] mustNeverConstraints describing what this action must never do (safety properties).Rendered into the system prompt as action-attributed guardrails. Use this to bind safety constraints to the action they apply to — the action method becomes the natural locus for traceability and audit.
@ActionSpec( type = ActionType.LOCAL, description = "Process a refund request", mustNever = {"refund without authorization", "refund expired orders"} ) public void processRefund(String orderId) { ... }- Since:
- 0.9.0
- Default:
{}
-
mustAlways
String[] mustAlwaysConstraints describing what this action must always do (liveness properties).Rendered into the system prompt as action-attributed guarantees.
@ActionSpec( type = ActionType.LOCAL, description = "Process a refund request", mustAlways = {"verify customer identity", "log refund to audit trail"} ) public void processRefund(String orderId) { ... }- Since:
- 0.9.0
- Default:
{}
-
precondition
String preconditionPlanning precondition that must be true before the planner selects this action.Expression evaluated against agent's beliefs.
If precondition is not satisfied, action cannot be selected by the planner. This expression is planner-only metadata and is not a runtime dispatch gate. Use
Contract.preconditions()for a precondition thatActionExecutormust enforce when the action is invoked.@ActionSpec( type = ActionType.LOCAL, description = "Reproduce by becoming alive", precondition = "state == DEAD && neighborCount == 3" ) public void reproduce() { ... }Syntax examples:
"state == DEAD"- equality check"neighborCount >= 2"- comparison"state == DEAD && neighborCount == 3"- logical AND"energy > 0 || hasBackup == true"- logical OR
- See Also:
- Default:
""
-
postcondition
String postconditionPlanning postcondition (effect) predicted after this action executes.Describes how beliefs change after action execution.
Used by the planner to predict action outcomes and build plans. This expression is planner-only metadata and is not checked after runtime dispatch. Use
Contract.postconditions()for a postcondition thatActionExecutormust enforce after invocation.@ActionSpec( type = ActionType.LOCAL, description = "Die from loneliness", precondition = "state == ALIVE && neighborCount < 2", postcondition = "state = DEAD" ) public void dieFromLoneliness() { ... }Syntax examples:
"state = ALIVE"- assignment"energy = energy - 10"- arithmetic"state = ALIVE, ticksAlive = ticksAlive + 1"- multiple effects
- See Also:
- Default:
""
-
fulfills
String[] fulfillsGoals that this action helps achieve.Links actions to goals for planning.
@ActionSpec( type = ActionType.LOCAL, description = "Update cell state based on Conway's rules", fulfills = {"survive", "reproduce"} ) public void updateState() { ... }The planner uses this to find actions that can satisfy goals.
- See Also:
- Default:
{}
-
effects
Effect[] effectsStructured effects for fine-grained planning.Alternative to postcondition string for complex effects.
@ActionSpec( type = ActionType.LOCAL, description = "Move to adjacent cell", effects = { @Effect(belief = "position", operation = ADD, value = "direction"), @Effect(state = "energy", operation = SUBTRACT, value = "5") } ) public void move(Direction direction) { ... }- See Also:
- Default:
{}
-
autoApplyEffects
boolean autoApplyEffectsIf true, framework automatically applies @Effect annotations to state fields.When enabled, you don't need to manually implement the effect logic in the method body - the framework reads @Effect annotations and applies them after method execution.
This eliminates double implementation:
// WITHOUT autoApplyEffects (need to write effect twice): @ActionSpec( effects = @Effect(state = "energy", operation = SUBTRACT, value = "10") ) public void move() { this.energy -= 10; // Must manually implement } // WITH autoApplyEffects (framework handles it): @ActionSpec( effects = @Effect(state = "energy", operation = SUBTRACT, value = "10"), autoApplyEffects = true ) public void move() { // No need to implement - framework applies effect automatically }- See Also:
- Default:
false
-
utility
Utility utilityUtility-based planning configuration.Configure cost/value trade-offs for Utility AI action selection. UtilityAIPlanner selects actions greedily based on utility score.
@ActionSpec( type = ActionType.LLM, description = "Search in cache (fast, cheap)", utility = @Utility(cost = 5, value = 30, tags = {"fast", "cheap"}) ) public SearchResults searchCache(String query) { ... } @ActionSpec( type = ActionType.LLM, description = "Search the web (slow, expensive but thorough)", utility = @Utility(cost = 20, value = 70, tags = {"slow", "expensive"}) ) public SearchResults searchWeb(String query) { ... }- See Also:
- Default:
@com.tnsai.annotations.Utility
-
knowledge
Knowledge[] knowledgeKnowledge this action sees, by name.Each entry names a source declared in the
knowledgeof the enclosingRoleSpecorAgentSpec. An entry may set onlyKnowledge.use(),Knowledge.maxTokens()andKnowledge.search(), each overriding the declared value; setting a source member such asKnowledge.path()fails at startup, and so does a name nothing declares.Valid on
ActionType.LLM, where the knowledge is spliced into the prompt, andActionType.LOCAL, where it is pulled before the body runs and a host may return it as the tool result. Other action types fail at startup.@ActionSpec(type = ActionType.LLM, description = "Answer a question", knowledge = @Knowledge(name = "product-docs", search = @Search(topK = 3))) public String answer(String question) { ... }- Returns:
- named knowledge (empty by default: the action sees none)
- Since:
- 0.18.0
- See Also:
- Default:
{}
-