Graine AI

Architecture and guarantees

The in-app platform from an engineer's seat — its surfaces, the two engines and how their audio travels, how a conversation is admitted, what data leaves the device and what is kept, the limits, what fails and how, and what each SDK does today.

This page is the platform as a whole, for the engineer who signs off on it: what runs where, what crosses the network, what is promised and where the edges are. To build first and read later, start with Integrate in an afternoon.

The pieces

The customer's phone — your appYour codethe screen · who the customer is · variablesyour action handlers · your call buttonIn-app enginehosted page, invisible viewthe bar's voice:WebRTC, or WebSocketcomponents on screenupdates with no releaseNative call enginein your app's processcalls: lock screen,screen off, Bluetoothvoice onlyupdates with the SDKGraine SDKmasks personal data · orders frames · answers every action · draws your designYour agent, at Graineadmits the conversation (publishable key)hears, decides, speaks, writesasks your app for actionsYour APIcomponent content,tools — from GraineCall Historytranscript, outcome,what the app didPush serviceAPNs (iOS) · FCM (Android) — rings for callsaudio, screen, actionsaudio, screen

Two engines in one SDK, one agent behind both. Only one conversation runs at a time.

PieceWhat it isHow it updates
The SDKA library in your app: React Native, Android (no runtime dependencies), iOS (a Swift package with no dependencies) or Flutter. It masks, orders and sends what your app reports, answers the agent's action requests through your handlers, and draws the bar, the chat and components in the design chosen in the dashboardWith your app
The in-app engineA page Graine hosts, loaded idle into an invisible web view at launch. It holds the microphone and the connection for the assistant bar's voice, and plays the agentLive — a fix reaches every app with no release
The native call engineAudio in your app's own process, with the platform's echo canceller, so a call rings through CallKit on iOS and a full-screen call notification on Android, and keeps going with the screen offWith the SDK — a new store build
Your agentThe agent's web-call variant: it shares the prompt and knowledge base with the agent that takes phone calls, and is kept in step with it, but runs separately — so nothing in your app can affect your phone callingLive — every save
Your designResolved on Graine's side into light and dark tokens and served with the session; every SDK draws from themLive — the next session

Surfaces

SurfaceRuns onCarriesPlatforms
The bar, voiceThe in-app engineAudio, screen context, actions, componentsAll four SDKs and websites
The bar, chatA text connection the SDK opensText, screen context, actions, componentsReact Native and websites today
ComponentsDrawn natively by the SDK, in your designA list, plans, slots or choices; the tap returns as the customer's turnWherever the bar runs
CallsThe native call engineAudio, screen context, actions — voice onlyAndroid, iOS, Flutter; React Native with its calls package
Website embedThe same engine page, in an iframeEverything the bar carriesAny site, through the loader

Only one conversation runs at a time in an app: moving from the call to the chat ends the call, and a native call ends a conversation in the bar (which ends which).

How the audio travels

The bar loads the engine page idle at launch — no microphone, nothing dialled — and keeps a line registered, so a tap connects in about a fifth of a second when the engine is ready and under two seconds when it is not. The agent's dashboard setting (Call audio) picks the transport:

High quality (WebRTC)Standard (WebSocket)
AudioOpus to Graine's media servers, with a relay (TURN) for mobile networksRaw 16 kHz PCM on the conversation's connection
LossAdaptive jitter buffer and loss concealmentNone — a lossy network sounds lossy
EchoThe device's own cancellerA level-based guard in the page
Screen contextA channel of its own, joined to the callThe same connection

The engine connects on the first network route that works within 1.5 seconds. If WebRTC cannot be used, the call moves to the WebSocket and says why (onTransport): not_configured, unreachable, screen_channel or media — a call that answered with no audio path is caught within 8 seconds. A network drop mid-call holds the call as reconnecting for up to 30 seconds and picks it up on the same session. Voice

A native call opens its own connection from your app's process: over WebRTC when the app includes the SDK's WebRTC module, else over the WebSocket. WebRTC that cannot set the call up within 12 seconds hands it to the WebSocket inside the same call — no second ring — and the call says why (transportFallbackReason). The line must open within 15 seconds. Once the audio flows a call never changes road: on WebRTC a dropped connection gets one chance to recover, and a network that stays down ends the call — your app hears ended with failed. WebRTC or the WebSocket

A chat is a text connection the SDK opens for the conversation, with a single-use ticket.

How a conversation is admitted

  1. The publishable key selects the agent. It is public by design — anyone can read it out of an app or a page — and it cannot read your data or place a phone call.
  2. Allowed domains decides who may use it. A website's origin must be on the list. A native app sends no origin, so the literal entry app:// admits apps. This is why the allowlist does not protect a key shipped in an app — Privacy and security.
  3. A connection ticket is minted per conversation — single-use, valid for about two minutes, not replayable — and session and ticket requests are rate-limited per key and per address.
  4. The agent must be switched on and your workspace must have credit. A conversation that cannot start never starts; one already running is never cut off by these checks.

Who the customer is

identify() takes the traits your app knows — a name, a plan — and the SDK masks them on the device like everything else. They become prompt variables (Hi {name}) and the line "who you are talking to" in the agent's context.

The one exception is the identifier, the trait the agent's identity setting names: it is sent as it is, only to identify the customer, and never becomes a prompt variable. Graine stores the identifier your app sends so your team can see who each conversation was with — Call History shows it, such as Email · asha@acme.example — and the memory service only ever receives a one-way key derived from it (Identity and users).

