Best Practices for Building Agentforce Apex Actions

Agentforce, the agentic layer of the Salesforce Platform, empowers the creation and deployment of AI agents to augment business teams and improve customer success. Powered by the Atlas Reasoning Engine and integrated with the Salesforce Customer 360, these agents can execute tasks across sales, service, commerce, and marketing. To ensure their real-world effectiveness, Agentforce allows for the development of complex, custom business processes and deterministic logic using Apex actions.

However, building Apex actions is more than just writing Apex code. A poorly designed action can lead to the agent failing to understand user intent, resulting in incorrect outcomes and a poor user experience. Furthermore, without adhering to best practices, these Apex actions can introduce performance bottlenecks. To truly unlock the potential of Agentforce, developers must build actions that are not just functional, but also efficient, secure, and easily understood by the Atlas Reasoning Engine. This blog post will walk you through the essential best practices to achieve just that.

Apex action best practices

Designing and planning are the foundational steps for building with Agentforce, as detailed in this guide. Once you’ve determined that Apex actions are necessary for your agent, consider the following best practices when building them.

Label and describe invocable methods for the Atlas Reasoning Engine

The Atlas Reasoning Engine relies on the Agent action configuration, which incorporates the labels and descriptions of your invocable methods and their input and output parameters. This information is crucial for Atlas to effectively plan, reason, and execute actions, alongside other instructions. Make sure that you use the same values in both your Apex code and the action config, and keep them in sync for easier maintenance.

Here’s an example of a well-designed Apex action showing how to write labels and descriptions for the invocable method and the invocable parameters.

public with sharing class OrderStatusCheck {

   @InvocableMethod(
        label='Check Order Status'
        description='Returns the status and completion date of an Order based on Order Number'
    )
public static List checkOrderStatus(
        List requests
    ) {
                // rest of the code
    }
}

    // Request wrapper class
    public class OrderStatusRequest {
        @InvocableVariable(
            required=true
            label='Order Number'
            description='Order Number to check status for'
        )
        public String orderNumber;
    }

    // Response wrapper class
    public class OrderStatusResponse {
        @InvocableVariable(
            label='Order Status'
            description='Status of the Order'
        )
        public String status;

        @InvocableVariable(
            label='Completion Date'
            description='Date when the order was completed or last modified'
        )
        public DateTime completionDate;

        @InvocableVariable(
            label='Is Found'
            description='Indicates if the order was found'
        )
        public Boolean isFound;
    }

The screenshot below shows the action configuration screen in Agent Builder. Notice how the agent action label and instructions defaults to the label and descriptions specified for the invocable method. Also notice how the label and descriptions of inputs and outputs of the agent action defaults to the labels and descriptions of the invocable variables specified in the Apex class.


Action creation screen showing default labels and instructions from the Apex invocable method.

Note:  If you are an ISV, follow the additional guidelines for designing your Apex action as documented (see docs)

Manage dependent actions using topic instructions and variables

Many scenarios require Atlas to execute multiple actions in a proper sequence. You can use topic instructions to sequence the actions, but this is non-deterministic. In addition to topic instructions, you’ll want to use variables to pass around data from one action to another.

If you need to ensure a fully deterministic behavior for action sequencing, consider creating a composite action that combines actions via Apex or Flow.

Favor generic actions for optimal reusability

A generic action in Apex refers to a reusable and abstracted method or class that can operate on different types of inputs or objects without being tied to a specific object or schema.

Think of it as a template that can be applied to multiple scenarios. This type of design would allow agents to work across various data models or metadata without coupling them tightly to the schema.

Here is a simple example of a Generic class that starts the approval process for any record:

