**Source URL:** https://limited.veevavault.dev/qualityone/ai-agents/agent-tools/how-to-invoke-agent-action-from-tool

# How to Invoke an Agent Action from a Custom Tool Type

<Aside type="note">
The functionality described on this page is only available to customers who have Vault AI enabled on their Vault.
</Aside>

A custom tool type can invoke one or more agent actions asynchronously. In `onExecute`, the tool starts the agent actions, returns a pending async response, and the platform calls `onAsyncExecutionComplete` after all declared results are committed.

Before following these steps, [create a custom tool type](/qualityone/ai-agents/agent-tools/how-to-create-custom-tool-type/).

<Steps>
1.  Get the tool call in `onExecute`.
    
    Call `context.getToolCall()` to retrieve an `AiToolCall`, the unique identifier linking the tool call to its async results. Forward the `AiToolCall` to every async operation you start.
    
    ```java
    AiToolCall toolCall = context.getToolCall();
    ```
    
2.  Start an agent instance and run the action with `withToolCall`.
    
    Build and start an agent instance, then build `AgentActionParameters` with `.withToolCall(toolCall)` and `.withResultHandler(MyToolActionAsyncHandler.class)`. Call `agentService.runAgentAction(parameters)` and retrieve the action execution ID from the returned `AgentActionRunResult`.
    
    ```java
    StartAgentInstanceResult agentInstanceResult = agentService.startAgentInstance(
            agentService.newStartAgentInstanceRequestBuilder()
                    .withAgentConfigurationName("classroom_agent__c")
                    .withScopeSource(scopeSource)
                    .build());
    
    String agentInstanceId = agentInstanceResult.getAgentInstanceId();
    
    AgentActionParameters parameters = agentService.newAgentActionParametersBuilder()
            .withAgentInstanceId(agentInstanceId)
            .withActionName("summarize_course__c")
            .withToolCall(toolCall)
            .withResultHandler(MyToolActionAsyncHandler.class)
            .build();
    
    AgentActionRunResult runResult = agentService.runAgentAction(parameters);
    String actionExecutionId = runResult.getActionInstanceId();
    ```
    
    Developers can only invoke `__c` agent actions from a tool.
    
3.  Declare async results and return an async response.
    
    Call `context.newAsyncExecutionBuilder().withActionExecutionIds(VaultCollections.asList(actionExecutionId)).build()` and return the result. `withActionExecutionIds` declares the action executions the platform must wait for. The platform waits until every declared action execution has committed its result with a terminal status before calling `onAsyncExecutionComplete`. A status of `NOT_COMPLETED` keeps the slot open.
    
    ```java
    return context.newAsyncExecutionBuilder()
            .withActionExecutionIds(VaultCollections.asList(actionExecutionId))
            .build();
    ```
    
    The maximum number of action execution IDs per tool call is five. Each action execution ID must be unique within the tool call.
    
4.  Implement the result handler.
    
    Create a class that implements `AgentActionResultHandler` and is annotated with `@AgentActionResultHandlerInfo`. In `onSuccess`, call `result.getToolCall()`. A non-null value means the action was invoked from a tool.
    
    Build an `AiToolResultRequest` with the tool call, action execution ID, status, and any output values, then commit it using `AiToolRuntimeService.appendAiToolResult()`. In `onError`, follow the same pattern with `AiToolResultStatus.FAILURE`.
    
    ```java
    package com.veeva.vault.custom.actionhandler;
    
    import …
    @AgentActionResultHandlerInfo
    public class MyToolActionAsyncHandler implements AgentActionResultHandler {
    
        @Override
        public void onSuccess(AgentActionSuccess result) {
            AiToolRuntimeService toolRuntimeService = ServiceLocator.locate(AiToolRuntimeService.class);
            LogService logService = ServiceLocator.locate(LogService.class);
    
            AiToolCall toolCall = result.getToolCall();
            if (toolCall != null) {
                JsonObject output = result.getOutput(0, AgentActionOutputType.JSON);
    
                toolRuntimeService.appendAiToolResult(
                        toolRuntimeService.newAiToolResultRequestBuilder()
                                .withToolCall(toolCall)
                                .withActionExecutionId(result.getActionInstanceId())
                                .withCompleteStatus(AiToolResultStatus.SUCCESS)
                                .withValue("output", output)
                                .build())
                        .onSuccess(response ->
                                logService.info("Result {} committed as {}",
                                        response.getActionExecutionId(), response.getStatus()))
                        .onError(error ->
                                logService.error("Commit failed for {}: {}",
                                        error.getActionExecutionId(), error.getErrorMessage()))
                        .execute();
            }
        }
    
        @Override
        public void onError(AgentActionError error) {
            AiToolRuntimeService toolRuntimeService = ServiceLocator.locate(AiToolRuntimeService.class);
            LogService logService = ServiceLocator.locate(LogService.class);
    
            AiToolCall toolCall = error.getToolCall();
            if (toolCall != null) {
                toolRuntimeService.appendAiToolResult(
                        toolRuntimeService.newAiToolResultRequestBuilder()
                                .withToolCall(toolCall)
                                .withActionExecutionId(error.getActionInstanceId())
                                .withCompleteStatus(AiToolResultStatus.FAILURE)
                                .withValue("errorMessage", error.getMessage())
                                .build())
                        .onSuccess(response ->
                                logService.info("Failure result {} committed as {}",
                                        response.getActionExecutionId(), response.getStatus()))
                        .onError(appendError ->
                                logService.error("Commit failed for {}: {}",
                                        appendError.getActionExecutionId(), appendError.getErrorMessage()))
                        .execute();
            }
        }
    }
    ```
    
