An Agentforce agent is only as capable as the actions you give it. Everything else, the reasoning, the instructions, the conversation, exists to select and call actions correctly. This is also the part of Agentforce that is ordinary Salesforce engineering, which is good news: you already know how to build a reliable Flow. What is new is designing for a non-deterministic caller that reads your descriptions, chooses your parameters, and sometimes tries again.
What an action is, precisely
An action is a callable unit of work exposed to the reasoning engine, with a name, a description, typed inputs, and typed outputs. In metadata it is a GenAiFunction. Actions are grouped into subagents, represented by GenAiPlugin, and the reasoning engine that selects among them is a GenAiPlannerBundle.
Salesforce ships a library of standard actions. Use them where they fit, because they are tested and maintained. You build custom actions when your process is specific to your business, which is most of the interesting cases.
The one mental shift required: your caller is not code. A reasoning engine decides whether to call your action, based on a description written in English, and populates the inputs from a conversation. It may call the action twice. It may pass a value that is technically valid and contextually wrong. Design accordingly.
Choosing between Flow, Apex, and prompt templates
| Type | Use it when | Avoid when |
|---|---|---|
| Flow | Record create, update, lookup, a few decisions, orchestrating a short process. Most actions should be this | Complex loops, heavy data volumes, intricate error handling |
| Apex | Logic Flow cannot express cleanly, external callouts with real error handling, bulk work, anything needing unit tests you control | A Flow would do it. Do not reach for Apex out of habit |
| Prompt template | The output is generated language: a summary, a draft reply, an explanation of a record | The output must be exact or deterministic. Never use a model to compute a total |
| External service or API | The system of record is outside Salesforce | Latency is unacceptable in a live conversation |
Default to Flow. It is declarative, visible to admins, and easier to change than Apex, which matters because agent actions get revised often as you learn what users actually ask.
Build actions for humans and the agent, not for the agent alone. If a screen flow already handles a process, expose the underlying subflow or Apex as the agent action. One tested code path serves both, and when the business rule changes you change it once. Teams that build a parallel agent-only implementation end up with two behaviours that diverge, and the divergence is discovered by a customer.
Descriptions are functional design
This is the part most teams underestimate. The planner selects actions by reading their descriptions. A vague description produces an agent that picks the wrong tool, which users experience as stupidity and engineers diagnose as a model problem. It is not a model problem.
Write descriptions that state what the action does, when to use it, and when not to.
| Weak | Strong |
|---|---|
| Gets order info | Returns the status, ship date, and tracking number for a single order, given an order number. Use when a customer asks where their order is. Does not cancel or modify orders |
| Updates the case | Sets a case to Escalated and assigns it to the tier two queue, given a case number and a reason. Use only after attempting resolution. Does not close cases |
| Refund action | Issues a refund of a specified amount against a paid order, up to 200 dollars. Use only when the customer has confirmed the amount and the order is within the 30 day window. Amounts above 200 dollars must be escalated instead |
Three rules that come out of this:
- State the boundary in the description. "Does not cancel orders" prevents a whole class of misselection. Negative statements do more work than positive ones.
- Make descriptions mutually distinguishable. Two actions with overlapping descriptions produce coin-flip routing. If you cannot tell them apart reading only the descriptions, neither can the planner.
- Describe the same thing consistently. Pick one term for a concept, order or purchase, not both, and use it everywhere including your instructions.
When an agent misroutes, read the descriptions of the candidate actions side by side before touching anything else. The fix is usually there.
Input and output design
Inputs are populated by a model reading a conversation, so the schema is your first line of defence.
- Type tightly. A picklist of three values cannot receive a fourth. A free text input can receive anything.
- Describe every input, including format. "Order number, format ORD followed by 8 digits" is far more reliable than "order number".
- Keep the input count low. Every additional input is another chance to be populated wrongly. Look things up inside the action rather than asking the planner to supply them.
- Prefer identifiers the user actually knows. Ask for an order number, not a record identifier. Resolve internally.
- Make optionality explicit, so the agent knows when it must ask a follow-up question rather than guess.
Outputs need as much care, because they become the model's understanding of what happened.
- Return structured results, not a prose sentence. Let the agent do the wording.
- Return an explicit success indicator. Do not make the agent infer success from the shape of the response.
- Return actionable failure reasons. "Order not found", "Outside refund window", "Amount exceeds limit" each let the agent choose a sensible next step. "Error" leaves it improvising, and improvisation is what produces embarrassing transcripts.
- Keep outputs small. Everything returned occupies context. Returning 60 fields when 5 are needed degrades the answer and costs more.
Building an Apex action
An Apex agent action is an invocable method. The annotations are what make it discoverable and selectable, so treat the labels and descriptions as production configuration rather than comments.
public with sharing class OrderStatusAction {
public class Request {
@InvocableVariable(
label='Order Number'
description='Customer-facing order number, format ORD followed by 8 digits'
required=true)
public String orderNumber;
}
public class Result {
@InvocableVariable(label='Success' description='True if the order was found')
public Boolean success;
@InvocableVariable(label='Failure Reason'
description='Why the lookup failed: NOT_FOUND or NO_ACCESS. Empty on success')
public String failureReason;
@InvocableVariable(label='Status' description='Current order status')
public String status;
@InvocableVariable(label='Tracking Number' description='Carrier tracking number, if shipped')
public String trackingNumber;
}
// Annotation values must be single literals, so the description stays on
// one line. Write it in the style shown in the table above.
@InvocableMethod(
label='Get Order Status'
description='Returns status and tracking for one order. Use when a customer asks where their order is. Does not cancel or modify orders.'
category='Order Service')
public static List<Result> getStatus(List<Request> requests) {
List<Result> results = new List<Result>();
for (Request req : requests) {
results.add(lookup(req.orderNumber));
}
return results;
}
}
Points that matter in production:
with sharing, always. The action executes as the agent running user. Running without sharing hands the agent access to every record in the org, which is exactly the outcome you were trying to avoid by creating a limited running user.- Return failure as data, not as an exception. An unhandled exception gives the agent nothing to reason about. A typed failure reason lets it respond usefully or escalate.
- Honour the bulk signature. Invocable methods take and return lists, and the positions must correspond.
- Write real unit tests. This is normal Apex and there is no excuse for skipping them. Test the failure paths in particular, because those are the ones the agent will hit.
Building a Flow action
Use an autolaunched flow. Mark input variables as available for input and output variables as available for output, and give every one of them a description, because those descriptions are what the planner sees.
Flow specific advice:
- Set fault paths on every element that can fail, and have them return a failure reason rather than letting the flow error out.
- Keep the flow narrow. One job. A flow doing five things is a subagent's worth of work compressed into an action the planner cannot reason about.
- Do validation inside the flow. Never assume the inputs are sensible, because they came from a conversation.
- Avoid screen elements. Agent actions are autolaunched. There is no user to look at a screen.
Making actions safe to retry
This section exists because of one fact: a planner can call an action more than once. Retries happen after ambiguous responses, timeouts, and conversational restarts.
So for any action with a side effect, ask what happens if it runs twice in a row. Then make the answer acceptable:
- Idempotency keys. Derive a key from the business context, such as order plus amount plus day, and refuse a second identical operation. This is the cleanest general solution.
- State checks before acting. Do not refund an order already marked refunded. Do not send a confirmation already sent.
- Limits inside the action, not only in the instructions. If the maximum refund is 200 dollars, enforce it in code. Instructions are guidance; code is a rule.
- Human confirmation for consequential actions. Anything that moves money, changes entitlement, sends external communication, or deletes data.
The distinction worth internalising. Instructions are strong suggestions to a probabilistic system. Permissions, validation rules, and code checks are enforcement. Every constraint that genuinely must hold belongs in the second category. Anyone who tells you a well-written prompt is sufficient control has not operated an agent in production.
Guardrails that actually hold
In descending order of reliability:
- The running user's permissions. The hardest boundary you have. Create a dedicated user with minimum object, field, and record access. Never an administrator. The agent cannot exceed this, whatever the conversation.
- Validation rules and code checks. Enforced regardless of what the model decided.
- Action existence. The most absolute guardrail is not building the action. An agent cannot delete accounts if no delete action exists.
- Conditional action availability. In Agent Script you can attach an
available whencondition to an action. When it evaluates false, the action is removed from the tool list presented to the model entirely, so the model cannot call something it cannot see. This is a platform gate, not a request in a prompt, which puts it in a completely different reliability class from an instruction saying "do not do this yet." One design rule comes with it: never let the model set the variable the gate depends on, or you have reintroduced the variability the gate existed to remove. - Typed, constrained inputs. A picklist beats a text field.
- The Einstein Trust Layer, providing toxicity detection, an audit trail, and zero data retention by the model provider. Infrastructure, not a substitute for design. Note that Salesforce documents data masking for large language models as currently disabled for agents, so do not treat it as the control that keeps sensitive fields away from the model. Withholding field access from the running user is the control that actually holds.
- Instructions. Useful and necessary, and the weakest item on this list. Use them for behaviour and tone, not for constraints that must never be broken.
Write instructions as policy statements. "Always verify identity before discussing balances" is actionable. "Be helpful" is decoration. And state prohibitions explicitly, because an agent given only positive guidance will fill the gaps itself.
Grouping actions into subagents
A subagent is a category of actions for one job to be done, with its own instructions. The grouping decision drives routing quality.
- One job per subagent. Order status. Returns. Billing questions. Not "customer service".
- Distinct, specific descriptions. Same principle as actions: if two subagents sound similar, routing becomes a coin flip.
- Three to seven actions each is a reasonable working range. Many more than that and the planner has too many similar options.
- An explicit escalation path per subagent, and treat escalation as a legitimate outcome. An agent with no honourable exit will invent an answer instead.
Testing actions and agents
Two distinct layers, and both are needed.
Test the action as code. Apex unit tests, Flow debug runs. Cover the failure paths and the boundaries: the refund exactly at the limit, the order that does not exist, the record the running user cannot see. This is where you catch the bugs that would otherwise appear as strange agent behaviour.
Test the agent as a system. Agentforce Testing Center runs sets of utterances against your agent and checks the outcomes. Build the set from real transcripts and support tickets, not from imagination, and include:
- The straightforward request, phrased several ways.
- Requests that should route to a different subagent, to check boundaries.
- Requests the agent should refuse or escalate.
- Ambiguous requests needing a clarifying question.
- Requests missing required information.
- Attempts to talk the agent past its limits.
Re-run the whole set on every change. Agent behaviour is not local: adding an action changes routing for existing ones, because the planner now has one more option to weigh. Without a regression set you will not notice until a user does.
Deploying agents between orgs
Agents are metadata and move like anything else, but the composition is worth knowing.
| Component | What it holds |
|---|---|
AiAuthoringBundle | The agent blueprint, including its Agent Script. This is what you edit |
GenAiPlannerBundle | The reasoning engine: subagent map, orchestration graph, instructions, actions. One per agent |
GenAiPlugin | A subagent |
GenAiFunction | An action |
Bot and BotVersion | Appear once a version is committed. A draft agent is the authoring bundle alone |
The CLI provides an Agent pseudo type as shorthand for all components of a published agent:
sf project retrieve start --metadata Agent:My_Agent --target-org sandbox
sf project deploy start --metadata Agent:My_Agent --target-org production
For a source-controlled pipeline, keeping the authoring bundle is usually enough. Deploy it, publish, then activate:
sf project deploy start --metadata AiAuthoringBundle --target-org my-org
sf agent publish authoring-bundle --api-name MyAuthoringBundle --target-org my-org
sf agent activate --api-name MyAuthoringBundle --version 2 --target-org my-org
If you want no manual commit step, keep both AiAuthoringBundle and GenAiPlannerBundle in source control and deploy them together. The platform recognises the script alongside its realised metadata and skips the commit, so the agent is live immediately after deployment.
Two traps to avoid:
- Deploying agent metadata does not deploy the Apex classes and Flows behind the actions. Include them explicitly or the deployment fails, or worse, succeeds into an agent whose actions are missing.
- Do not use wildcards for ApexClass, Flow, or prompt template types. It pulls the entire org and leads to very long deployments and timeouts. List the components your agent actually uses.
Note also that GenAiPlannerBundle replaced the earlier GenAiPlanner type from API version 64.0, so older pipelines and blog posts you find may reference the legacy type.
For the surrounding architecture, see the Agentforce and Data Cloud reference design.
Frequently asked questions
Should Agentforce actions be built in Flow or Apex?
Default to Flow for record work, short orchestration, and simple decisions, because it is declarative, visible to admins, and easier to revise as you learn what users ask. Use Apex when logic cannot be expressed cleanly in Flow, when you need real error handling on callouts, when volumes are high, or when you want unit tests you control. Use a prompt template only when the output is generated language rather than an exact value.
Why does my agent call the wrong action?
Almost always the descriptions. The planner selects actions by reading them, so two actions with overlapping descriptions produce coin-flip routing. Write descriptions that state what the action does, when to use it, and explicitly what it does not do, and make sure any two actions are distinguishable from their descriptions alone. Also check that subagents are scoped to one job each.
What permissions does an Agentforce action run with?
Actions execute as the agent running user and are subject to that user’s object, field, and record access. Create a dedicated user with the minimum access needed and never reuse an administrator. In Apex, always declare classes with sharing, since running without sharing gives the agent access to every record in the org and defeats the point of a limited running user.
How do I stop an agent from performing an action twice?
Assume the planner can retry, because it can after ambiguous responses, timeouts, and conversational restarts. Make side-effecting actions idempotent using a key derived from business context, check current state before acting so a refunded order is not refunded again, and enforce limits in code rather than only in instructions. Require human confirmation for anything that moves money or sends external communication.
Are instructions enough to keep an agent within limits?
No. Instructions are strong suggestions to a probabilistic system, useful for behaviour and tone. Anything that must never happen belongs in enforcement: the running user’s permissions, validation rules, code checks, constrained input types, or simply not building the action at all. The most absolute guardrail is that an agent cannot do something it has no action for.
How do I deploy an Agentforce agent from sandbox to production?
Use the CLI with the Agent pseudo type as shorthand, for example sf project deploy start --metadata Agent:My_Agent, or deploy the AiAuthoringBundle then publish and activate it. Critically, deploying agent metadata does not include the Apex classes and Flows behind the actions, so list those explicitly, and avoid wildcards on those types because pulling the whole org causes timeouts.