ResolveKit
← Back to blog
Tutorial2026-05-18·9 min read

How to Add In-App AI Support to Your iOS App in an Afternoon

How to Add In-App AI Support to Your iOS App in an Afternoon

Most iOS apps handle support like it's 2015: users leave the app, open their email client, fill out a form, and wait three days for a response that doesn't actually solve their problem. Meanwhile, they leave a two-star review and churn.

In-app AI support changes the math entirely. When a user hits a blocker, they get instant help — without leaving your app, without waiting, and without losing context. The agent has full access to their account state, can actually perform actions on their behalf (with their approval), and resolves issues before they become retention problems.

This guide walks you through integrating the ResolveKit iOS SDK — from zero to a working prototype — in a single afternoon. No web views, no 30MB dependencies, no compromises.

What You're Building

By the end of this post, you'll have an iOS app with:

  • A native chat interface that looks and feels like part of your app
  • An AI agent that can answer questions and perform actions using your own Swift code
  • An operator approval flow for sensitive actions (the user approves before anything runs)
  • Full session logging so you can review exactly what happened in every conversation

Prerequisites

Before you start:

  • iOS 16 or later
  • Xcode 15 or later (Swift 5.9+)
  • A ResolveKit backend instance (self-host with the MIT-licensed backend, or use the managed tier at console.resolvekit.app)
  • API key from your ResolveKit dashboard
  • Your own LLM provider keys — ResolveKit does not ship with a model. You bring your own OpenAI, Anthropic, or Ollama keys

Step 1: Add the SDK

Open Xcode and go to File → Add Package Dependencies. Paste:

https://github.com/resolve-kit/resolvekit-ios-sdk

ResolveKit uses the Swift Package Manager. The SDK ships two products:

  • `ResolveKitCore` — the session and transport layer
  • `ResolveKitUI` — the ready-made chat views for SwiftUI and UIKit

Link `ResolveKitUI` in your app target. It transitively includes `ResolveKitCore`.

Step 2: Define Your First Tool Function

Tool functions are how the AI agent does things in your app — not just answers questions, but actually performs actions. You define them as Swift structs with the `@ResolveKit` macro.

Here's a function that checks a user's subscription status:

import ResolveKitAuthoring

@ResolveKit(
    name: "get_subscription_status",
    description: "Returns the current subscription tier and renewal date for the active user.",
    timeout: 5,
    requiresApproval: false
)
struct GetSubscriptionStatus: ResolveKitFunction {
    func perform() async throws -> String {
        guard let subscription = currentUser?.subscription else {
            throw NSError(domain: "SubscriptionError", code: 404, userInfo: [
                NSLocalizedDescriptionKey: "No active subscription found"
            ])
        }
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        let data = try encoder.encode(subscription)
        return String(data: data, encoding: .utf8) ?? "{}"
    }
}

And here's one that cancels a subscription — which requires the user to explicitly approve before it runs:

@ResolveKit(
    name: "cancel_subscription",
    description: "Cancels the current user's subscription. This action requires explicit user approval.",
    timeout: 10,
    requiresApproval: true
)
struct CancelSubscription: ResolveKitFunction {
    func perform(reason: String) async throws -> Bool {
        // Your billing integration here
        try await BillingService.shared.cancelSubscription(
            userId: currentUser.id,
            reason: reason
        )
        return true
    }
}

The `requiresApproval: true` parameter is the key to operator control. When the agent decides to cancel a subscription, the SDK interrupts the flow and shows the user a clear confirmation dialog. Nothing runs until they say yes.

Step 3: Initialize the Runtime

The runtime manages the session, connection, and tool dispatch. Configure it with your API key and the list of functions you want available:

import ResolveKitUI

let runtime = ResolveKitRuntime(configuration: ResolveKitConfiguration(
    apiKeyProvider: { "rk_your_api_key_here" },
    functions: [GetSubscriptionStatus.self, CancelSubscription.self]
))

Keep the runtime alive for the lifetime of your chat session. Store it in a view model, a coordinator, or a dedicated service object.

Step 4: Embed the Chat Interface

