integrate-agentforce-android
This skill walks a consumer through wiring the Agentforce Mobile SDK into their Android 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
agentforce-sdk/oragentforce-service/Gradle modules at the root, 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)orOrgJWTby default. They're only correct in specific situations. Recommend the path that matches the user's described use case. - Hold the
AgentforceClientfor the conversation's lifetime. Stash it in your Application or a long-lived ViewModel; if it's recreated mid-chat the conversation is lost. - Default to the latest stable public release. Use
15.130.4(Agentforce Mobile 262.1.3) for both core and voice unless the project already pins another compatible version. Keep all Agentforce artifacts on exactly the same version. - Match the project's Kotlin/Compose toolchain. On Kotlin 2.x, apply
org.jetbrains.kotlin.plugin.composeat the same Kotlin version. On Kotlin 1.9.x, preserve the compatible legacy Compose compiler configuration instead of adding the Kotlin 2.x plugin. - 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:
settings.gradle.kts/settings.gradle(root of an Android Gradle project)build.gradle.kts/build.gradle(in app module)- An
app/(or similar) module containingAndroidManifest.xml
If none is present, ask the user where the Android project root is and cd there. If the directory contains agentforce-sdk/ and agentforce-service/ modules at the root, refuse — that's this SDK's own repo (or its internal counterpart).
See references/dep-detection.md for the full Gradle setup 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.FullConfig (employee path)
- 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 (UserAccountManager) → AgentforceAuthCredentials.OAuth(authToken, orgId, userId)
- Org JWT → AgentforceAuthCredentials.OrgJWT(orgJWT)
- Salesforce Mobile SDK: scaffold
AppCredentialProviderfromreferences/snippets/AppCredentialProvider+OAuth.kt. The provider'sgetAuthCredentials()reads fromSalesforceSDKManager.getInstance().userAccountManager.currentUser— or wraps the consumer's existing token-source class if they already have one. - Org JWT: scaffold from
references/snippets/AppCredentialProvider+OrgJWT.kt. Ask for the source of the JWT (a function reference, encrypted SharedPreferences key, or a backend call) and wiregetAuthCredentials()to call into it on every invocation. Don't cache.
For both employee paths, use AgentforceMode.FullConfig(configuration). Salesforce currently exposes EmployeeAgentConfiguration as well, but the public SDK path most apps follow is FullConfig with an AgentforceConfiguration.builder(...) — that's what the README shows and it accepts every option (network, navigation, logger, theme).
Branch B — Public service agent
This is the simplest path:
- Use
AgentforceMode.ServiceAgent(serviceAgentConfiguration, agentforceConfiguration). - The SDK still requires an
AgentforceAuthCredentialProvider— for unauthenticated service agents, scaffold one that returnsAgentforceAuthCredentials.Guest(url = "<your salesforceDomain>"). Seereferences/snippets/AppCredentialProvider+Guest.kt. - 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)— for public agents going through the Agent API behind an Experience Cloud site. Most "public agent" cases should use Branch B's MIAW service-agent flow instead.PassThroughAuth(miawJWT, eventID)— only for service agents whose MIAW deployment usesAuthorizationMethod.PASSTHROUGH. The consumer's backend mints a MIAW JWT and the SDK callsfetchMIAWJWTForPassthrough(...)on demand.
Phase 2 — Pick the chat presentation point
AskUserQuestion: "Where should the chat UI live?"
- Bottom sheet (recommended) → ChatHost+BottomSheet.kt
- Full-screen Activity / route → ChatHost+FullScreenActivity.kt
- Embedded panel in an existing screen → ChatHost+EmbeddedPanel.kt
- Dialog / modal popup → ChatHost+Dialog.kt
Each option corresponds to one snippet in references/snippets/. AgentforceClient.AgentforceConversationContainer(conversation, onClose) is a @Composable — drop it inside any composable scope.
See references/chat-presentation.md for the patterns and how to remember showChat state across configuration changes (rememberSaveable).
Phase 3 — Collect config values
Based on the chosen branch:
| Branch | Required values |
|---|---|
| Employee + Mobile SDK | salesforceDomain (instance URL, e.g. https://mycompany.my.salesforce.com); agentId |
| Employee + Org JWT | Same as above, plus the JWT source (function/closure, secure storage key, or backend endpoint) |
| Public Service Agent | esDeveloperName, organizationId, serviceApiURL, plus a salesforceDomain for the Guest URL |
| Guest (via "Other") | salesforceDomain, 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 dependencies
Edit two Gradle files. See references/dep-detection.md for full details and KTS/Groovy variants.
settings.gradle.kts — add Maven repos
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
maven { url = uri("https://opensource.salesforce.com/AgentforceMobileSDK-Android/agentforce-sdk-repository") }
maven { url = uri("https://s3.amazonaws.com/inapp.salesforce.com/public/android") }
maven { url = uri("https://s3.amazonaws.com/salesforce-async-messaging-experimental/public/android") }
}
}
App-module build.gradle.kts — plugins and dependencies
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
id("kotlin-kapt")
id("kotlinx-serialization")
}
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
api("com.salesforce.android.agentforcesdk:agentforce-sdk:15.130.4")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
// Optional: voice support
// api("com.salesforce.android.agentforcesdk:agentforce-sdk-voice:15.130.4")
}
The plugin block above shows the Kotlin 2.x path. On Kotlin 2.0+, composeOptions.kotlinCompilerExtensionVersion is replaced by the org.jetbrains.kotlin.plugin.compose Gradle plugin; apply it at the project's Kotlin version and remove the obsolete composeOptions block. On Kotlin 1.9.x, do not add that plugin—keep the project's compatible Compose compiler extension.
The consumer needs:
- Min SDK ≥ 29 (Android 10).
- Compose enabled in their app module — the chat UI is
@Composable. - Kotlin ≥ 1.9.22, AGP 8.9.1+, Android Studio Meerkat 2024.3.1+. Prefer a consistent Kotlin 2.x toolchain for new projects. If dependency sync reports a Kotlin metadata mismatch, upgrade Kotlin, serialization, kapt/KSP, and the Compose compiler plugin together rather than suppressing the metadata check.
If their app is not Compose-based, surface this and ask whether they want to add Compose to the existing module. The SDK does not ship a View-based chat surface.
Phase 5 — Scaffold Kotlin files
Create the package com.<their.package>.agentforce and write the following, substituting placeholders with values from Phase 3:
| File | When | Source snippet |
|---|---|---|
AppCredentialProvider.kt |
Always | snippets/AppCredentialProvider+OAuth.kt, +OrgJWT.kt, or +Guest.kt based on Phase 1 |
AppNetwork.kt |
Always | snippets/AppNetwork.kt (OkHttp-backed Network impl) |
AppLogger.kt |
Always | snippets/AppLogger.kt (android.util.Log-backed Logger impl) |
AppNavigation.kt |
Always | snippets/AppNavigation.kt (no-op Navigation to start) |
AgentforceHolder.kt |
Always | snippets/AgentforceHolder.kt (initializes the client; lives on Application) |
AppAgentforceUIDelegate.kt |
Always | snippets/AppAgentforceUIDelegate.kt |
AgentforceChatHost.kt |
Always | one of snippets/ChatHost+*.kt based on Phase 2 |
AgentforceHolder.kt is parameterized by mode — pass the right AgentforceMode and the right conversation-starter call (startAgentforceConversation() for employee/full-config, startAgentforceServiceConversation(esDeveloperName = ...) for service agents).
The logger conforms to com.salesforce.android.mobile.interfaces.logging.Logger (methods e/i/w, no d). Wire it via .setLogger(AppLogger()) on the configuration builder.
Phase 6 — Wire it into Application
AgentforceClient should outlive any single Activity. Patch the consumer's Application subclass:
class MyApp : Application() {
lateinit var agentforce: AgentforceHolder
private set
override fun onCreate() {
super.onCreate()
agentforce = AgentforceHolder(application = this)
}
}
…and register the Application class in AndroidManifest.xml:
<application
android:name=".MyApp"
... >
If the consumer already uses Hilt or another DI framework, surface that instead — provide AgentforceHolder as a @Singleton rather than putting it on Application directly.
Phase 7 — Verify
Tell the user:
- Sync Gradle (
./gradlew :app:dependenciesor via Android Studio). Expect a clean sync. - Build:
./gradlew :app:assembleDebug. If it fails with duplicate classes, confirm every Agentforce artifact uses15.130.4and inspect./gradlew :app:dependencyInsight --dependency agentforce-sdk. - Holder lifetime: confirm
AgentforceHolderis owned at theApplicationlevel (or as a Hilt singleton). If it's instantiated inside an Activity, the conversation will reset on rotation. - Run on device/emulator, navigate to the chat surface, send a test utterance, watch for streamed response.
- Logs: in Logcat, filter on tag
AgentforceSDK(the default for the scaffoldedAppLogger) to see SDK loglines. - Service Agent only: if
AuthorizationMethod.USERVERIFIEDorPASSTHROUGHwas chosen, remind the user to implementfetchMIAWJWTForPassthrough(...)andgetIdentityToken()on theirAgentforceAuthCredentialProvider.
If the build fails, common causes:
- Missing
kotlin-kaptorkotlinx-serializationplugins. - Missing core library desugaring on Android Gradle Plugin <8.x.
- Compose not enabled in the consuming module.
- Salesforce Maven repo not added to
settings.gradle.kts. AppAgentforceUIDelegatemissing the currentdidReceiveResponse(message, conversation)callback.Navigationimplementation missing the target-awaregoto(destination, target, replace)overload.
References
references/auth-flows.md— full credential-flow decision tree, includingGuest,OrgJWT, andPassThroughAuthedge cases.references/client-setup.md—AgentforceClient.init(...), mode selection, holder pattern, conversation lifecycle.references/logger-setup.md—LoggerandNetworkinterface conformance.references/chat-presentation.md— bottom sheet / full-screen / embedded / dialog Compose patterns.references/dep-detection.md— Gradle KTS/Groovy variants and Compose enablement check.references/snippets/*.kt— file templates with{{PLACEHOLDERS}}to substitute.