Class ActionExecutor

java.lang.Object
com.tnsai.actions.ActionExecutor

@ThreadSafe public class ActionExecutor extends Object
Main router for action execution in the TnsAI framework.

ActionExecutor is the central dispatcher that routes action requests to appropriate executors based on ActionType. It handles parameter conversion, approval checking, and error wrapping.

Action Types and Executors

ActionTypeExecutorDescription
LOCAL(Reflection)Direct method invocation on Role
WEB_SERVICEWebServiceExecutorHTTP REST API calls
LLMLLMRoleExecutorLLM reasoning (tool calling deferred to function-shape POJOs via AgentBuilder.toolPojos)
MCP_TOOL(tnsai-mcp module)MCP server tool execution (optional)

Execution Flow

 execute(action, role, params, context)
     │
     ├── 1. Validate inputs (null checks)
     │
     ├── 2. Check approval (@ApprovalRequired)
     │       └── Throw ApprovalRequiredException if token missing
     │
     ├── 3. Route by ActionType
     │       ├── LOCAL → invokeDirectly() via reflection
     │       └── Others → TypedActionExecutor.execute()
     │
     └── 4. Return result or wrap exception
 

Usage Examples

Basic Execution


 ActionExecutor executor = new ActionExecutor();
 // Required before any role dispatch, which is refused before the action body without them.
 // AgentBuilder does this for the agents it builds.
 executor.setPrincipal(principal);
 executor.setDefaultScope(AuthorityScope.unrestricted(Duration.ofHours(1)));

 // Get action from role
 Role role = new MyRole();
 ActionMetadata action = role.getAction("processData");

 // Execute with parameters
 Map<String, Object> params = Map.of("input", "data.csv");
 Object result = executor.execute(action, role, params);
 

With Approval Token


 // For actions annotated with @ApprovalRequired
 Map<String, Object> params = Map.of(
     "targetServer", "production",
     "_approval_token", approvalService.getToken()
 );
 executor.execute(deployAction, role, params);
 

Custom Executor Registration


 // Register a custom executor for a specific action type
 executor.registerExecutor(ActionType.WEB_SERVICE, new CustomWebServiceExecutor());
 

Parameter Conversion

The executor automatically converts parameters using TypeConverter:

  • String → int/long/double/boolean
  • Number → int/long/double/float
  • Map → POJO (using Jackson ObjectMapper)

Error Handling

All exceptions are wrapped in ActionExecutionException with:

  • PARAMETER - Invalid parameter values
  • INVOCATION - Reflection invocation errors
  • NETWORK - HTTP/API call failures
  • UNKNOWN - Unexpected errors

Thread Safety

ActionExecutor is thread-safe. Multiple threads can call execute concurrently. Each executor implementation must also be thread-safe.

