Annotation Interface ActionSpec


@Documented @Retention(RUNTIME) @Target(METHOD) public @interface ActionSpec
Marks a method as an executable action that can be discovered and invoked by agents.

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

TypeDescriptionConfiguration
LOCALDirect Java method execution-
WEB_SERVICEHTTP API callWebService
LLMLLM-driven dispatch (optional per-action llmTemperature() + llmSystemPrompt() overrides; tool exposure is configured at the agent level via AgentBuilder.builtInTools(...) / .toolPojos(...)) llmTemperature, llmSystemPrompt
MCP_TOOLCalls MCP server toolMCPTool

Optional Configurations

AnnotationPurpose
UtilityCost/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:

  1. Role class is scanned for @Action annotated methods
  2. Spec is extracted (name, description, parameters)
  3. JSON Schema is generated for LLM tool definitions
  4. Actions become available via agent.executeAction()
Author:
TnsAI Team
See Also:
  • Element Details

    • execution

      ExecutionPolicy[] execution
      Optional access policy. An empty array declares no additional access restriction.
      Default:
      {}
    • type

      REQUIRED: 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 description
      Description of what this action does.

      Used in system prompts and tool definitions.

      Returns:
      action description
      Default:
      ""
    • excludeFromLLM

      boolean excludeFromLLM
      If 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 webService
      Web 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 llmSystemPrompt
      Optional system prompt override for LLM actions. When set, the LLMRoleExecutor prepends 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 llmTemperature
      Optional temperature override for LLM actions. When >= 0, the LLMRoleExecutor sets 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.0f for 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 mcpTool
      MCP 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[] examples
      Examples 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[] mustNever
      Constraints 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[] mustAlways
      Constraints 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 precondition
      Planning 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 that ActionExecutor must 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 postcondition
      Planning 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 that ActionExecutor must 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[] fulfills
      Goals 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[] effects
      Structured 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 autoApplyEffects
      If 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 utility
      Utility-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:
      • Utility
      • invalid reference
        com.tnsai.planning.UtilityAIPlanner
      Default:
      @com.tnsai.annotations.Utility
    • knowledge

      Knowledge[] knowledge
      Knowledge this action sees, by name.

      Each entry names a source declared in the knowledge of the enclosing RoleSpec or AgentSpec. An entry may set only Knowledge.use(), Knowledge.maxTokens() and Knowledge.search(), each overriding the declared value; setting a source member such as Knowledge.path() fails at startup, and so does a name nothing declares.

      Valid on ActionType.LLM, where the knowledge is spliced into the prompt, and ActionType.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:
      {}