Class ActionExecutor
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
| ActionType | Executor | Description |
|---|---|---|
| LOCAL | (Reflection) | Direct method invocation on Role |
| WEB_SERVICE | WebServiceExecutor | HTTP REST API calls |
| LLM | LLMRoleExecutor | LLM 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 valuesINVOCATION- Reflection invocation errorsNETWORK- HTTP/API call failuresUNKNOWN- 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 Summary
ConstructorsConstructorDescriptionCreates an ActionExecutor with no external tools — the dispatcher is seeded with an emptyToolMethodRegistry.ActionExecutor(ToolMethodDispatcher toolDispatcher) Creates an ActionExecutor with aToolMethodDispatcherfor external tool dispatch. -
Method Summary
Modifier and TypeMethodDescriptionauthorizeAction(ActionMetadata action, Object target, Map<String, Object> parameters) Side-effect-free authorization preflight; does not consume approval or execute hooks.voidauthorizeExternalTool(String name, Map<String, Object> parameters) voidauthorizeMcpAlias(ActionMetadata action, Role role, String alias, Map<String, Object> parameters) Discovered endpoint aliases need their own explicit registration in secured dispatch.voidconfigureExecution(AccessSubject agent) Bind the actual executing agent once at trusted host startup.voidconfigureExecution(AccessSubject agent, Function<Object, String> targetSystem) Optional target-system lookup supports the existing AuthorityScope system bounds.Executes an action without additional context.execute(ActionMetadata action, Role role, Map<String, Object> parameters, Map<String, Object> context) Executes an action with the given parameters and context.execute(ActionMetadata action, Object target, Map<String, Object> parameters, Map<String, Object> context) Executes an action on a generic Object target.executeExternalTool(String toolName, Map<String, Object> args) Executes an external tool by name through theToolMethodDispatcher.findExternalTool(String toolName) Returns theToolMethodregistered under the given name, oremptyif none exists.Gets the McpToolExecutor if registered.Returns theToolMethodDispatcherbacking external tool dispatch.booleanhasExternalTool(String toolName) Checks whether the dispatcher knows a tool with the given name.voidregisterExecutor(ActionType type, TypedActionExecutor executor) Registers a custom executor for a specific action type.voidsetDefaultScope(AuthorityScope scope) Configure theAuthorityScoperecorded on every dispatch (TNS-298).voidConfigure thesinkthe executor publishes per-dispatchAgentLiabilityRecordentries to (TNS-298).voidsetPrincipal(AgentPrincipal principal) Configure theAgentPrincipalthis executor attributes its actions to (TNS-298).
-
Constructor Details
-
ActionExecutor
Creates an ActionExecutor with aToolMethodDispatcherfor external tool dispatch.Pre-0.7.0 this constructor took a
List<Tool>of legacyToolinstances. TheToolinterface and its single-stringexecutemethod 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 byAgentBuilder.buildToolMethodDispatcher(). Must not benull; 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 emptyToolMethodRegistry.
-
-
Method Details
-
configureExecution
Bind the actual executing agent once at trusted host startup. -
configureExecution
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
-
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
Gets the McpToolExecutor if registered.- Returns:
- the McpToolExecutor or null if not registered
-
setLiabilitySink
Configure thesinkthe executor publishes per-dispatchAgentLiabilityRecordentries to (TNS-298). Optional; no records are retained unless a sink is set.- Throws:
NullPointerException- ifsinkis null
-
setPrincipal
Configure theAgentPrincipalthis executor attributes its actions to (TNS-298). Required beforeexecuteruns.- Throws:
NullPointerException- ifprincipalis null
-
setDefaultScope
Configure theAuthorityScoperecorded on every dispatch (TNS-298). Required beforeexecuteruns.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- ifscopeis null
-
registerExecutor
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 metadatarole- The role instance that contains the methodparameters- The parameters for the actioncontext- Additional context (e.g., agent reference)- Returns:
- The execution result
- Throws:
ApprovalRequiredException- if action requires approval but no token provided
-
execute
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
Roleinstances, such as SCOP framework roles (ai.scop.core.Role). When the target is a TnsAI Role, it delegates to the standardexecute(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
enforcement
invalid reference
InputGuardrail - Parameter validation and conversion
- Declarative
@Contractpreconditions, 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:
@Securityparameter 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
@OutputGuardrailenforcement - @AfterAction hooks
The following steps are skipped for non-Role targets:
- Imperative
ActionContractvalidation and@InvariantCheckstate invariants @Fallbackrecovery and@Metered/@Tracedinstrumentation- Builder-supplied role configuration (programmatic resilience and output guardrail)
- Parameters:
action- The action metadatatarget- The target object that contains the method (can be any Object)parameters- The parameters for the actioncontext- Additional context- Returns:
- The execution result
- Throws:
ActionExecutionException- if execution fails
-
getToolDispatcher
Returns theToolMethodDispatcherbacking external tool dispatch. Use to obtain the registry for catalog enumeration (e.g.reads the JSON-Schema fragments through this).invalid reference
AgentToolExecutor#getToolsForLLM()- Returns:
- the dispatcher (never null)
-
executeExternalTool
Executes an external tool by name through theToolMethodDispatcher.Pre-0.7.0 this took a single string
inputand called. 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 (forinvalid reference
Tool.execute(input)StaticToolMethod) or forwards it verbatim to the registered handler (forDynamicToolMethod).- Parameters:
toolName- the name of the tool to executeargs- 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 registeredToolTimeoutException- when the tool exceeds its configured deadlineActionExecutionException- if an input guardrail rejects the call or the tool body throwsIdempotencyException- for any idempotency conflict, propagated unwrapped so the caller can readgetConflict()and decide
-
findExternalTool
Returns theToolMethodregistered under the given name, oremptyif none exists. -
hasExternalTool
Checks whether the dispatcher knows a tool with the given name.
-