See Also:
  • Constructor Details

    • ActionExecutor

      public ActionExecutor(ToolMethodDispatcher toolDispatcher)
      Creates an ActionExecutor with a ToolMethodDispatcher for external tool dispatch.

      Pre-0.7.0 this constructor took a List<Tool> of legacy Tool instances. The Tool interface and its single-string execute method were retired with the function-shape POJO migration (RFC #188 v0.7.0 closeout); the dispatcher now resolves both @Tool-annotated POJO methods (StaticToolMethod) and runtime-defined dynamic tools (DynamicToolMethod) through the same path.

      Parameters:
      toolDispatcher - dispatcher seeded by AgentBuilder.buildToolMethodDispatcher(). Must not be null; use the no-arg constructor for an agent with no external tools.
    • ActionExecutor

      public ActionExecutor()
      Creates an ActionExecutor with no external tools — the dispatcher is seeded with an empty ToolMethodRegistry.
  • Method Details

    • configureExecution

      public void configureExecution(AccessSubject agent)
      Bind the actual executing agent once at trusted host startup.
    • configureExecution

      public void configureExecution(AccessSubject agent, Function<Object,String> targetSystem)
      Optional target-system lookup supports the existing AuthorityScope system bounds.
    • authorizeAction

      public AccessRequest authorizeAction(ActionMetadata action, Object target, Map<String,Object> parameters)
      Side-effect-free authorization preflight; does not consume approval or execute hooks.
    • authorizeExternalTool

      public void authorizeExternalTool(String name, Map<String,Object> parameters)
    • authorizeMcpAlias

      public void authorizeMcpAlias(ActionMetadata action, Role role, String alias, Map<String,Object> parameters)
      Discovered endpoint aliases need their own explicit registration in secured dispatch.
    • getMcpToolExecutor

      public McpToolExecutor getMcpToolExecutor()
      Gets the McpToolExecutor if registered.
      Returns:
      the McpToolExecutor or null if not registered
    • setLiabilitySink

      public void setLiabilitySink(LiabilitySink sink)
      Configure the sink the executor publishes per-dispatch AgentLiabilityRecord entries to (TNS-298). Optional; no records are retained unless a sink is set.
      Throws:
      NullPointerException - if sink is null
    • setPrincipal

      public void setPrincipal(AgentPrincipal principal)
      Configure the AgentPrincipal this executor attributes its actions to (TNS-298). Required before execute runs, unless the dispatch runs in an InvocationContext that carries a principal: that one wins for the dispatch.
      Throws:
      NullPointerException - if principal is null
    • setDefaultScope

      public void setDefaultScope(AuthorityScope scope)
      Configure the AuthorityScope recorded on every dispatch (TNS-298). Required before execute runs.

      Use AuthorityScope.unrestricted(java.time.Duration) when the consumer deliberately chose not to narrow — that's still an explicit decision recorded in the audit trail, not a silent default.

      Throws:
      NullPointerException - if scope is null
    • registerExecutor

      public void registerExecutor(ActionType type, TypedActionExecutor executor)
      Registers a custom executor for a specific action type.
    • execute

      public Object execute(ActionMetadata action, Role role, Map<String,Object> parameters, Map<String,Object> context)
      Executes an action with the given parameters and context.
      Parameters:
      action - The action metadata
      role - The role instance that contains the method
      parameters - The parameters for the action
      context - Additional context (e.g., agent reference)
      Returns:
      The execution result
      Throws:
      ApprovalRequiredException - if action requires approval but no token provided
    • execute

      public Object execute(ActionMetadata action, Role role, Map<String,Object> parameters)
      Executes an action without additional context.
    • execute

      public Object execute(ActionMetadata action, Object target, Map<String,Object> parameters, Map<String,Object> context)
      Executes an action on a generic Object target.

      This overload supports targets that are not TnsAI Role instances, such as SCOP framework roles (ai.scop.core.Role). When the target is a TnsAI Role, it delegates to the standard execute(ActionMetadata, Role, Map, Map) method.

      For non-Role targets, the following pipeline steps apply:

      • Access control and approval checks
      • Accountability wiring check before the body, and one liability record for each dispatch that passes the entry authorization, wiring and circular-dependency checks
      • @BeforeAction hooks (discovered via reflection on target class)
      • Method- or class-level
        invalid reference
        InputGuardrail
        enforcement
      • Parameter validation and conversion
      • Declarative @Contract preconditions, invariants and postconditions
      • Action knowledge context injection
      • Method- or class-level @Resilience: retry inside the executor's default circuit breaker, and a timeout around both that bounds the caller's wait for all attempts together and runs the body on a worker thread
      • Security enforcer steps through its target-typed methods: @Security parameter encryption after the input guardrail, the security timeout inside @Resilience, and result encryption and audit after the @AfterAction hooks
      • ActionType-based routing (LOCAL via reflection, others via typed executors)
      • Method- or class-level @OutputGuardrail enforcement
      • @AfterAction hooks

      The following steps are skipped for non-Role targets:

      • Imperative ActionContract validation and @InvariantCheck state invariants
      • @Fallback recovery and @Metered/@Traced instrumentation
      • Builder-supplied role configuration (programmatic resilience and output guardrail)
      Parameters:
      action - The action metadata
      target - The target object that contains the method (can be any Object)
      parameters - The parameters for the action
      context - Additional context
      Returns:
      The execution result
      Throws:
      ActionExecutionException - if execution fails
    • getToolDispatcher

      public ToolMethodDispatcher getToolDispatcher()
      Returns the ToolMethodDispatcher backing external tool dispatch. Use to obtain the registry for catalog enumeration (e.g.
      invalid reference
      AgentToolExecutor#getToolsForLLM()
      reads the JSON-Schema fragments through this).
      Returns:
      the dispatcher (never null)
    • executeExternalTool

      public Object executeExternalTool(String toolName, Map<String,Object> args)
      Executes an external tool by name through the ToolMethodDispatcher.

      Pre-0.7.0 this took a single string input and called

      invalid reference
      Tool.execute(input)
      . With the Tool interface gone, dispatch is JSON-Schema-aware end-to-end: the LLM emits a typed argument map; the dispatcher coerces it to the tool's parameter types (for StaticToolMethod) or forwards it verbatim to the registered handler (for DynamicToolMethod).
      Parameters:
      toolName - the name of the tool to execute
      args - the LLM's argument map (Jackson-friendly)
      Returns:
      the tool's response (caller serialises back to the LLM)
      Throws:
      IllegalArgumentException - if the tool is not registered
      ToolTimeoutException - when the tool exceeds its configured deadline
      ActionExecutionException - if an input guardrail rejects the call or the tool body throws
      IdempotencyException - for any idempotency conflict, propagated unwrapped so the caller can read getConflict() and decide
    • findExternalTool

      public Optional<ToolMethod> findExternalTool(String toolName)
      Returns the ToolMethod registered under the given name, or empty if none exists.
    • hasExternalTool

      public boolean hasExternalTool(String toolName)
      Checks whether the dispatcher knows a tool with the given name.