agentleFS
grzmolgrzmol/vibe-force/skills/sf-agentforce-development/SKILL.md

sf-agentforce-development

Skill2 starsChanged 2 months agoMITView on GitHub

Building, previewing, testing and shipping Agentforce agents from a source-tracked SFDX project - authoring bundles and Agent Script, the Bot/BotVersion/GenAiPlannerBundle/GenAiPlugin/GenAiFunction metadata graph, custom agent actions written as invocable Apex, autolaunched Flows or prompt templates, action input/output JSON schemas, prompt templates and grounding, the agent user and the permissions it runs under, AiEvaluationDefinition test definitions and the sf agent test commands, agent preview in simulated and live mode, trace analysis, and moving an agent from scratch org to sandbox to production. Use this skill when a story mentions an agent, a copilot, a subagent, a topic, an agent action, an authoring bundle, a .agent file, Agentforce Builder, Testing Center, Prompt Builder, an @InvocableMethod that an agent calls, or when an agent routes to the wrong action, returns data the running user should not see, or fails to activate after a deploy.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every action an agent runs executes as a dedicated Salesforce user.

    line 68
  2. Very specific:

    Only one version is active at a time, and --version is the number in vN.botVersion-meta.xml.

    line 258
  3. Very specific:

    Use Metadata to Move an Agent to a New Org - https://developer.salesforce.com/docs/ai/agentforce/guide/agent-dx-deploy-metadata.html

    line 379

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Agentforce agent development

An agent is metadata. It moves through the same retrieve/deploy/version-control loop as Apex, and it breaks in the same ways: wrong permissions, wrong deploy order, untested contracts. The parts that are genuinely new are the planner (an LLM chooses which action to run, from your prose) and the agent user (every action runs as a service user, not as the person chatting).

This skill covers the API 67.0 metadata shape pinned in config/vibe-force.defaults.json, and names what changes at 68.0. Grounding data modelled in Data 360 belongs to skill sf-data-cloud; this skill links to it rather than re-explaining data streams, DMOs or retrievers.

When to use

SituationToolWhy
Free-form user request, unknown number of steps, natural language in and outAgentThe planner picks actions at runtime; you cannot enumerate the branches
Deterministic multi-step process with known branches, admin-maintainableFlowNo LLM cost, no non-determinism, testable by construction (skill sf-flow-automation)
Transactional logic, bulk DML, callouts, anything needing exact controlApexWrap it in @InvocableMethod and let the agent call it - do not push logic into instructions
Fixed UI on a record page, known inputs, sub-second responseLWCAn agent is the wrong shape for a form (skill sf-lwc-development)
One prompt, one record, one generated field or emailPrompt template aloneNo agent needed; call it from Flow, Apex or the Connect API
Needs an auditable, repeatable, identical result every runNot an agentLLM output varies; tests score, they do not assert equality
Story says "summarise", "answer questions about", "help the rep decide"AgentThen read references/action-patterns.md before writing any Apex
TriggerGo to
"Create an agent", "new subagent", "new topic"Patterns 1-3, references/agent-cli-reference.md
"Add an action" of any kindPattern 4, references/action-patterns.md
Action description / routing quality problemAction design section, Anti-patterns
"Test the agent", "Testing Center", regression suitePattern 6, references/testing-and-evaluation.md
Moving an agent between orgs, activation, packagingPattern 7-8, references/deployment-checklist.md
"What metadata type is X", manifest authoringreferences/metadata-reference.md
Agent reads records the end user must not seeAnti-patterns, skill sf-security-model

Core patterns

1. Provision an org that can run agents

agent CLI commands fail with This feature is not currently enabled for this user type or org when Einstein or Agentforce is off. Scratch orgs turn both on declaratively:

{
  "orgName": "vf agent dev",
  "edition": "Developer",
  "features": ["EnableSetPasswordInApi", "Einstein1AIPlatform"],
  "settings": {
    "agentPlatformSettings": { "enableAgentPlatform": true },
    "einsteinGptSettings": { "enableEinsteinGptPlatform": true }
  }
}
sf org create scratch --definition-file config/afdx-scratch-def.json \
  --alias vf-agent --set-default --target-dev-hub DevHub

Sandboxes need the same two switches set manually in Setup after creation. If the agent grounds on Data 360, use a sandbox, not a scratch org - see skill sf-data-cloud.

