You build a custom Apex agent action. You deploy it. You wire it into your agent. Everything looks right. Then you open the planner, and it cheerfully reports Actions: 0 — as if your action doesn’t exist. The agent never calls it, and there’s no error to chase.
I lost real hours to this one. The fix turned out to be simple, but the reason it happens is the kind of thing that’s almost impossible to guess from the symptom. Here’s both.
The symptom
You have an Apex class annotated as an invocable action, intended to be used as an Agentforce action. From the agent builder, the action either doesn’t appear in the list at all, or it appears but the planner counts zero usable actions when it runs. Your prompts that should trigger it just… don’t.
Crucially: there’s no deployment error. The metadata deployed “successfully.” That success is what sends you down the wrong path — you assume the problem is your prompt, your instructions, or the planner’s reasoning, when it’s none of those.
What’s actually wrong
An Agentforce action isn’t just your Apex class. It’s backed by a GenAiFunction — the metadata record that describes the action to the planner: its name, its inputs and outputs, and the schema the LLM uses to decide whether and how to call it.
When you deploy the Apex action via metadata, it’s easy to end up in a state where:
- The Apex class exists.
- The action is referenced in the UI.
- But the
GenAiFunctionschema is missing or incomplete, so the planner has nothing describing the action it can actually reason over.
The result is a desynchronization: the UI shows the action, but the metadata the planner consumes doesn’t describe it. So the planner, looking only at what it can reason about, correctly reports zero usable actions. The UI and the planner are reading two different sources of truth, and they disagree.
The action “exists” in the way a labeled empty box exists. The label is there; the contents the planner needs aren’t.
The fix that actually holds
The reliable fix is counterintuitive if you’re a metadata-first developer: create the action through the Setup UI rather than deploying it purely as metadata.
When you create the agent action in Setup:
- Salesforce generates the complete
GenAiFunctionschema for you, including the input/output descriptions the planner needs. - The UI reference and the underlying metadata are created together, in sync, by the same operation.
- The planner now has a real schema to reason over — and the action count reflects reality.
Concretely:
- In Setup, go to your agent and add the Apex action through the action UI rather than hand-rolling and deploying the
GenAiFunctionmetadata yourself. - Let Salesforce generate the function schema; review the generated input/output descriptions, since the planner uses those descriptions to decide when to call the action.
- Re-open the planner. The action count should now reflect the action, and prompts that match its purpose will trigger it.
Why metadata deployment bites here specifically
Most Salesforce development rewards a metadata-first, source-driven workflow — it’s reproducible and version-controlled, and you reach for it by reflex. Agentforce actions are one of the spots where that reflex backfires, because the GenAiFunction schema is generated content with parts the UI fills in for you. Deploy the pieces by hand and it’s very easy to ship a structurally valid but semantically empty function: everything that makes the deployment “succeed” is present, and the one thing the planner actually needs is not.
Once the action is created correctly through Setup, you can pull it into source and manage it like anything else. The UI-first step is about getting a complete, valid schema generated in the first place — not about abandoning source control forever.
The general lesson
When a tool reports zero of something you can plainly see exists, suspect that two systems are reading different sources of truth. With Agentforce, the UI and the planner can disagree, and the planner — the thing that actually decides what runs — is the one whose view matters. Fix the source it reads, and the phantom “Actions: 0” disappears.