Build an Agentforce agent with Agentforce DX — from an empty org to a published, active agent with the Salesforce CLI, using Agent Script, simulated and live preview, publish, activate and source control

Step-by-step, CLI-only build of an Agent Script agent — turn on Einstein and Agentforce, create the agent user, generate a project and authoring bundle, write and validate the .agent file (a complete linted example), preview with simulated then live actions, publish, activate, and commit what publishing retrieves.

markdownguidev1YDT6-TKSZstage: buildtrust: officialservice: agentforceinterface: cliinterface: agent-scriptAgentforce DXbuild agent CLIsf project generate --template agentsf org create agent-usersf agent generate agent-specsf agent generate authoring-bundle --no-specsf agent validate authoring-bundlesf agent previewsf agent preview start --simulate-actions --use-live-actionssf agent preview sendsf agent preview end--context-variables--context-variables-jsonsf agent publish authoring-bundle--skip-retrievesf agent activate --versioncomplete Agent Script example

Created 23 Oct 2026 · Modified 23 Oct 2026 · 1 version

Build an agent with Agentforce DX

The example is a data-steward agent like the agent-force.cuppy.io demo's: it finds duplicate accounts and records a steward's approval. Commands were checked against CLI 2.155.7; the agent file passes Salesforce's open-source linter and compiler.

1. Prepare the org (once)

  1. In Setup, turn on Einstein (Einstein Setup), then Agentforce (Agentforce Agents). Developer Edition orgs include both. For scratch orgs, add the features to the definition (reference/orgs/orgs-and-editions.md).
  2. Give yourself the authoring permission: sf org assign permset --name EinsteinGPTPromptTemplateManager --target-org dev.
  3. Create the agent's runtime user. It gets the Einstein Agent User profile and the three agent permission sets: sf org create agent-user --target-org dev. Note the username it prints (reference/access/users-permissions-and-agent-users.md).
  4. Log in with sf org login web --alias dev. The CLI's default login works with the agent commands. If you log in through your own External Client App, it needs extra scopes (reference/agentforce/agent-errors.md, last section).

2. Create the project and the bundle

sf project generate --name steward --template agent    # or use an existing DX project
cd steward
sf agent generate authoring-bundle --no-spec --name "Steward Agent" --api-name Steward_Agent --target-org dev
  • This writes force-app/main/default/aiAuthoringBundles/Steward_Agent/ with Steward_Agent.agent and Steward_Agent.bundle-meta.xml. It needs an org even with --no-spec.
  • --force-overwrite replaces an existing local bundle.
  • With a spec instead: sf agent generate agent-spec --type customer --role "Review duplicate accounts" --company-name "Crestwave Media" --company-description "Media group" --output-file specs/steward.yaml --target-org dev writes a YAML spec with LLM-generated topics. Pass it with --spec specs/steward.yaml to get a first draft.

3. Write the agent

Replace the generated file. The generator writes default_agent_user into config, where it's deprecated. Here it sits in access, with the username from step 1:

config:
    developer_name: "Steward_Agent"
    agent_label: "Steward Agent"
    description: "Helps data stewards review duplicate accounts and approve merges"
    agent_type: "AgentforceServiceAgent"

access:
    default_agent_user: "steward.agent@<example>"

variables:
    account_id: mutable string = ""
        description: "Salesforce ID of the account under review"
    match_count: mutable number = 0
        description: "How many duplicate candidates were found"
    approved: mutable boolean = False
        description: "Whether the steward approved the merge"
    session_id: linked string
        source: @MessagingSession.Id

system:
    messages:
        welcome: "Hi, I can help you review duplicate accounts."
        error: "Sorry, something went wrong. Please try again."
    instructions: "You help data stewards review duplicate accounts. Never merge without explicit approval."

connection messaging:
    escalation_message: "Connecting you with a data governance lead."
    outbound_route_type: "OmniChannelFlow"
    outbound_route_name: "Steward_Escalation_Flow"

start_agent agent_router:
    description: "Route the steward to review or help"
    reasoning:
        instructions: |
            Select the tool that best matches the user's message. If unclear, ask one question.
        actions:
            go_review: @utils.transition to @subagent.review_duplicates
                description: "Review duplicate candidates for an account"
            go_human: @utils.escalate
                description: "Hand the conversation to a person"