2. Create the agent user before the agent

Every action an agent runs executes as a dedicated Salesforce user. Create it first, because the Agent Script file references it by username:

sf org create agent-user --base-username vf-agent@example.com --target-org vf-agent

The command assigns the Einstein Agent User profile plus the AgentforceServiceAgentBase, AgentforceServiceAgentUser and EinsteinGPTPromptTemplateUser permission sets, and verifies the licences exist. It does not grant object, field or record access for your data - that is a permission set you write (skill sf-security-model). The generated user has no password and cannot log in.

3. Author from a spec, not from a blank file

# 1. Spec: role + company context, LLM proposes subagents
sf agent generate agent-spec --type internal --max-topics 5 \
  --role "Answer rep questions about open opportunities and log next steps." \
  --company-name "Acme" --company-description "B2B industrial supplies" \
  --agent-user vf-agent@example.com --tone neutral \
  --output-file specs/opportunityCoach.yaml --target-org vf-agent

# 2. Authoring bundle: the .agent blueprint, written in Agent Script
sf agent generate authoring-bundle --spec specs/opportunityCoach.yaml \
  --name "Opportunity Coach" --api-name Opportunity_Coach --target-org vf-agent

# 3. Compile check, local, fast, run after every edit
sf agent validate authoring-bundle --api-name Opportunity_Coach --target-org vf-agent

The bundle lands in force-app/main/default/aiAuthoringBundles/Opportunity_Coach/ as Opportunity_Coach.agent plus Opportunity_Coach.bundle-meta.xml. A bundle generated with --no-spec is boilerplate; a thin spec produces generic subagents. Spec quality is the single biggest lever on generated output.

4. Declare an action with an explicit contract

Agent Script actions name a target by URI scheme - apex, flow or prompt - and declare typed inputs and outputs:

subagent opportunity_review:

    actions:
        summarise_pipeline:
            description: "Return open-opportunity totals and the next step for one account."
            inputs:
                accountId: string
                    label: "Account Id"
                    description: "18-character Salesforce Id of the account to summarise."
                    is_required: True
            outputs:
                summary: string
                openAmount: number
            target: "apex://OpportunityPipelineAction"

The Apex behind it obeys the invocable contract - one list in, a list of the same size and order out, USER_MODE on every query:

public with sharing class OpportunityPipelineAction {

    public class Request {
        @InvocableVariable(required=true label='Account Id'
            description='18-character Salesforce Id of the account to summarise.')
        public Id accountId;
    }

    public class Result {
        @InvocableVariable(label='Summary'
            description='One sentence naming the open pipeline value and the next step.')
        public String summary;
        @InvocableVariable(label='Open amount')
        public Decimal openAmount;
    }

    @InvocableMethod(
        label='Summarise Account Pipeline'
        description='Returns open-opportunity totals and the next step for the given account.'
        category='Sales'
    )
    public static List<Result> run(List<Request> requests) {
        Set<Id> accountIds = new Set<Id>();
        for (Request r : requests) { accountIds.add(r.accountId); }

        Map<Id, Decimal> totals = new Map<Id, Decimal>();
        for (AggregateResult ar : [
            SELECT AccountId aid, SUM(Amount) total
            FROM Opportunity
            WHERE AccountId IN :accountIds AND IsClosed = false
            WITH USER_MODE
            GROUP BY AccountId
        ]) {
            totals.put((Id) ar.get('aid'), (Decimal) ar.get('total'));
        }

        List<Result> results = new List<Result>();
        for (Request r : requests) {          // same size, same order as the input
            Result out = new Result();
            out.openAmount = totals.containsKey(r.accountId) ? totals.get(r.accountId) : 0;
            out.summary = out.openAmount == 0
                ? 'No open opportunities for this account.'
                : 'Open pipeline is ' + out.openAmount + '.';
            results.add(out);
        }
        return results;
    }
}

Full annotation rules, the Flow and prompt-template variants, and the GenAiFunction JSON schema form live in references/action-patterns.md. The general @InvocableMethod contract is owned by skill sf-flow-automation.

5. Preview before publishing

# Simulated: LLM mocks every action. Use while the Apex or Flow does not exist yet.
sf agent preview --authoring-bundle Opportunity_Coach --target-org vf-agent