Traits are what the device says. With a publishable key and no server of yours in the path, nothing verifies them. Use them to personalise, never to authorise: before the agent discusses an account, have it confirm the customer through one of your actions, which runs in your signed-in app.

Data handled

DataLeaves the deviceMaskedKept
Screen context you reportAs it changes, while a conversation is openOn the device, then again at GraineShapes the conversation; not kept as a record of its own afterwards
Traits and variablesWhen a conversation starts, and as they changeOn the deviceOn the conversation, masked
The identifierWhen a conversation startsNo — sent as it is, to identify the customerOn the conversation, so Call History shows who it was with; memory receives only a one-way key derived from it
The customer's voiceOnly while a call is connected—As the transcript, in Call History
Typed messagesIn a chat—As the transcript
Your action resultsWhen your handler answersNot by the SDK — it sends what your handler returns, so keep identifiers out of messageOn the record: action, arguments, outcome, your message
Component answersOn the tapNot by the SDKIn the conversation and at your webhook
Connection telemetryDuring a call: transport, quality, networkNothing personal in itQuiet rows in Execution Logs, never spoken

Masked means PAN, Aadhaar, card and account numbers, phone numbers and email addresses become [PAN], [AADHAAR], [CARD], [PHONE] and [EMAIL] before anything leaves the phone, with your own patterns added through the masking rules. A masked value stays masked everywhere after it: there is no unmasked copy to recover. Component content and tools are fetched from Graine's servers, never the phone, so a key in a request header never ships in your app; endpoints must be public https and get 8 seconds.

What the SDK never does: read the screen on its own (no screen recording, no accessibility scraping), open the microphone before a call starts, or perform an action your app did not register.

Limits

Limit
Variables on a conversationUp to 12, names up to 40 characters, values up to 200, about 1 KB in all — past that, the biggest are left off first, who_called and call_reason last
An action's answer6 seconds, then the agent is told it did not finish
Actions an agent offers the model24
A component's endpoint8 seconds, then the authored content is shown
Speaking up unpromptedNever while it is talking or a reply is on the way; by default not within 20 seconds of the last time, and at most three times a conversation
Product events kept for contextThe last 10
The bar after the app leaves the screenEnds after 2 seconds, unless you turn endOnBackground off
The bar through a network dropHeld for 30 seconds
Calls at onceOne per device
A call's ring30 seconds unless you set another
Opening a call's line15 seconds

When something fails

What failsWhat the SDK doesWhat the customer seesWhat you see
The key is rejected, or app:// is missingNothing is drawnNo launcheronError, and a 401 or 403 with a code — Voice → When rtc cannot be used
The engine page will not loadReloads it on a bounded, jittered ladder"Connecting…", then a retryconnectivity.state, and retry()
WebRTC cannot connectMoves the call to the WebSocketNothing differentonTransport with the reason
The network drops mid-call (bar)Holds the call, then resumes it on the same session"Reconnecting…"reconnecting; a call record row
WebRTC cannot set a native call upContinues it on the WebSocket, inside the same callConnecting… a moment longertransport websocket, transportFallbackReason
The network drops mid-call (native call)Ends the call — on WebRTC after one try to recoverThe call endsended with failed
The microphone is refusedNever falls back, never prompts againYour explanationonMicDenied / micDenied
A handler is slow, throws, or is missingAnswers for it: timeout, error or refusedThe agent says it was not doneThe action on the record, with its outcome
A component's endpoint failsShows the authored contentThe component, as designedThe failure on the record
The app leaves the screenEnds the bar's call after 2 seconds; a native call carries on—onEnded

Guarantees

  • Masking runs before the network, the same way everywhere. Every SDK applies the same rules, checked against one shared set of test cases, in every script — Devanagari next to an identifier included.
  • Every action request gets exactly one answer — yours, or a timeout, error or refused given on its behalf. The agent never waits forever and never claims something your app did not report.
  • Screen frames are ordered. Each carries a sequence number, so a late frame never replaces a newer one, and the latest screen is sent again when the connection rejoins and when the app returns to the foreground.
  • Ending ends it. Hanging up is idempotent from every path, and a page that never confirms is torn down.
  • One conversation at a time, so the agent never answers twice — once aloud, once in writing.
  • Your design is legible. Every text colour a design resolves to is checked for contrast, in light and dark.
  • Older SDKs keep working. New fields are optional: an SDK that does not know a component look draws Cards, and one older than a design feature draws without it — a plain surface instead of windows, say.

What each SDK does today

React NativeAndroidiOSFlutterWebsite
Voice in the bar, WebRTC or WebSocket✓✓✓✓✓
Text chat✓——Your own chat connection, drawn by the bar✓
Components while the bar is on a call✓✓✓✓✓
Components in a chat✓———✓
Components during a native call—————
Screen context and masking✓✓✓✓Through page messages
The screen reported from your navigator by itself✓ (React Navigation)————
Taps tracked, rage taps✓————
Friction signalsAll sixIdle on a step, during a callIdle on a step, during a callIdle on a step, during a call—
Pointing at an element✓————
Native callsWith the calls package✓✓✓—
Native calls over WebRTC (opt-in)With the calls package, built with WebRTCWith inapp-android-rtcWith GraineInAppRTCBuilt with WebRTC—

Where next

On this page