How to Build Custom Tool Functions for Your AI Agent in ResolveKit
When your users hit a problem your AI agent can't solve, the instinct is to either expand the system prompt or file a ticket for engineering. Neither is satisfying. Tool functions give you a third option — teach the agent to take action inside your product on behalf of the user, with the right guardrails.
This guide covers what tool functions are in ResolveKit, how to define them in Swift and Kotlin, and how to attach the approval policies that determine when they run autonomously vs. pause for human review.
What Is a Tool Function?
A tool function is a named capability your AI agent can call during a conversation. Think of it as an API endpoint the agent knows how to invoke — with arguments it extracts from the conversation, a result it can interpret, and a policy that governs when it runs.
Tool functions let you extend the agent's reach beyond answering questions. Common examples:
- Look up order status and relay the result to the user
- Apply a discount code to a pending cart
- Reset a user's session or regenerate an API key
- Fetch a user's subscription tier and surface plan-relevant features
- Open a support ticket in your existing system
Without tool functions, the agent is a fancy FAQ retrieval engine. With them, it becomes a relay between user intent and product action.
Defining a Tool Function
In ResolveKit, tool functions are Swift structs or Kotlin classes annotated with `@ResolveKit`. Each function has a name, a description, a timeout, and a policy.
iOS (Swift)
import ResolveKit
@ResolveKit(
name: "apply_discount",
description: "Apply a discount code to the user's active cart",
timeout: 15,
requiresApproval: true // pauses for operator review before executing
)
struct ApplyDiscount: ResolveKitFunction {
func perform(cartId: String, discountCode: String) async throws -> ApplyDiscountResult {
// your implementation — call your backend, update the cart
let result = try await CartService.applyDiscount(cartId: cartId, code: discountCode)
return ApplyDiscountResult(success: result.success, newTotal: result.total)
}
}Android (Kotlin)
import dev.resolvekit.*
@ResolveKit(
name = "apply_discount",
description = "Apply a discount code to the user's active cart",
timeout = 15,
requiresApproval = true
)
class ApplyDiscount : ResolveKitFunction {
override suspend fun perform(cartId: String, discountCode: String): ApplyDiscountResult {
// your implementation
val result = CartService.applyDiscount(cartId, discountCode)
return ApplyDiscountResult(success = result.success, newTotal = result.total)
}
}The `description` field is important. The agent reads it to decide whether to call this function based on the user's request. Write descriptions that are specific about what the function does and what outcomes to expect.
The Three Execution Policies
Every tool function has an execution policy. ResolveKit supports three:
Auto-approved — the function executes immediately when the agent calls it. Use for low-risk, read-only operations where reversibility isn't a concern.
@ResolveKit(
name: "get_order_status",
description: "Look up the current status of an order by order ID",
timeout: 10,
requiresApproval: false,
policy: .autoApprove
)
struct GetOrderStatus: ResolveKitFunction { ... }Requires approval — the function pauses before executing. The operator dashboard receives the request, shows the conversation context, and the operator approves, rejects, or modifies before anything runs. The agent waits.
@ResolveKit(
name: "apply_discount",
description: "Apply a discount code to the user's active cart",
timeout: 15,
requiresApproval: true
)
struct ApplyDiscount: ResolveKitFunction { ... }Blocked — the function cannot be called under any circumstances, regardless of what the agent decides to do. Use this for destructive or irreversible actions you never want the agent attempting.
@ResolveKit(
name: "delete_account",
description: "Permanently delete a user account and all associated data",
timeout: 30,
requiresApproval: false,
policy: .blocked
)
struct DeleteAccount: ResolveKitFunction { ... }Connecting Functions to the Agent
Register your tool functions when you initialize the ResolveKit runtime:
let runtime = ResolveKitRuntime(configuration: ResolveKitConfiguration(
apiKeyProvider: { "rk_your_api_key" },
functions: [
GetOrderStatus.self, // auto-approved, runs immediately
ApplyDiscount.self, // requires operator approval
DeleteAccount.self, // blocked at policy level
]
))val runtime = ResolveKitRuntime(configuration = ResolveKitConfiguration(
apiKeyProvider = { "rk_your_api_key" },
functions = listOf(
GetOrderStatus::class, // auto-approved
ApplyDiscount::class, // requires approval
DeleteAccount::class // blocked
)
))The SDK handles the waiting state when a function requires approval. If the operator doesn't respond within the configured timeout, the agent either fails the action gracefully or escalates to a human, depending on your policy settings.
Policy-Level Configuration
Annotating individual functions works for a small set, but as you expand the agent's capabilities, you'll want to manage policies at scale.
ResolveKit supports policy-level overrides that apply across all functions or scope them by context:
let policy = ApprovalPolicy(
default: .autoApprove,
overrides: [
.functionCategory(.financial): .requiresApproval,
.functionCategory(.accountManagement): .requiresApproval,
.userRole(.admin): .autoApprove,
.appVersion("..<1.2.0"): .blocked,
]
)This lets you tune behavior without annotating every function individually — change a rule once, and the SDK enforces it across the board at runtime.
What to Build First
If you're deciding which tool functions to implement first, start with what appears most often in your support tickets. Look for patterns like:
- "Where's my order?" → `get_order_status`
- "Can I get a discount?" → `apply_discount`
- "I need to reset my password" → `reset_password`
- "How do I upgrade my plan?" → `get_subscription_tier`, `update_subscription`
These are high-frequency, low-latency actions. Auto-approve the read operations. Route the write operations through approval flows until you've built confidence in the agent's judgment.
Get Started
Tool functions are available in the ResolveKit iOS and Android SDKs. The operator dashboard handles approval routing, trace logs, and policy enforcement — you define the functions and the rules; the SDK enforces them at runtime.
- ResolveKit SDK documentation — tool function reference
- iOS SDK on GitHub — sample app with tool function examples
- Android SDK on GitHub — the Kotlin equivalent
Ready to start building? See pricing and get an API key.