# Live: real Apex, Flows and prompt templates, with Apex Replay Debugger support.
sf agent preview --authoring-bundle Opportunity_Coach --use-live-actions --apex-debug \
  --output-dir transcripts/coach --target-org vf-agent

Live mode uses whatever is deployed, so deploy the Apex first. Preview writes trace files; read them by dimension instead of guessing:

sf agent preview sessions
sf agent trace read --session-id <SESSION_ID> --format detail --dimension routing
sf agent trace read --session-id <SESSION_ID> --format detail --dimension actions

--dimension accepts actions, grounding, routing, errors. Routing answers "why did it pick that subagent"; actions answers "what did it actually call, with what inputs".

6. Publish, then test with a spec-driven suite

sf project deploy start --metadata "ApexClass:OpportunityPipelineAction" --target-org vf-agent
sf agent publish authoring-bundle --api-name Opportunity_Coach --target-org vf-agent

sf agent generate test-spec --output-file specs/Opportunity_Coach-testSpec.yaml
sf agent test create --spec specs/Opportunity_Coach-testSpec.yaml \
  --api-name Opportunity_Coach_Test --target-org vf-agent
sf agent test run --api-name Opportunity_Coach_Test --wait 10 \
  --result-format junit --output-dir .vibeforce/reports --target-org vf-agent

agent test create writes an AiEvaluationDefinition component back into the project. The agent must exist in the org before the test can be created. Expectations are scored, not asserted: topic_sequence_match and action_sequence_match are the deterministic ones and the only sensible CI gate. See references/testing-and-evaluation.md.

7. Move the agent between orgs

At API 67.0 an agent is a graph, not a file. Retrieve the whole graph the first time:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types><members>Opportunity_Coach</members><name>Bot</name></types>
    <types><members>Opportunity_Coach.v1</members><name>BotVersion</name></types>
    <types><members>Opportunity_Coach_v1</members><name>GenAiPlannerBundle</name></types>
    <types><members>Opportunity_Coach_1</members><name>AiAuthoringBundle</name></types>
    <types><members>OpportunityPipelineAction</members><name>ApexClass</name></types>
    <version>67.0</version>
</Package>
sf project retrieve start --manifest manifest/package.xml --target-org vf-dev
sf project deploy start --manifest manifest/package.xml --target-org vf-int

With source tracking on, --metadata Agent:Opportunity_Coach is a CLI-only shorthand that resolves the whole agent graph. The retrieved metadata carries the source org's agent username; replace it before deploying, or the agent cannot run in the target. Deploy order, the draft/committed distinction and post-deploy checks are in references/deployment-checklist.md.

8. Activate deliberately

sf agent activate --api-name Opportunity_Coach --version 1 --target-org vf-int
sf agent deactivate --api-name Opportunity_Coach --target-org vf-int

Only one version is active at a time, and --version is the number in vN.botVersion-meta.xml. A deployed agent is not a live agent. Activation is a separate, reversible step and belongs in the release runbook, not in the deploy command (skill sf-deployment-strategies).

Action design

The planner reads your prose. Descriptions are the routing algorithm's only input, so they are production code, not documentation.

PropertyRuleFailing exampleWorking example
Action descriptionName the job, the entity and the trigger conditionGets dataReturns open-opportunity totals for one account when the user asks about pipeline.
Input descriptionState the format and where the value comes fromThe id18-character Salesforce Id of the account; obtain it from the record lookup action first.
Output descriptionState what the agent should do with itResultOne-sentence pipeline summary; quote it verbatim to the user.
Output visibilityAt least one output must be flagged as used by the planner, or responses driftall outputs display-onlyone output marked for planner use (copilotAction:isUsedByPlanner)
OverlapTwo actions must not match the same utteranceGetAccount + FetchAccountone action, one branch inside it
GranularityOne verb, one objectManageOrder doing 6 thingsCancelOrder, RescheduleOrder

Typing rules that the runtime enforces:

  • One input parameter, one output list. Both are lists because actions are bulkified; the i-th output must correspond to the i-th input.
  • Input and output property types come from a fixed lightning__* set. lightning__textType caps at 250 characters - long prose must be lightning__multilineTextType or richTextType.
  • @InvocableVariable fields of type List<List<sObject>> cause a runtime error inside user-defined classes.
  • From API 66.0, an Apex class used for invocable parameters needs a visible no-argument constructor.

