19 KiB
Agent User Setup & Permission Model
Complete provisioning workflow for Einstein Agent Users and permission sets. Validated against ORM1, ORM2, AutomotiveSupport, and SalesforceProductAssistant agents.
License Requirement
PID_DigitalAgent (typically included with Agentforce licenses)
Agent Type Decision Matrix
| Aspect | AgentforceServiceAgent | AgentforceEmployeeAgent |
|---|---|---|
| Use Case | Customer-facing, external users | Internal employees |
| Runs As | Dedicated Einstein Agent User | Logged-in user |
| Einstein Agent User? | Required | Not needed |
System PS (AgentforceServiceAgentUser) |
Required | Not needed |
Custom PS ({AgentName}_Access) |
Assigned to agent user | Assigned to employees |
default_agent_user in config |
Required | Omit entirely |
| Respects Sharing Rules | No (consistent permissions) | Yes (user's data access) |
How to check agent type: Look at the agent_type field in the config: block of your .agent file, or query: sf data query --json --query "SELECT DeveloperName, Type FROM BotDefinition WHERE DeveloperName = 'AgentName'" -o TARGET_ORG
CLI Fast Track: Complete Workflow
For CLI-first workflow (tested: ~8 minutes total):
# Step 1: Query existing Einstein Agent Users (30 seconds)
sf data query --json \
--query "SELECT Id, Username, IsActive FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true" \
-o TARGET_ORG
# Step 2: Create Einstein Agent User (2 minutes)
# Get Profile ID (read result.records[0].Id from JSON response)
sf data query --json \
--query "SELECT Id FROM Profile WHERE Name = 'Einstein Agent User'" \
-o TARGET_ORG
# For Production/Sandbox (non-scratch org):
# Use the ProfileId from the query above
sf data create record --json --sobject User --values \
"Username=<agent_name>_user@<orgId>.ext \
LastName=<AgentName> \
Email=admin@example.com \
Alias=<alias> \
TimeZoneSidKey=America/Los_Angeles \
LocaleSidKey=en_US \
EmailEncodingKey=UTF-8 \
ProfileId=<PROFILE_ID> \
LanguageLocaleKey=en_US" \
-o TARGET_ORG
# For Scratch Orgs (use user definition file):
# sf org create user --definition-file config/einstein-agent-user.json -o TARGET_ORG
# Step 3: Assign System Permission Set (1 minute)
sf org assign permset --json \
--name AgentforceServiceAgentUser \
--on-behalf-of <agent_name>_user@<orgId>.ext \
-o TARGET_ORG
# Step 4: Deploy Custom Permission Set (3 minutes)
# (Create the .permissionset-meta.xml file first - see Section 3.2 template)
sf project deploy start --json \
--metadata PermissionSet:<AgentName>_Access \
-o TARGET_ORG
# Assign custom PS
sf org assign permset --json \
--name <AgentName>_Access \
--on-behalf-of <agent_name>_user@<orgId>.ext \
-o TARGET_ORG
# Step 5: Verify All Permissions (1 minute)
sf data query --json \
--query "SELECT PermissionSet.Name, PermissionSet.Label FROM PermissionSetAssignment WHERE Assignee.Username = '<agent_name>_user@<orgId>.ext' ORDER BY PermissionSet.Name" \
-o TARGET_ORG
# Expected: AgentforceServiceAgentUser + <AgentName>_Access
# Step 6: Deploy Agent Bundle (unpublished metadata)
sf project deploy start --json \
--source-dir force-app/main/default/aiAuthoringBundles/<AgentName> \
-o TARGET_ORG
# Step 7: Test BEFORE Publishing (recommended)
sf agent preview start --json \
--api-name <AgentName> \
-o TARGET_ORG
# Test all subagents and actions to verify permissions
# Step 8: Publish & Activate (only after testing passes)
sf agent publish authoring-bundle --json \
--api-name <AgentName> \
-o TARGET_ORG
sf agent activate --json \
--api-name <AgentName> \
-o TARGET_ORG
Critical notes:
- For scratch orgs, use
sf org create user --definition-file - For production/sandbox, use
sf data create recordas shown above sf org create useronly works in scratch orgs — it will fail in production/sandbox- Always test with preview BEFORE publishing to avoid version management overhead
- Assign
AgentforceServiceAgentUserBEFORE publishing to prevent "Internal Error" - Publishing does NOT activate — you must run
sf agent activateseparately
Service Agent Setup (6 Steps)
Step 1: Create Einstein Agent User
Service agents need a dedicated service account with consistent permissions.
Get Org ID first (needed for username format):
sf org display --json -o TARGET_ORG
# Read result.id from the JSON response
Query existing Einstein Agent Users (skip creation if one exists):
sf data query --json --query "SELECT Id, Username, IsActive FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true" -o TARGET_ORG
Create the user (if none exists):
-
Get the Einstein Agent User profile ID:
sf data query --json --query "SELECT Id FROM Profile WHERE Name = 'Einstein Agent User'" -o TARGET_ORG -
Create a user definition file (
config/einstein-agent-user.json):{ "Username": "{agent_name}_agent@{orgId}.ext", "LastName": "{AgentName} Agent", "Email": "placeholder@example.com", "Alias": "agntuser", "ProfileId": "<profile-id-from-step-1>", "TimeZoneSidKey": "America/Los_Angeles", "LocaleSidKey": "en_US", "EmailEncodingKey": "UTF-8", "LanguageLocaleKey": "en_US", "UserPermissionsKnowledgeUser": true } -
Create the user:
Option A: Scratch Org (Definition File)
sf org create user --json \ --definition-file config/einstein-agent-user.json \ -o TARGET_ORGOption B: Production/Sandbox (Direct Record Creation)
# Get Profile ID first # Get Profile ID (read result.records[0].Id from JSON response) sf data query --json \ --query "SELECT Id FROM Profile WHERE Name = 'Einstein Agent User'" \ -o TARGET_ORG # Create user directly (use ProfileId from query above) sf data create record --json --sobject User --values \ "Username='{agent_name}_agent@{orgId}.ext' LastName='{AgentName} Agent' Email='placeholder@example.com' Alias='agntuser' ProfileId='<PROFILE_ID>' TimeZoneSidKey='America/Los_Angeles' LocaleSidKey='en_US' EmailEncodingKey='UTF-8' LanguageLocaleKey='en_US'" \ -o TARGET_ORGNote:
sf org create useronly works in scratch orgs. For production/sandbox, usesf data create record. Attemptingsf org create userin a non-scratch org fails with an authorization error. -
Verify creation:
sf data query --json --query "SELECT Id, Username, IsActive FROM User WHERE Username = '{agent_name}_agent@{orgId}.ext'" -o TARGET_ORG
Username format: {agent_name}_agent@{orgId}.ext (production) or {agent_name}.{suffix}@{orgfarm}.salesforce.com (dev/scratch). Always query the target org to confirm the exact format.
Step 2: Assign System Permission Set (AgentforceServiceAgentUser)
Critical: Must be assigned BEFORE publishing the agent. Without it, publish fails with "Internal Error".
Via Setup UI:
- Setup > Permission Sets > search "AgentforceServiceAgentUser"
- Manage Assignments > Add Assignments > select the Einstein Agent User > Save
Via CLI:
sf org assign permset --json --name AgentforceServiceAgentUser --on-behalf-of "{agent_name}_agent@{orgId}.ext" -o TARGET_ORG
Verify assignment:
sf data query --json --query "SELECT Id, PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '{agent_name}_agent@{orgId}.ext' AND PermissionSet.Name = 'AgentforceServiceAgentUser'" -o TARGET_ORG
Step 3: Create Custom Permission Set for Apex Classes
The custom PS grants the agent user permission to execute your Apex invocable actions.
Naming convention: {AgentName}_Access (e.g., AutomotiveSupport_Access)
File: force-app/main/default/permissionsets/{AgentName}_Access.permissionset-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Grants access to {AgentName} Agent Apex classes</description>
<hasActivationRequired>false</hasActivationRequired>
<label>{AgentName} Access</label>
<!-- Add one entry per Apex class the agent calls -->
<classAccesses>
<apexClass>YourApexClassName</apexClass>
<enabled>true</enabled>
</classAccesses>
<!-- Repeat for ALL Apex classes referenced via apex:// in agent script -->
</PermissionSet>
Key rule: Include EVERY Apex class referenced via apex:// in your agent script. Missing even one causes "invocable action does not exist" at runtime.
Deploy the permission set:
sf project deploy start --json --source-dir force-app/main/default/permissionsets/{AgentName}_Access.permissionset-meta.xml -o TARGET_ORG
Step 4: Assign Custom Permission Set to Agent User
Via CLI:
sf org assign permset --json --name {AgentName}_Access --on-behalf-of "{agent_name}_agent@{orgId}.ext" -o TARGET_ORG
Verify both permission sets are assigned:
sf data query --json --query "SELECT PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '{agent_name}_agent@{orgId}.ext'" -o TARGET_ORG
Expected output includes both:
AgentforceServiceAgentUser(system){AgentName}_Access(custom)
Step 5: Set default_agent_user in Agent Config
In your .agent file:
config:
developer_name: "AgentName"
agent_description: "Your agent description"
agent_type: "AgentforceServiceAgent"
default_agent_user: "{agent_name}_agent@{orgId}.ext" # Service agents ONLY
Step 6: Deploy, Test, Publish & Activate
Validated workflow pattern: Deploy as unpublished metadata, test with preview, then publish only when tests pass. This avoids version management overhead during iteration.
6.1: Deploy Agent Bundle (Unpublished)
sf project deploy start --json \
--source-dir force-app/main/default/aiAuthoringBundles/<AgentName> \
-o TARGET_ORG
This deploys the agent as unpublished metadata — you can edit freely without version management.
6.2: Test with Preview (Before Publishing)
sf agent preview start --json \
--api-name <AgentName> \
-o TARGET_ORG
What to test:
- All subagents trigger correctly
- All Apex actions execute without "Insufficient Privileges" errors
- Agent responds with expected data
- No compilation errors
If testing reveals problems, edit your agent script or Apex classes, redeploy, and test again — no publish required.
⚠️ WITH USER_MODE Object Permissions: Apex using WITH USER_MODE requires the Einstein Agent User to have read access on queried objects. Class-level access alone is not enough. Missing object permissions fail silently — 0 rows, no error. If live preview returns empty but simulated works, check Setup > Profiles > Einstein Agent User > Object Permissions. Fix by adding <objectPermissions> to your custom PS:
<objectPermissions>
<allowRead>true</allowRead>
<object>Vehicle__c</object>
</objectPermissions>
See preview-test-loop.md for the complete smoke test workflow.
6.3: Publish Agent
Only publish after all tests pass.
sf agent publish authoring-bundle --json \
--api-name <AgentName> \
-o TARGET_ORG
Publishing does NOT activate. The new BotVersion is created as Inactive. You must explicitly activate.
6.4: Activate Agent
sf agent activate --json \
--api-name <AgentName> \
-o TARGET_ORG
Note: sf agent activate may not support --json in all CLI versions. It prints a plain-text confirmation.
6.5: Verify Activation
sf data query --json \
--query "SELECT Id, DeveloperName, Status FROM BotVersion WHERE BotDefinition.DeveloperName = '<AgentName>' ORDER BY CreatedDate DESC LIMIT 1" \
-o TARGET_ORG
Expected: Status = 'Active'
After publish: Any further changes require version management. Test thoroughly before publishing.
Employee Agent Setup
Employee agents run as the logged-in user. The permission model is simpler.
What You DO NOT Need
- No Einstein Agent User creation
- No
AgentforceServiceAgentUsersystem permission set - No
default_agent_userin agent config
What You DO Need
Custom permission set(s) assigned to employees who will use the agent.
Step 1: Create Custom Permission Set
Same XML template as Step 3 above. Include <classAccesses> for all Apex classes the agent calls.
Step 2: Assign to Employees
Assign the custom PS to employees (not to a service account):
sf org assign permset --json --name {AgentName}_Access --on-behalf-of "employee@company.com" -o TARGET_ORG
Or use Permission Set Groups for role-based access.
Step 3: Configure Agent Script (No default_agent_user)
config:
developer_name: "Employee_Agent"
agent_description: "Internal employee assistant"
agent_type: "AgentforceEmployeeAgent"
# NO default_agent_user — agent runs as logged-in user
Step 4: Publish
sf agent publish authoring-bundle --json --api-name Employee_Agent -o TARGET_ORG
Auto-Generated Permission Set Warning
Salesforce auto-generates NextGen_{AgentName}_Permissions when an agent is published. Do NOT rely on this PS — it is often incomplete.
ORM1 Testing Example
- Agent script referenced 4 Apex classes:
OrderManagementVerification,FraudRiskCalculator,OrderLookupService,ShipmentTracker - Auto-generated
NextGen_ORM1_Permissionsonly included 3 classes (missingShipmentTracker) - Runtime error: "invocable action track_delivery does not exist"
- Fix: Created custom
ORM1_Accesswith all 4 classes — no errors
Best practice: Always create your own custom {AgentName}_Access PS with explicit <classAccesses> for every Apex class. Ignore the auto-generated PS.
End-to-End Verification Checklist
Run this combined query to verify all setup steps for a Service Agent:
# 1. Einstein Agent User exists and is active
sf data query --json --query "SELECT Id, Username, IsActive, Profile.Name FROM User WHERE Username = '{agent_name}_agent@{orgId}.ext'" -o TARGET_ORG
# 2. System PS assigned
sf data query --json --query "SELECT PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '{agent_name}_agent@{orgId}.ext' AND PermissionSet.Name = 'AgentforceServiceAgentUser'" -o TARGET_ORG
# 3. Custom PS assigned
sf data query --json --query "SELECT PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '{agent_name}_agent@{orgId}.ext' AND PermissionSet.Name = '{AgentName}_Access'" -o TARGET_ORG
# 4. All permission sets for user (combined view)
sf data query --json --query "SELECT PermissionSet.Name, PermissionSet.Label FROM PermissionSetAssignment WHERE Assignee.Username = '{agent_name}_agent@{orgId}.ext'" -o TARGET_ORG
# 5. Agent config has default_agent_user
# Check your .agent file's config: block
# 6. Agent publishes successfully
sf agent publish authoring-bundle --json --api-name AgentName -o TARGET_ORG
Checklist:
- Einstein Agent User created and active (
IsActive = true) - Profile is "Einstein Agent User" (or "Minimum Access - Salesforce")
AgentforceServiceAgentUsersystem PS assigned- Custom
{AgentName}_AccessPS deployed with ALL Apex classes - Custom PS assigned to the agent user
default_agent_userset in.agentconfig block- Agent tested with preview before publishing
- Agent publishes without error
- Agent activated (publish does NOT auto-activate)
Common Pitfalls (Validated)
1. "Internal Error" on First Publish
- Cause: Publishing before assigning
AgentforceServiceAgentUser - Prevention: Assign system PS (Step 2) before publishing (Step 6.3)
- Result: First-time publish success (no retries needed)
2. "Insufficient Privileges" on Apex Actions
- Cause: Missing
<classAccesses>in custom permission set - Prevention: Custom PS template includes all Apex classes (Step 3)
- Result: All actions execute without permission errors
3. Testing After Publishing
- Cause: Publishing before testing, then needing version management for fixes
- Prevention: Deploy → Test → Publish workflow (Step 6.1-6.3)
- Result: No version management overhead during development
4. Wrong User Creation Command
- Cause: Using
sf org create userin non-scratch orgs - Prevention: Step 1 provides correct commands for each org type (Option A vs B)
- Result: User created successfully without authorization errors
5. Auto-Generated Permission Set Gaps
- Cause: Relying on
NextGen_{AgentName}_Permissions(often incomplete) - Prevention: Custom PS with explicit Apex access (Step 3)
- Result: All Apex classes accessible from the start
6. Forgot to Activate After Publish
- Cause: Assuming publish automatically activates
- Prevention: Step 6 splits publish and activate into separate steps with verification
- Result: Agent is both published AND activated
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| "Internal Error" on publish | AgentforceServiceAgentUser PS not assigned to Einstein Agent User |
Assign system PS (Step 2), wait 2-3 min, retry publish |
| "Insufficient Privileges" at runtime | Custom PS missing or incomplete <classAccesses> |
Verify custom PS includes ALL Apex classes, redeploy + reassign |
| "invocable action does not exist" | Apex class not in custom PS (auto-generated PS incomplete) | Create custom {AgentName}_Access with all <classAccesses> (Step 3) |
| "Invalid default_agent_user" | Username typo or user not active | Query Einstein Agent Users, verify exact username + IsActive = true |
| Agent runs but returns wrong data | Employee agent using wrong user context | Verify agent_type — Service agents use dedicated user, Employee agents use logged-in user |
sf org create user fails |
Used in production/sandbox org | Use sf data create record instead (Step 1, Option B) |
Permission Set XML Template (Complete Example)
AutomotiveSupport agent (5 Apex classes):
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Grants access to Automotive Support Agent Apex classes</description>
<hasActivationRequired>false</hasActivationRequired>
<label>Automotive Support Access</label>
<classAccesses>
<apexClass>VehicleLookupService</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>ErrorCodeDiagnosticsService</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>CheckEngineDiagnosticsService</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>BehaviorAnalysisService</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>ServiceSchedulerService</apexClass>
<enabled>true</enabled>
</classAccesses>
</PermissionSet>
Validated against: ORM1, ORM2, AutomotiveSupport, SalesforceProductAssistant agents. Last validated: 2026-03-07.