subagent review_duplicates:
    description: "Find duplicate candidates for an account and record the steward's decision"

    actions:
        find_duplicates:
            description: "Find duplicate candidates for an account"
            inputs:
                accountId: string
                    description: "Salesforce ID of the account"
                    is_required: True
            outputs:
                matchCount: number
                    description: "Number of candidates found"
                summary: string
                    description: "Short summary of the candidates"
                    is_displayable: True
            target: "flow://Find_Duplicate_Accounts"

    before_reasoning:
        if @variables.account_id == "":
            set @variables.match_count = 0

    reasoning:
        instructions: ->
            | Ask for the account if you don't have it, then find its duplicates with {!@actions.find_duplicates}.
            if @variables.match_count > 0:
                | {!@variables.match_count} candidates were found. Ask the steward to approve or reject the merge.
            else:
                | No candidates yet.
        actions:
            capture_account: @utils.setVariables
                description: "Record the account ID the steward gives"
                with account_id = ...
            find: @actions.find_duplicates
                available when @variables.account_id != ""
                with accountId = @variables.account_id
                set @variables.match_count = @outputs.matchCount
            record_decision: @utils.setVariables
                description: "Record whether the steward approved the merge"
                available when @variables.match_count > 0
                with approved = ...
            done: @utils.end_session
                description: "End the conversation when the steward is finished"

    after_reasoning:
        if @variables.approved == True:
            transition to @subagent.confirm_merge

subagent confirm_merge:
    description: "Confirm the approved merge back to the steward"
    reasoning:
        instructions: ->
            | Tell the steward the merge for {!@variables.account_id} is approved and queued.

Language rules: reference/agentforce/agent-script.md. Action declarations: reference/agentforce/agent-script-actions-and-targets.md.

4. Validate, then preview with simulated actions

sf agent validate authoring-bundle --api-name Steward_Agent --target-org dev
sf agent preview --authoring-bundle Steward_Agent --target-org dev

Validate compiles on Salesforce's servers and lists errors with locations (reference/agentforce/agent-errors.md).

Preview runs a chat in the terminal. Without --use-live-actions it simulates every action from its description, so the Find_Duplicate_Accounts flow doesn't need to exist yet. Transcripts and traces: reference/agentforce/agent-observability.md.

For scripts, use a programmatic session; a bundle needs --simulate-actions or --use-live-actions:

sf agent preview start --authoring-bundle Steward_Agent --simulate-actions --target-org dev --json   # returns the session ID
sf agent preview send --session-id <id> --authoring-bundle Steward_Agent --utterance "Check account 001XX000003DHPh" --target-org dev
sf agent preview end --session-id <id> --authoring-bundle Steward_Agent --target-org dev
sf agent preview sessions

Seed variables with --context-variables 'approved=False':

  • Linked variables take the $Context. prefix. Single-quote the value so the shell doesn't expand it.
  • Values are text. Use --context-variables-json '[{"name":"match_count","type":"Number","value":2}]' for typed values.

5. Preview with live actions

  1. Deploy the actions' implementations (flows and Apex: family 9): sf project deploy start --metadata Flow:Find_Duplicate_Accounts --target-org dev.
  2. Preview with --use-live-actions. Add --apex-debug to sf agent preview to capture Apex debug logs for the Replay Debugger.
  3. Changes to local flows or Apex need a fresh deploy before the preview sees them.

6. Publish and activate

sf agent publish authoring-bundle --api-name Steward_Agent --target-org dev
sf agent activate --api-name Steward_Agent --version 1 --target-org dev
  • Publish compiles, creates the agent (or a new version), retrieves the generated metadata into the project, and deploys the bundle. Use --skip-retrieve to skip the retrieve; --verbose and --concise change the output.
  • Activate makes one version live. Only one version is active at a time. --version is the number in bots/Steward_Agent/v<N>.botVersion-meta.xml. With --json and no --version, the latest version is activated.
  • Published agents preview with --api-name Steward_Agent and always use live actions.
  • sf agent deactivate --api-name Steward_Agent takes it offline.

7. Source control and other orgs

  • Commit the bundle and the metadata publish retrieved. To move the agent to another org:
  • deploy the actions first, then the bundle and its Bot/BotVersion from one manifest (reference/dx/agentforce-metadata-types.md); or
  • publish there from the same bundle.
  • Activate it in the target, and run agent tests (guides/agentforce/test-an-agent.md). CI: patterns/github-actions-deploy-with-jwt.md.
  • Retrieve every version of a bundle with sf project retrieve start --metadata "AiAuthoringBundle:Steward_Agent*".

How the CLI does this underneath, and the scopes your own app needs: reference/agentforce/agent-errors.md.

Read this document as markdown — the same bytes an agent receives.