The SDK provides native chat views for SwiftUI, UIKit, and AppKit. Here's how to use them:

SwiftUI

import SwiftUI
import ResolveKitUI

struct SupportView: View {
    @StateObject private var runtime = ResolveKitRuntime(configuration: ResolveKitConfiguration(
        apiKeyProvider: { "rk_your_api_key_here" },
        functions: [GetSubscriptionStatus.self, CancelSubscription.self]
    ))

    var body: some View {
        ResolveKitChatView(runtime: runtime)
    }
}

UIKit

import UIKit
import ResolveKitUI

final class SupportViewController: UIViewController {
    private let runtime = ResolveKitRuntime(configuration: ResolveKitConfiguration(
        apiKeyProvider: { "rk_your_api_key_here" },
        functions: [GetSubscriptionStatus.self, CancelSubscription.self]
    ))

    @IBAction func showSupportChat(_ sender: Any?) {
        let chatVC = ResolveKitChatViewController(runtime: runtime)
        let nav = UINavigationController(rootViewController: chatVC)
        present(nav, animated: true)
    }
}

`ResolveKitChatView` and `ResolveKitChatViewController` handle the full lifecycle: connecting to your backend, streaming text responses in real-time, showing the approval dialog for sensitive operations, and recovering gracefully from network drops.

How the Approval Flow Works

Here's the sequence when a user asks "cancel my subscription":

1. The SDK sends the message to your ResolveKit backend

2. Your backend routes it to your LLM (with your API keys)

3. The LLM decides to call the `cancel_subscription` tool

4. The SDK intercepts the tool call, reads `requiresApproval: true`, and pauses

5. The user sees a confirmation dialog: "This app wants to cancel your subscription. Reason: [whatever they typed]. Allow?"

6. If the user taps Allow, the SDK runs your Swift code and sends the result back

7. The agent delivers the final response: "Your subscription has been cancelled. You'll retain access until [date]."

As the operator, you can see the full trace in your ResolveKit dashboard: every message, every tool call, every approval decision. If something goes wrong, you have the full context to debug it.

What to Do After Integration

Once the SDK is live, these are the highest-leverage things to do next:

1. Upload your product documentation

The more context the agent has about your specific app, the fewer hallucinated responses you get. Upload your help articles, FAQs, and screenshot annotations to the ResolveKit dashboard.

2. Expand your function library

Every action your users can take is a potential tool function. Start with the top 5 support ticket topics and work down. The more the agent can do natively, the higher your resolution rate.

3. Tune your approval policies

Not every function needs human approval forever. As you gain confidence in a function's reliability, you can flip `requiresApproval` to `false` and let it run automatically for trusted user segments.

4. Review session traces weekly

Look for patterns: where is the agent getting stuck? What questions does it consistently fail to answer? Use that to improve your function definitions and your documentation.

Why Not a Web View?

You might be tempted to use a web-based support widget — they exist, they're quick to integrate, and they work across platforms. Here's the tradeoff:

  • **App size**: Web widgets typically add 20–30MB to your binary. The ResolveKit SDK adds ~500KB.
  • **Battery**: Web views keep a browser process alive in the background. Native code doesn't.
  • **Session continuity**: Web views lose state on app switch. The SDK maintains session context across network transitions.
  • **Native access**: Web views can't call your Swift APIs. The SDK can call any function you define.

For a production app with real users and real support volume, the native SDK is the only choice that doesn't compromise on user experience.

Get Started

You can have a working prototype in under an hour. The SDK is MIT-licensed, the backend is MIT/AGPL, and the managed tier is free to start.

Start your free trial →

Or dig deeper into how operator approval flows work:

  • [Operator Approval Flows for AI Agents: A Technical Guide](/blog/operator-approval-flows-ai-agents) — trace logs, policy controls, and multi-step approvals
  • [Native SDK vs Web Widget: Why Embedded Support Wins](/blog/native-sdk-vs-web-widget) — full comparison of integration approaches

Ready to build better in-app support?

ResolveKit is an open-source SDK for embedding AI support directly in your mobile app.