5.  Implement `onAsyncExecutionComplete`.
    
    Override `onAsyncExecutionComplete(AiToolTypeAsyncExecutionContext context)` on the tool type class. Retrieve all async results via `context.getAllAiToolAsyncResults()`, then iterate and read each result. Return an `AiToolTypeExecutionResponse`.
    
    `onAsyncExecutionComplete` fires after every declared action execution has committed its result with a terminal status. A status of `NOT_COMPLETED` keeps the slot open.
    
    The following example shows both `onExecute` and `onAsyncExecutionComplete` in a complete tool type implementation.
    
    ```java
    package com.veeva.vault.custom.tooltype;
    
    import …
    
    @ExecuteAs(ExecuteAsUser.REQUEST_OWNER)
    @AiToolTypeRuntimeHandlerInfo(dynamicToolSpec = false)
    public class InvokeAgentToolType implements AiToolTypeRuntimeHandler {
    
        @Override
        public AiToolTypeExecutionResponse onExecute(AiToolTypeExecuteContext context) {
            AgentService agentService = ServiceLocator.locate(AgentService.class);
            AiService aiService = ServiceLocator.locate(AiService.class);
    
            JsonObject toolInput = context.getInput();
            String courseId = toolInput.getValue("course_id", JsonValueType.STRING);
    
            AiScopeSource scopeSource = aiService.newAiObjectScopeSourceBuilder()
                    .withObjectName("course__c")
                    .withRecordId(courseId)
                    .build();
    
            StartAgentInstanceResult agentInstanceResult = agentService.startAgentInstance(
                    agentService.newStartAgentInstanceRequestBuilder()
                            .withAgentConfigurationName("classroom_agent__c")
                            .withScopeSource(scopeSource)
                            .build());
    
            AiToolCall toolCall = context.getToolCall();
            String agentInstanceId = agentInstanceResult.getAgentInstanceId();
    
            AgentActionParameters parameters = agentService.newAgentActionParametersBuilder()
                    .withAgentInstanceId(agentInstanceId)
                    .withActionName("summarize_course__c")
                    .withToolCall(toolCall)
                    .withResultHandler(MyToolActionAsyncHandler.class)
                    .build();
    
            AgentActionRunResult runResult = agentService.runAgentAction(parameters);
    
            return context.newAsyncExecutionBuilder()
                    .withActionExecutionIds(VaultCollections.asList(runResult.getActionInstanceId()))
                    .build();
        }
    
        @Override
        public AiToolTypeExecutionResponse onAsyncExecutionComplete(AiToolTypeAsyncExecutionContext context) {
            List<AiToolResult> results = context.getAllAiToolAsyncResults();
            AiToolResult result = results.get(0);
    
            JsonObject output = result.getValue("output", AgentActionParametersValueType.OBJECT);
    
            return context.newSuccessBuilder(AiToolTypeExecutionResponse.JsonResultBuilder.class)
                    .withJson(output)
                    .build();
        }
    }
    ```
</Steps>

## Constraints

The following limits and restrictions apply when invoking agent actions from a custom tool type:

*   The maximum number of action execution IDs per tool call is five.
*   Each action execution ID must be unique within the tool call. Duplicate declarations are retry-safe.
*   Developers may only invoke `__c` agent actions from a tool.
*   Parallel invocation of a Job and an agent action in the same tool call is not supported.

---

**Previous:** [How to Create Custom Agent Tool Types](/qualityone/ai-agents/agent-tools/how-to-create-custom-tool-type)  
**Next:** [API Reference](/qualityone/ai-agents/api)