public with sharing class GenericApprovalStarter {

    @InvocableMethod(
        label='Start Approval Process'
        description='Submits records into the associated approval process. The record must be eligible for approval.'
    )
    public static List startApproval(List inputs) {
        List responses = new List();
        
        for (ApprovalInput input : inputs) {
            ApprovalResponse resp = new ApprovalResponse();
            resp.recordId = input.recordId;

            try {
                Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
                req.setObjectId(input.recordId);
                req.setComments('Submitted via generic action');
                Approval.ProcessResult result = Approval.process(req);
                
                if (result.isSuccess()) {
                    resp.status = 'Success';
                    resp.message = 'Record submitted for approval.';
                } else {
                    resp.status = 'Failed';
                    resp.message = 'Failed to submit record for approval: ' + result.getInstanceStatus();
                }
            } catch (Exception ex) {
                resp.status = 'Error';
                resp.message = 'Exception: ' + ex.getMessage();
            }

            responses.add(resp);
        }

        return responses;
    }

    public class ApprovalInput {
        @InvocableVariable(
            label='Record Id'
            description='The ID of the record to be submitted for approval. The record must have an active approval process configured.'
        )
        public Id recordId;
    }

    public class ApprovalResponse {
        @InvocableVariable(
            label='Record Id'
            description='ID of the record submitted for approval.'
        )
        public Id recordId;

        @InvocableVariable(
            label='Status'
            description='Result status: Success, Failed, or Error.'
        )
        public String status;

        @InvocableVariable(
            label='Message'
            description='Detailed result message or error information.'
        )
        public String message;
    }
}


Secure agent actions

Security is always a top priority. Understanding the context in which your agents run helps you to design secure actions. Employee Agents inherit the permissions from the current user logged in, while Agentforce Service Agents (ASA) run as the user assigned to the agent at the time of creation for unauthenticated channels. For authenticated experiences, like on an Experience Cloud site, if you have turned on Credential-Based User Verification, then the Agentforce Service Agents inherit the permissions of the logged-in Experience Cloud user.

Below are some security best practices to consider when building agent actions in Apex:

  • For private actions, always verify the user before executing the Apex action. The security principles are highlighted in the documentation.
  • For public actions, limit SOQL queries in your action to sending responses to expose only the necessary fields, thereby avoiding the exposure of sensitive data
  • Always use the with sharing keyword in your Apex classes and run all DML and queries in “with user” mode to enforce the user’s object level permissions and field-level security

Implement robust error handling

Robust error handling is essential for a smooth user experience. Always use a try-catch block to handle errors gracefully and return a user-friendly error message.

If you need to process records partially, use the Database class instead of DML verbs.

Below is an example pattern that shows error handling and returning user-friendly messages when working with a Database class for partial processing.

@InvocableMethod(
    label='Update Order Status'
    description='Takes list of Order Requests, processes them, and returns the list of updated order information'
)
public static List processOrders(List<List> requests) {

// rest of the code

List saveResults = Database.update(
    ordersToUpdate,
    /* allOrNone */ false,
    AccessLevel.USER_MODE
);

// Map each result to its corresponding order
for (Integer i = 0; i < saveResults.size(); i++) {
    Order order = ordersToUpdate[i];
    Database.SaveResult sr = saveResults[i];

    OrderResult result = new OrderResult();
    result.orderId = order.Id;
    result.newStatus = order.Status;
    result.orderNumber = order.OrderNumber;

    if (sr.isSuccess()) {
        result.isSuccess = true;
        result.message = 'Order status updated successfully';
    } else {
        result.isSuccess = false;
        result.message = 'Failed to update order: ' + sr.getErrors()[0].getMessage();
    }

    orderResultByOrderId.put(result.orderId, result);
}

// rest of the code

}

Test your agent actions

Thorough testing is critical to ensure that your agent behaves as expected. Agentforce provides several tools for this purpose:

  • Agent Builder: Use Agent Builder to test your agent’s planning and reasoning capabilities in real time
  • Agentforce Testing Center: For bulk testing of your agent’s responses to a variety of inputs, use Agentforce Testing Center
  • Agentforce DX Pro-Code tools: You can automate testing via CI/CD using Agentforce DX Pro-Code tools
  • Apex Unit Tests: For the Apex code used for the actions themselves, continue to write comprehensive Apex unit tests to ensure that your code is bug-free

Optimize action performance

A slow agent action provides a poor user experience. Keep your Apex actions performing at their best with the following tips.

Write bulkified actions

