Installation
Add the package with Swift Package Manager (Xcode: File ▸ Add Package Dependencies…):Quick start: Interaction API
interaction.eventId is the event’s id (a UUID unless you pass eventId). Use it for signals.
Updating an interaction
Resuming an interaction
beganAt so the event keeps its original time.
Single-shot tracking (trackAi)
trackAi needs input, output, or both. Use trackEvent for non-AI events:
Tracking signals (feedback)
Identifying users
Feature flags
Tracing
Spans are exported to Raindrop as OpenTelemetry (OTLP JSON). Start them from an interaction so they’re linked to its event, user and conversation:startToolSpan(_:input:)gives you aToolSpanto end yourself.trackTool(_:input:output:error:duration:)records a call that already happened.withToolrecords the result, or the thrown error, and rethrows.- Token usage is sent as raw provider counts (
gen_ai.usage.*); Raindrop computes cost. RaindropClient.startSpan,startLlmSpanandstartToolSpancreate spans outside an interaction. PasseventId:to link them to an event.span.refis aSpanRef(hex trace and span ids). Save it to parent spans recorded later, even in another process:parent: .ref(savedRef).RaindropConfiguration.tracingEnabled = falseturns all spans off.
Nesting spans automatically
A span started withoutparent: nests under the current span if that span belongs to the same event. withSpan, withTool and inlineSubagent make their span current while their closure runs, so you don’t have to pass parents around:
- The current span is task-local:
Task {}andasync letinherit it,Task.detacheddoesn’t.Span.currentreads it, andspan.withCurrent { }sets it for any span. - Spans for a different event never nest under it, so a detached subagent (its own event) stays separate even when started inside the scope.
- An explicit
parent:always wins.parent: .rootstarts a new trace.
Subagents
A subagent can be recorded in two ways. Use inline when the parent waits for the subagent as part of the same turn, and detached when it runs on its own.Inline subagents
inlineSubagent records a tool span tagged raindrop.agent.role=subagent and raindrop.subagent.name, makes it current while the closure runs, records the result (or the thrown error) and rethrows.
Detached subagents (experimental)
A detached subagent is work that reports as its own event, linked back to the turn that launched it. It can run in aTask.detached, on another thread, or in another process. The link is just ids, carried on span attributes (raindrop.handoff.*).
subagent(name:)records alaunch_subagenttool span on the parent with the child’s event id. Later spans in that turn are opted in so a turn whose only content is launches still becomes an event.beginSubagent(_:)opens the child’s interaction underhandoff.childEventId. Every span started from it carriesraindrop.handoff.parentEventId,parentSpanId,nameandraindrop.agent.role=subagent. The child’s user and conversation default to the parent’s.- End the child with
finish(output:),fail(_:)(records an erroredai.subagent.failedspan if none of its spans errored, and outputsAborted: …) orcancel(reason:)(setsraindrop.handoff.terminal=cancelledon the child event). SubagentHandoffis plainCodabledata. To run the child in another process, encode it and send it however you like. You can also build one from ids you stored yourself:SubagentHandoff(parentEventId:parentSpanId:childEventId:name:).- Only accept a handoff from code you trust: it decides which event the child’s data is attributed to.
- Not included: the
x-raindrop-handoff/traceparentheader carrier used by the TypeScript and Python SDKs. To hand off to a service using those SDKs, pass the ids yourself.
Configuration
Retries: rate limits (429) and server errors are retried with backoff. Other 4xx responses are not retried; the payload is dropped with a warning.
How calls behave
trackAi,trackEvent,begin, interaction updates,identifyandtrackSignalonly add to an in-memory queue and return immediately. They never wait on the network and never throw.- Queued data is sent in the background about once a second.
finish(),flush()andclose()send what’s queued and return once it’s delivered, or after 10 seconds (flushDeadline).- Call
await raindrop.close()before your process exits (for example at the end of a serverless handler).
Apps (iOS, tvOS, watchOS, visionOS)
- When the app goes to the background, the SDK sends what’s queued within the time the system allows, then saves anything left to disk. Saved payloads are sent on the next launch, unchanged.
- Offline storage is bounded (5 MB, 7 days) and excluded from backups. By default it’s readable once the device has been unlocked after a restart, so background flushing works while it’s locked again. With
storageProtection = .completeit’s unreadable whenever the device is locked: unsent data stays in memory until the next unlock, and saved data from an earlier run is sent once the device is unlocked. Data queued while locked is lost if the app is killed before then. - The package includes a privacy manifest (
PrivacyInfo.xcprivacy). It declares no tracking, and the user ID, user content and product interaction data the SDK sends. Reflect these in your app’s privacy details. - In apps there’s no process environment: set
appGitand the write key in code or from your build configuration. - A write key shipped inside an app can be extracted from it. Treat it accordingly.
Application Git provenance
raindrop.app.commit_sha, raindrop.app.commit_dirty and raindrop.app.branch. Values you set take precedence over the RAINDROP_COMMIT_SHA, RAINDROP_COMMIT_DIRTY and RAINDROP_BRANCH environment variables. Properties you send yourself always win. Only full 40- or 64-character SHAs are used. Set appGit = nil to turn this off. The SDK never inspects a Git checkout itself.
Raindrop Workshop
SetRAINDROP_WORKSHOP=1 (or localWorkshopURL) to mirror every request to a local Workshop daemon at http://localhost:5899. With no write key, the client sends to Workshop only. On a physical device, point localWorkshopURL at your Mac’s address, and allow local networking in your app’s App Transport Security settings (NSAllowsLocalNetworking).
Validating your setup
Known limitations
- No automatic instrumentation of LLM clients: record interactions and spans with the APIs above.
- Detached subagents are experimental, and there’s no header carrier (
toHeaders/fromHeaders) yet; pass the ids explicitly. trackAidoesn’t create a span on its own; use the tracing APIs.- The SDK doesn’t read Git or CI metadata automatically; pass build identity explicitly.
resumeInteractionin a new process can’t recover fields set earlier in another process. Updates merge into the stored event server-side.