Agent Script language reference — blocks, access and default_agent_user, variables and linked sources, subagents, reasoning and tools, before and after reasoning, utils, conditions, templates, operators, and where the docs and the compiler disagree

The Agent Script language that Agentforce agents are written in (.agent files in an aiAuthoringBundle), checked against Salesforce's open-source linter and compiler — required blocks, the access block that now holds default_agent_user, variables and the three linked sources, subagents, tools, before and after reasoning, the four @utils, if/else (no nested if), templates, operators, and where Salesforce's own docs, recipes and CLI templates disagree.

markdownreferencev1AV61-ZZD7stage: buildtrust: officialservice: agentforceinterface: agent-scriptAgent Script.agent fileAiAuthoringBundleconfig blockdeveloper_nameagent_typeAgentforceServiceAgentAgentforceEmployeeAgentaccess blockdefault_agent_user deprecated in configvariables blockmutablelinkedsource @MessagingSession.Id@MessagingEndUser.ContactId@VoiceCall.Idsystem blockconnection messagingOmniChannelFlowlanguage blockmodel_configstart_agent agent_routersubagenttopic keyword deprecatedreasoning instructions -> and |reasoning actions toolsbefore_reasoningafter_reasoning@utils.transition to@utils.escalate@utils.setVariables@utils.end_sessionsubagent as a tool returnsavailable whenwith ...set @outputsrun @actionsif elseelse ifnested if unsupportedtemplates {!}operators no multiplynaming rules

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

Agent Script

An agent is one .agent file inside an authoring bundle: aiAuthoringBundles/<Name>/<Name>.agent plus <Name>.bundle-meta.xml (bundleType AGENT). A complete, linted agent is in guides/agentforce/build-agent-with-agentforce-dx.md.

Check it offline: Salesforce publishes the parser, linter and compiler as open source (github.com/salesforce/agentscript). The hub builds it locally (work/tools/README-aslint.md). sf agent validate authoring-bundle compiles on Salesforce's servers and remains the final word.

Blocks

