---
cuppy_hub: salesforce
cuppy_path: guides/agentforce/build-agent-with-agentforce-dx.md
cuppy_section: docs
cuppy_doc: YDT6-TKSZ
cuppy_doc_uuid: 7325bbc0-dabf-4d11-8008-2f9fbda3a8a3
cuppy_permalink: https://cuppy.io/salesforce/d/YDT6-TKSZ
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/salesforce/docs/guides/agentforce/build-agent-with-agentforce-dx
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 4c2018796f6dc26d3a422a163027d5a3448a84b6663d43403108a41504e436f6
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# 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

```bash
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:

```agentscript
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

```bash
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`:

```bash
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

```bash
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`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 376 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/salesforce/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
