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
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)
- 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). - Give yourself the authoring permission:
sf org assign permset --name EinsteinGPTPromptTemplateManager --target-org dev. - 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). - 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/withSteward_Agent.agentandSteward_Agent.bundle-meta.xml. It needs an org even with--no-spec. --force-overwritereplaces 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 devwrites a YAML spec with LLM-generated topics. Pass it with--spec specs/steward.yamlto 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
- Deploy the actions' implementations (flows and Apex: family 9):
sf project deploy start --metadata Flow:Find_Duplicate_Accounts --target-org dev. - Preview with
--use-live-actions. Add--apex-debugtosf agent previewto capture Apex debug logs for the Replay Debugger. - 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-retrieveto skip the retrieve;--verboseand--concisechange the output. - Activate makes one version live. Only one version is active at a time.
--versionis the number inbots/Steward_Agent/v<N>.botVersion-meta.xml. With--jsonand no--version, the latest version is activated. - Published agents preview with
--api-name Steward_Agentand always use live actions. sf agent deactivate --api-name Steward_Agenttakes 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/BotVersionfrom 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.