BlockRequiredHolds
configYesdeveloper_name (letters, digits and single underscores; starts with a letter, no trailing underscore, 80 characters at most), agent_label, description, agent_type (AgentforceServiceAgent or AgentforceEmployeeAgent, among others), runtime and file_upload sub-blocks
accessService agents only (omit for employee agents)default_agent_user (the agent user's username), sharing_policy, verified_customer_record_access
variablesNoState (below)
systemYesinstructions, plus messages.welcome and messages.error
connection messagingFor @utils.escalateescalation_message, outbound_route_type: "OmniChannelFlow", outbound_route_name (an Omni-Channel flow in the org), adaptive_response_allowed
languageNo (required for voice)default_locale, additional_locales
model_configNomodel: "model://sfdc_ai__Default…"; also allowed per subagent
start_agent <name>Yes ("No start_agent block found" otherwise)The router: description and reasoning with transition tools
subagent <name>At least onedescription, optional label, system, actions, before_reasoning, reasoning, after_reasoning
connected_subagent <name>No (beta)Another agent as a tool: target: "agentforce://<id>_<Name>"

default_agent_user goes in access. In config it still works but the linter warns: "'default_agent_user' is deprecated: Property default_agent_user has moved from config to access. Move field to access block." A service agent without it fails: "'default_agent_user' is required for AgentforceServiceAgent type agents." Create the user with sf org create agent-user (reference/access/users-permissions-and-agent-users.md).

Keep the recipes' block order (config, variables, system, connection, knowledge, language, start_agent, subagents); the linter accepts others.

Variables

variables:
    account_id: mutable string = ""
        description: "Salesforce ID of the account under review"
    approved: mutable boolean = False
    session_id: linked string
        description: "Messaging session ID"
        source: @MessagingSession.Id
  • mutable: the agent reads and writes it. Give it a default. Types: string, number, boolean, object, date, timestamp, currency, id, list[T] (no nested lists).
  • linked: read-only, filled from the session. It needs a source and can't have a default, an object type or a list type. The only sources the linter accepts are @MessagingSession (Id, MessagingEndUserId, EndUserLanguage), @MessagingEndUser (ContactId) and @VoiceCall (Id).
  • visibility: "External" lets an API (the Agent API, family 5) set a variable; the default is Internal.
  • Values: True, False, None, quoted strings and numbers. true fails: "'true' is not a valid value in AgentScript. Did you mean 'True' (capitalized)?"
  • System variables: @system_variables.user_input (the latest utterance), .current_modality ("voice" or "text"), .current_connection.

Subagents, reasoning and tools

subagent review_duplicates:
    description: "Find duplicate candidates for an account"
    reasoning:
        instructions: ->
            | Ask for the account if you don't have it.
            if @variables.match_count > 0:
                | {!@variables.match_count} candidates were found.
            else:
                | No candidates yet.
        actions:
            capture_account: @utils.setVariables
                description: "Record the account ID the steward gives"
                with account_id = ...
    after_reasoning:
        if @variables.approved == True:
            transition to @subagent.confirm_merge
  • Instructions are -> (a procedure: | lines, if, run, set) or | (a plain multi-line template). A quoted string is only allowed in system. In a subagent it fails: "Expected procedure (->) or template (|) syntax".
  • reasoning.actions are tools the LLM may call. Each wraps a subagent action (@actions.x), a utility (@utils.x) or a subagent (@subagent.x). The subagent-level actions: block declares what exists (reference/agentforce/agent-script-actions-and-targets.md).
  • On a tool: with param = ... lets the LLM fill a value, with param = @variables.x or a literal binds it, set @variables.y = @outputs.z saves an output, and available when <condition> hides the tool until the condition holds.
  • before_reasoning / after_reasoning run logic with no LLM: if, set, run, transition to, but no | lines. after_reasoning runs after the reasoning loop on every request. It's skipped if the subagent transitioned away. Neither is allowed in a subagent that uses the EinsteinHyperClassifier model.
  • Names in instruction text: write {!@actions.find_duplicates}, not the bare name.

The four utilities

ToolDoes
@utils.transition to @subagent.xOne-way hand-off. In after_reasoning and other directives write a bare transition to @subagent.x. In reasoning.actions a bare transition fails: "'transition' is not a defined value".
@subagent.x used as a toolRuns that subagent, then returns to the caller
@utils.setVariablesThe LLM sets the variables named in with v = ...
@utils.escalateHands off to a person through connection messaging. The linter doesn't require the block, but without it escalation has nowhere to go
@utils.end_sessionEnds the conversation

Conditions, templates and operators

  • if / else at the top level of a procedure. A nested if fails: "AgentScript does not support nested 'if' statements. Combine conditions with 'and'/'or', or restructure the logic."
  • elif is a syntax error. else if compiles in the current open-source compiler, but Salesforce's docs say it isn't supported, so prefer and/or.
  • Templates: {!@variables.x}, with a conditional value {!@variables.s if @variables.s else "pending"}.
  • Operators: ==, !=, <, <=, >, >=, is, is not, and, or, not, +, -, . and []. Multiplication fails: "Operator '*' is not supported".

Where Salesforce's sources disagree (October 2026)

TopicWhat the hub found
default_agent_userDocs and linter: the access block. The CLI's agent template (sf project generate --template agent), the CLI's authoring-bundle boilerplate and the recipes' rules file still write it in config, which triggers the deprecation warning
Linked source for the session IDThe recipes' rules file shows source: @session.sessionID, and the ContextHandling recipe uses @messagingSession.*. Both fail: "'source' must reference one of: @MessagingSession, @MessagingEndUser, @VoiceCall"
ConditionsSpec and docs say no else if, and the spec suggests nested if instead. The linter does the reverse: it allows else if and rejects nested if
topicRenamed subagent in April 2026. topic still parses, with a deprecation warning. Older blogs and the classic metadata (GenAiPlugin) still say topic
VoiceDocs list flat modality voice fields (voice_id, outbound_speed and so on). The newest recipe uses nested language, outbound.persona_id and outbound.model.id. Let Agent Builder's Voice Settings write the block
Rules the linter doesn't enforceA mutable variable without a default, a missing system.messages, set on a utility tool, @inputs in set, and tabs all pass the linter. The recipes' guide says they fail on the server (the @inputs case "causes unknown error at deploy time"). Follow the guide

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