Give agents your app's own diagnostics and actions
Expose focused iOS app data and operations so agents can answer questions the built-in tools cannot.
Custom tools let an agent ask your app for a precise answer, such as the current debug user, or run a focused development operation. Your app implements the handler and exposes it through the same guarded remote-tool protocol as the packaged suites.
Register a Tool
import AnsightCore
struct CurrentUserTool: AnsightTool {
let userId: String
var descriptor: AnsightToolDescriptor {
AnsightToolDescriptor(
id: "app.current_user",
name: "Current User",
description: "Returns the current debug user id.",
category: "app",
policy: .read
)
}
func availability(
context: AnsightToolAvailabilityContext
) -> AnsightToolAvailability {
guard !userId.isEmpty else {
return .unavailable(
reasonCode: "user_not_signed_in",
reason: "No debug user is signed in.",
requiredState: "Signed-in debug session",
remediation: "Open the account fixture and sign in.",
retryable: true
)
}
return .availableNow
}
func execute(arguments: [String: String]) throws -> AnsightToolExecutionResult {
.success(.object([
"userId": .string(userId)
]))
}
}
try AnsightRuntime.shared.registerTool(
CurrentUserTool(userId: session.currentUserId)
)
Descriptor-only registration is also available through registerTool(_ tool: AnsightToolDescriptor, replaceExisting:), but executable app tools should provide a concrete AnsightTool.
Dynamic Availability
availability(context:) is optional because the protocol supplies .availableNow by default. Implement it when execution depends on current UI, account, database, or device state. The tool catalog publishes the result under runtime, sets executable, and re-evaluates availability immediately before execution.
Return .unavailable(...) with a stable reason code, the required state, and a useful remediation, as shown in CurrentUserTool above. An unavailable tool does not execute; callers receive the reason and retryability information instead.
The example captures userId when the tool is registered. If that value changes, register a replacement with replaceExisting: true, or implement the tool so both availability and execution read current app state. Rechecking availability does not refresh a value captured by the tool itself.
Registration API
registerTool(_ tool: any AnsightTool, replaceExisting: Bool = false): registers an executable native tool.registerTool(_ tool: AnsightToolDescriptor, replaceExisting: Bool = false): registers a descriptor-backed tool.isToolRegistered(...): checks whether a tool id is currently registered.
Specific Concerns
- Prefer narrow, app-specific operations.
- Choose the correct policy:
.read,.write, or.critical. - Do not expose broad scripting, arbitrary file access, or secret access through custom tools.
- Registered tools are still hidden and unusable until the runtime guard allows their policy.
Tool Definition Checklist
- Stable tool id, usually under an app-owned prefix such as
app. - Clear name and description
- Category and keywords that match the workflow
- Correct policy
- Arguments and result schemas when callers need structured inputs or outputs
- Explicit failure messages for invalid arguments or unavailable app state
- Stable availability reason codes, required state, remediation, and retryability when the tool has runtime prerequisites