Agentforce actions do not bulkify by default, and each action executes in its transaction. For maximum efficiency, we recommend bulkifying your Apex code. Apex agent actions are invocable actions and you may end up reusing them in a flow, so this should give you another reason to bulkify your code.

The following example demonstrates a method for constructing method signatures when designing a bulkified Apex action.

public with sharing class OrderManagement {

    @InvocableMethod(
        label='Update Order Status',
        description='Takes a list of order requests and updates their status in bulk. Returns the result for each order.'
    )
    public static List updateOrders(List<List> requests) {
        // Bulk-safe logic goes here with Sets and Maps
    }

    public class OrderRequest {
        @InvocableVariable(
            label='Order ID',
            description='The ID of the order to update'
        )
        public Id orderId;

        @InvocableVariable(
            label='New Status',
            description='The new status value to apply to the order'
        )
        public String newStatus;
    }

    public class OrderResult {
        @InvocableVariable(
            label='Order ID',
            description='The ID of the order that was processed'
        )
        public Id orderId;

        @InvocableVariable(
            label='New Status',
            description='The status that was set on the order'
        )
        public String newStatus;

        @InvocableVariable(
            label='Order Number',
            description='The order number of the updated order'
        )
        public String orderNumber;

        @InvocableVariable(
            label='Success',
            description='True if the update was successful, false otherwise'
        )
        public Boolean isSuccess;

        @InvocableVariable(
            label='Message',
            description='Message indicating success or error details'
        )
        public String message;
    }

    public class OrderResponse {
        public List orderResults;
    }

    public class WrapperOrderResult {
        public WrapperOrderResult(OrderResponse orderResults) {
            this.orderResults = orderResults;
        }

        @InvocableVariable(
            label='Order Results',
            description='List of status update results for the order batch'
        )
        public OrderResponse orderResults;
    }
}

To ensure that the agent always sends requests in bulk, you can provide instructions. For example, for the above Apex action, we can have an instruction in the topic like — “If updating more than one order, ensure that you pass all the orders to the action for bulk processing the requests. Do not process them one by one” — to ensure that the agent sends orders to update in bulk.

Break down large actions

If you hit CPU timeout limits with a composite action, it’s recommended to decompose complex actions into several smaller, more manageable actions. This approach helps with distributing the processing load and preventing a single action from consuming excessive resources.

Offload resource intensive tasks

For tasks that require significant computational power, consider leveraging Heroku Applink. The Heroku Applink tool lets you integrate code from the Heroku platform to an Agentforce action without needing custom code for integrating both. Heroku allows you to scale compute resources elastically and eliminates the need for extensive infrastructure management.

Optimize SOQL queries

Efficient and well-filtered SOQL queries are crucial for preventing performance bottlenecks. By writing optimized queries, you can ensure that data retrieval is swift and resource consumption is minimized. The query and search optimization cheat sheet is a great resource for learning how to optimize SOQL.

Utilize asynchronous processing

For processes that are expected to run for a long duration, utilize asynchronous processing with Queueable Apex. This method prevents the user interface from blocking and improves the overall responsiveness of the application. The caveat here is that when you use asynchronous processing, use either other communication mechanisms (such as emails or text) to notify users when your Queueable Apex job has finished processing or alternatively provide users a requestId that they can use to ask for the status of the processing.

Conclusion

Building Agentforce Apex Actions is a powerful way to give agents the capability to perform complex business processes in a deterministic way. By following these best practices, you can create actions that are not only powerful and intelligent, but also secure, reliable, and performant. As you embark on your Agentforce development journey, remember that clear design, thorough testing, and a focus on performance and security are the keys to success.

About the author

Mohith Shrivastava is a Principal Developer Advocate at Salesforce with 14 years of experience building enterprise-scale products on the Salesforce Platform. Mohith is currently among the lead contributors on Salesforce Stack Exchange, a developer forum where Salesforce Developers can ask questions and share knowledge. You can follow him on LinkedIn.

The post Best Practices for Building Agentforce Apex Actions appeared first on Salesforce Developers Blog.