Idempotence: the planner may call the same action twice in one conversation, and a test rerun replays the same utterance. Reads are naturally safe. Writes must be made repeatable - upsert on an external id, or check for an existing record before creating one - and destructive actions should set confirmation-required so the user approves before the write.

Failure handling: an invocable method must return the same number of results as it received, even when individual items fail. Return a success flag and a message on the result object instead of throwing, so one bad input does not abort the whole batch.

Anti-patterns

Anti-patternFailing codeFix
Loop-per-request DML or SOQLfor (Request r : requests) { [SELECT ... WHERE Id = :r.id]; }Collect ids, query once, map results back by index (skill sf-governor-limits)
Returning fewer results than inputsif (bad) { continue; } inside the result loopAlways add a result; carry success=false and a reason
Vague description@InvocableMethod(label='Do it')Label and description naming entity, action and when to use it
Sharing bypassedpublic without sharing class OrderActionwith sharing + WITH USER_MODE / AccessLevel.USER_MODE; the agent user is a real user (skill sf-security-model)
Grounding on data the agent user cannot seeAction queries Contact.SSN__c; agent user lacks FLS -> empty or error at runtimeGrant the agent user exactly the object, field and record access the actions need, and no more
Instructions doing arithmetic or policyinstructions: "compute the 12% discount and apply it"Move deterministic logic into Apex or Flow; the LLM must not be the calculator
Two overlapping actionsGetCase and LookupCase both described as "find a case"Delete one; overlapping descriptions make routing non-deterministic
Publish assumed to deploy Apexsf agent publish authoring-bundle after editing a local Apex classDeploy the Apex first; publishing a bundle never deploys ApexClass or Flow
Partial retrieve of an agentsf project retrieve start --metadata AiAuthoringBundle:My_AgentAdd the wildcard (My_Agent*) for all versions, or use a full manifest
Deploying a version into an org that lacks the agentmanifest with BotVersion onlyDeploy the whole agent graph first, then versions
Treating bot_response_rating as a CI gatepipeline fails on a scored metricGate on topic_sequence_match / action_sequence_match; track scored metrics as trends
sfdx force:* syntax anywheresfdx force:source:deploysf project deploy start --target-org <alias> (skill sf-cli-operations)

Verification

Local, no org, before anything else:

sf agent validate authoring-bundle --api-name Opportunity_Coach --target-org vf-dev
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" local --changed

Apex behind the actions, in an org:

node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" apex \
  --target-org vf-dev --tests OpportunityPipelineActionTest

Agent behaviour, in an org:

sf agent test list --target-org vf-dev
sf agent test run --api-name Opportunity_Coach_Test --wait 10 \
  --result-format junit --output-dir .vibeforce/reports --target-org vf-dev
sf agent test results --use-most-recent --verbose --target-org vf-dev

Deploy gates and post-deploy state:

node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" deploy-validate --target-org vf-int
node "${CLAUDE_PLUGIN_ROOT}/scripts/checks/vf-check.mjs" smoke --target-org vf-int

# The agent and its versions exist in the target org
sf org list metadata --metadata-type Bot --target-org vf-int
sf org list metadata --metadata-type BotVersion --target-org vf-int

# The agent user is active and licensed
sf data query --target-org vf-int --query \
  "SELECT Id, Username, IsActive, Profile.Name FROM User WHERE Profile.Name = 'Einstein Agent User'"

vf-check verify runs the wave-4 verdict once the smoke probes pass (skill sf-workflow-orchestration). For a failing conversation, reproduce it with sf agent preview and read the trace before changing any instruction text (skill sf-debugging-logs for the Apex side).

References

  • references/metadata-reference.md - every agent-related metadata type, its file suffix, directory, API version floor and fields; manifests for whole agents and single versions; the v67 to v68 change.
  • references/action-patterns.md - invocable Apex, Flow and prompt-template actions end to end, GenAiFunction input/output schema properties, description quality rubric, security and idempotence.
  • references/testing-and-evaluation.md - AiEvaluationDefinition shape, test spec YAML, expectation and metric catalogue, custom evaluations with JSONPath, CI wiring, limits.
  • references/deployment-checklist.md - org prerequisites, permissions, deploy order, draft versus committed agents, activation, post-deploy queries.
  • references/agent-cli-reference.md - every sf agent, sf org create agent-user and sf org open agent subcommand with its verified flags.

Official documentation used (fetched for this skill):

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.