integrate-agentforce-ios
This skill walks a consumer through wiring the Agentforce Mobile SDK into their iOS app. It is interactive — ask the user the questions in each phase before generating code. Don't assume; the wrong auth flow is the most common integration mistake.
Operating rules
- Run inside the consumer's project, not inside the SDK repo. If the working directory contains
Sources/AgentforceSDKTarget/orPackage.swiftdeclares the productAgentforceSDKas abinaryTarget, refuse and tell the user tocdinto their app first. - Discover before deciding. Always run Phase 1 (use-case discovery) before recommending an auth flow. Don't ask "which auth flow do you want?" — most consumers don't know.
- Don't suggest
.Guest(url:)or.OrgJWTby default. They're only correct in specific situations. Recommend the path that matches the user's described use case. - Retain the client for the conversation's lifetime.
AgentforceClientmust live as long as the conversation; deallocating it loses the session. Always scaffold the client inside an@MainActorObservableObjectowned by@StateObjectat the app root. - Default to the latest stable public release. Use
18.26.17(Agentforce Mobile 262.1.3) unless the project already pins another compatible version. Use18.33.14-rc1only when the user explicitly requests the 262.2 prerelease; label it non-production. - Treat SPM as the preferred install path. Add only
https://github.com/salesforce/AgentforceMobileSDK-iOS.git; never addAgentforceMobileService-iOSseparately because the public package already bundles the matchingAgentforceServicebinary. - Use
AskUserQuestionfor branching choices. Don't free-text prompts — give 2–4 explicit options. - Substitute placeholders, don't leave
{{TOKENS}}in the final files. Collect values up front; if the user can't provide a value, leave a clearly-marked// TODO:comment instead.
Phase 0 — Detect the target project
Look in the current working directory for:
Package.swift(SPM-only project), orPodfile(CocoaPods project), or*.xcodeproj/*.xcworkspace(Xcode project; could use either)
If none is present, ask the user where the iOS project root is and cd there. If Sources/AgentforceSDKTarget/ exists at the working directory, refuse — that's this SDK's own repo.
See references/dep-manager-detection.md for the full decision tree.
Phase 1 — Discover the use case (this drives auth)
Ask first what they're building, then map to an auth flow:
AskUserQuestion: "What kind of agent are you integrating?"
- Employee agent (signed-in users, internal tools) → AgentforceMode.employeeAgent
- Public service agent (customer-facing, no sign-in) → AgentforceMode.serviceAgent
- Other / not sure → see references/auth-flows.md
Branch A — Employee agent
Ask the follow-up:
AskUserQuestion: "How are you obtaining auth credentials?"
- Salesforce Mobile SDK → AgentforceAuthCredentials.OAuth(authToken, orgId, userId)
- Org JWT → AgentforceAuthCredentials.OrgJWT(orgJWT)
- Salesforce Mobile SDK: scaffold
AppCredentialProviderfromreferences/snippets/AppCredentialProvider+OAuth.swift. The provider'sgetAuthCredentials()reads fromUserAccountManager.shared.currentUserAccount— or wraps the consumer's existing token-source class if they already have one. - Org JWT: scaffold from
references/snippets/AppCredentialProvider+OrgJWT.swift. Ask for the source of the JWT (a closure, a keychain key, or an environment value) and wiregetAuthCredentials()to call into it on every invocation. Don't cache.
Branch B — Public service agent
This is the simplest path:
- Use
AgentforceMode.serviceAgent(ServiceAgentConfiguration). - Scaffold
AppCredentialProviderfromreferences/snippets/AppCredentialProvider+Guest.swift.AgentforceClientstill receives a credential provider; for an unverified public service deployment it returns.Guest(url: salesforceDomain). - Tell the user they'll need a Messaging-for-In-App-Web (MIAW) mobile deployment in their Salesforce org first, and link the docs:
- If they don't have one yet, pause here. The skill can't proceed without
esDeveloperName,organizationId, andserviceApiURLfrom the deployment.
Branch C — Other / not sure
Walk them through references/auth-flows.md. The two extra options to surface here:
.Guest(url:)— only for Service Agent on the public Agent API behind an Experience Cloud site, when URL-only guest credentials are required. Most "public agent" cases should use Branch B instead..OrgJWT— already covered in Branch A.
Phase 2 — Pick the chat presentation point
AskUserQuestion: "Where should the chat UI live?"
- Sheet (recommended) → ChatHost+Sheet.swift
- Full-screen cover → ChatHost+FullScreen.swift
- Push in NavigationStack → ChatHost+Push.swift
- AgentforceLauncher (iOS 26+ tab bar accessory) → ChatHost+Launcher.swift
Each option corresponds to one snippet in references/snippets/. The launcher path requires iOS 26+ — gate with #available(iOS 26.0, *) and provide a sheet fallback for earlier OS targets.
See references/chat-presentation.md for the patterns and the tabViewBottomAccessory setup.
Phase 3 — Collect config values
Based on the chosen branch:
| Branch | Required values |
|---|---|
| Employee + Mobile SDK | forceConfigEndpoint (instance URL, e.g. https://mycompany.my.salesforce.com); User fields (userId, org.id, username, displayName); agentId |
| Employee + Org JWT | Same as above, plus the JWT source (closure / keychain key / env) |
| Public Service Agent | esDeveloperName, organizationId, serviceApiURL (the MIAW -scrt endpoint), salesforceDomain (used for both the guest url and the required forceConfigEndPoint) |
| Guest (via "Other") | url, forceConfigEndpoint, agentId |
Ask one question per missing value. If the user gives "I don't know" for a Service Agent value, point them back at the MIAW deployment link and stop.
Phase 4 — Add the dependency
Pick the path based on Phase 0 detection. Prefer SPM if both are present.
SPM
For Package.swift-based projects, add to dependencies:
.package(
url: "https://github.com/salesforce/AgentforceMobileSDK-iOS.git",
from: "18.26.17"
)
Add the required product to the app target:
.product(name: "AgentforceSDK", package: "AgentforceMobileSDK-iOS")
Add .product(name: "AgentforceVoice", package: "AgentforceMobileSDK-iOS") only when the app uses voice. For the 262.2 prerelease, AgentforceCustomization is also available as an optional product; pin .package(..., exact: "18.33.14-rc1") and add that product only when the app uses the standalone customization API.
Do not add AgentforceMobileService-iOS: import AgentforceService works through the bundled binary and a second package declaration creates a duplicate-target resolution error.
For .xcodeproj-only projects, walk the user through File → Add Package Dependencies in Xcode with the same URL. Don't try to edit .xcodeproj files by hand.
CocoaPods
Edit Podfile. Mirror the structure from this SDK's sample app (PlantCareCompanionSampleApp/Podfile):
platform :ios, '17.0'
target_deployment_version = '17.0'
target 'YourApp' do
source 'https://github.com/forcedotcom/SalesforceMobileSDK-iOS-Specs.git'
source 'https://github.com/Salesforce-Async-Messaging/podspecs.git'
source 'https://github.com/livekit/podspecs.git'
source 'https://cdn.cocoapods.org/'
use_frameworks!
pod 'AgentforceSDK'
pod 'Messaging-InApp-Core', '> 1.10.0'
# Optional voice support:
# pod 'AgentforceVoice'
# pod 'LiveKitClient'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
Then pod install (or pod install --repo-update if specs aren't found).
See references/dep-manager-detection.md for the full Podfile, including static-linking variants.
Phase 5 — Scaffold Swift files
Create the directory Agentforce/ at the app target root and write the following, substituting placeholders with values from Phase 3:
| File | When | Source snippet |
|---|---|---|
AppCredentialProvider.swift |
Always | snippets/AppCredentialProvider+OAuth.swift, +OrgJWT.swift, or +Guest.swift |
AgentforceConsoleLogger.swift |
Always | snippets/AgentforceConsoleLogger.swift |
AgentforceManager.swift |
Always | snippets/AgentforceManager.swift |
AgentforceUIDelegate+Default.swift |
Always | snippets/AgentforceUIDelegate+Default.swift |
AgentforceChatHost.swift |
Always | one of snippets/ChatHost+*.swift based on Phase 2 |
AgentforceManager.swift is parameterized by mode — pass the right AgentforceMode (.employeeAgent, .serviceAgent, or .fullConfig) and the right conversation-starter call (startAgentforceConversation(forAgentId:) for employee/full-config, forESDeveloperName: for service agents).
The logger is OSLog-backed (SalesforceLogging.Logger conformance, one os.Logger per LogLevel under subsystem com.salesforce.agentforce). It's wired via .withLogger(...) on EmployeeAgentConfiguration / ServiceAgentConfiguration, or via salesforceLogger: on AgentforceConfiguration. See references/logger-setup.md.
Phase 6 — Wire into the App entry point
Patch the consumer's @main App struct:
@main
struct MyApp: App {
@StateObject private var agentforceManager = AgentforceManager()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(agentforceManager)
}
}
}
If the user already has a DI container or composition root, surface that — don't force a @StateObject if their architecture has another lifetime owner. The hard requirement is only that the manager outlives every conversation it creates.
Phase 7 — Verify
Tell the user:
- Resolve dependencies: for SPM run
xcodebuild -resolvePackageDependencies -project YourApp.xcodeproj -scheme YourApp; for CocoaPods runpod install. - Build: use the project's real container:
xcodebuild -project YourApp.xcodeproj ...for SPM/Xcode projects orxcodebuild -workspace YourApp.xcworkspace ...for CocoaPods. Expect a clean build with no unresolved placeholders. - Retention check: confirm
AgentforceManageris owned at app-root (via@StateObject). If it's@Stateinside a leaf view, the conversation will drop when the view is unmounted. - Run on simulator, navigate to the chat surface, send a test utterance, and watch for a streamed response.
- Logs: open Console.app and filter on subsystem
com.salesforce.agentforce. - Service Agent only: if user verification is enabled, wire the deployment's verification delegate before testing.
If the build fails, common causes:
- Missing
BUILD_LIBRARY_FOR_DISTRIBUTION = YESon Pods (CocoaPods only). - Wrong source order in
Podfile(Salesforce specs must be first). - Static linking without the
cocoapods-user-defined-build-typesplugin. AgentforceClientinstantiated on a non-main thread.- A duplicate
AgentforceServicetarget because the app addedAgentforceMobileService-iOSseparately from the AgentforceSDK SPM package. - Swift 6 isolation errors because app-owned UI state or delegates were not kept on
@MainActor.
References
references/auth-flows.md— full credential-flow decision tree, including.Guestand.OrgJWTedge cases.references/client-setup.md—AgentforceClientinit, mode selection, retention, conversation lifecycle.references/logger-setup.md— OSLogSalesforceLogging.Loggerconformance.references/chat-presentation.md— sheet / fullScreenCover / push / launcher patterns.references/dep-manager-detection.md— SPM vs CocoaPods detection and full Podfile.references/snippets/*.swift— file templates with{{PLACEHOLDERS}}to substitute.