Graine AI

Execution Logs

Every turn of a finished call — what the agent heard, how long the model took, when the caller heard a reply — plus what was flagged, the APIs and webhooks it called, the prompt it ran on, and the raw numbers behind all of it.

A call that went wrong is rarely wrong everywhere. It is one turn where the transcriber took two and a half seconds to settle, or one where the model thought for 1.8 s, or one where the reply never reached the caller at all. The execution log is that call broken into turns, so the question stops being "why was this call slow?" and becomes "which turn, and which stage of it".

Every finished call has one. Phone calls and web calls produce the same log from the same measurements.

Where to find it

RouteUse it when
Call History → Logs on the call's row, or open the call and choose Execution logsYou started from the call
/history/{execution_id}You have the execution id
/agent-executions/{agent_id}/{execution_id}/logsYou are debugging one agent and want a URL that names it

All three render the same page. The agent-scoped URL exists because it can be pasted into a ticket and read by a colleague who then knows which agent it is without opening it.

At the top

The page is headed Agent execution logs, with who the call was with (their name or number; a web session's short id), when it started (in your own time zone), its status and its execution id, which you can copy.

Below that, the call at a glance:

FigureWhat it is
Talk timeHow long the conversation lasted.
TurnsHow many turns it had.
Typical waitThe median of what the caller waited for a reply.
Slowest waitThe longest of those waits.
CostWhat the call cost.
Ended byWho ended the call.

An in-app call, from your app or your website, also has a Connection card beside the figures: how the call connected and how its audio held up (see Connection), and a Device list: which phone and which build it ran on (see Device). A phone call has neither.

Download logs and JSON are beside the heading (see Downloading).

The rest of the page is in tabs: Insights, Execution log, Timeline, Turn timing, Connections, Webhooks, Instructions and Raw data. The number on a tab is how many entries it holds.

Connection

Every in-app call reports how it connected while it connects, over WebRTC or the WebSocket. The Connection card puts that in plain words.

LineWhat it tells you
WebRTC or WebSocketWhich connection carried the call's audio. Fallback marks a call that could have used WebRTC and didn't, and the sentence under it says why: for example the call server didn't answer, no audio path opened in time, or the agent's In-app call audio setting is Standard.
Prepared or Cold startWhether the call was set up before the tap. A prepared call was ready when the customer tapped: your app prepared it ahead of time, or your website's assistant bar was already open. A cold start had to set everything up after the tap. Beside it is where the call's credential came from: issued directly, or through the web.
Agent spoke … after the tapThe wait your customer felt: from their tap to the agent's voice on their device. Under it is the part of that wait after the call was answered.
The barWhere that wait went. Setup is the credential, the microphone and the network path. Login is the device signing in to the call server, and appears on a cold start only. Answer runs from placing the call to its answer. First words runs from the answer to the agent's voice. A prepared call did its login and credential before the tap, so they aren't part of this wait; the line under the bar says what they took.
Call audioAt the end of a call that had audio: how long it ran, the share of packets lost, the jitter (average and peak) and the round trip, with a verdict of Good, Fair or Poor. Under them are the audio format and the network path: direct, or through a relay server.
NotesWhen the connection recovered on its own (it dialled again, restarted the audio path, or replaced a connection that had gone quiet), and when the call was answered but no audio could flow.

A line appears only when the call reported it. A number the app didn't measure is left out rather than shown as zero, so a call from an older app version shows less, never something wrong.

The same reports appear in the Execution log at the moment they happened, as Connection · … lines: Connection · carried over WebRTC, Connection · connected in 812 ms (prepared before the tap), Connection · the agent was heard 1.6 s after the tap, Connection · the device went offline. On the Timeline they are the rings on the app's lane.

Reading a slow connect. A long Login on a Cold start is the case for preparing the call before the tap. A long Answer is the network between the device and the call server. A long First words is the time the agent took to start speaking once the call was answered. A Fallback whose reason names a setting, such as it isn't set up for this app or site yet, is fixed in that setting (often Allowed domains); the call itself carried on over the WebSocket.

Device

An in-app conversation shows the device it ran on: iOS or Android, the SDK and app versions, the app, the push environment (Sandbox is a test build) and the time zone. Call History shows the same list in a conversation's details, the platform and SDK version on its row, and the same values in the export. If the user has other devices on record, they are listed under it.

Comparing the SDK version with the version you meant to ship is the quickest way to find a build that did not update: an iOS app built with Kotlin Multiplatform takes its iOS SDK from a separate dependency.

Each conversation says how sure this is:

It saysWhat it means
The phone this conversation ran on.Recorded when the conversation ended.
The phone this call rang.An older booked call: the phone that rang, with the versions it reports now. It may have been on an older one for this call.
This user's latest device.An older conversation: the versions the user's device reports now. It may not be the phone it ran on, or the version it had.

An answer read back this way says now, as in iOS · now SDK 0.14.9: that is the phone's version today, not the call's.

A push token or an install reference is never shown. A conversation with no device on record, such as one in a browser, has no list.

Insights

What to look at first.

  • Call QA, when the call has been graded: whether it passed or needs review, a score for each dimension, and the grader's verdict.
  • Issues across this call: everything flagged, from the grading and from the call's own readings: slow replies, the caller going quiet, the agent repeating itself, the agent being cut off. A graded issue names the turn it happened on.

An empty tab says so: nothing was flagged, and Call QA has not graded the call.

Execution log

Everything that happened, in the order it happened, in two views.

Conversation, the default, is one card per turn. Each card is headed Greeting, Turn 1, Turn 2… (or Between turns), with the clock and the caller's wait: from their words settling to the agent's first audio. Under it are what the caller said, one quiet line of what happened in between (thought for the model's time, voice for the time to the first audio, and how many chunks a long reply was spoken in), and what the agent said back.

What the model saw opens the two things behind a reply:

  • What it heard on the way: the drafts the transcriber produced before settling. This is the evidence for a misheard turn, and every view that shows only the final transcript throws it away.
  • Conversation the model had in front of it: the transcript as it stood when that turn was decided.

In an in-app or web conversation, what the app did sits in the turn it happened in: a screen opened or changed, a tap, an action the agent ran (with its result), and what the app reported. The connection's own reports read as Connection · … lines, such as Connection · carried over WebRTC or Connection · the device went offline (see Connection). A row from before the agent picked up says Before the call.

Table is one row per event, for reconciling against a provider's own logs:

ColumnWhat it holds
TimestampWhen it happened.
Log dataWhat was said or done, with the same expandable detail.
Directionrequest into a service, or response out of it.
ComponentWhich part of the call produced it: the transcriber, the model, the voice, the app, the connection.
ProviderThe service behind that part.
LatencyHow long it took.

A ~ before a timestamp means it was derived, not recorded. Only the transcriber writes a wall clock; the model and the voice record durations. The rest of the turn is reconstructed from that anchor plus what each stage took. Shown identically to a recorded time, someone would correlate it against a carrier log and reach the wrong conclusion, so it is marked.

The log is reconstructed, not replayed. Requests to the model are not stored one by one: that would mean storing the entire system prompt again on every turn, the same tens of kilobytes repeated per exchange, carrying whatever that prompt says about the person being called. The timings, the transcripts and the discarded drafts answer "why was this slow?" and "what did it mishear?" without keeping a copy of the prompt beside every customer's phone number.

Timeline

The call as a picture: one row per turn on a shared clock, so the gap between the caller stopping and the reply starting is something you see rather than a number to compare. What the app did sits on its own lane above, at the moment it happened, with the connection's reports as rings. Only recorded clocks are drawn; a derived time is drawn lighter and says so on hover.

Turn timing

One row per turn.

ColumnWhat it measures
TurnThe turn's number, or Greeting for the welcome message.
Caller spoke forHow long the caller talked. Not a wait: it is not part of the reply time.
Recognised inHow long the transcriber took to settle on the words after the caller stopped.
Model replied inFrom the words reaching the model to its first token.
First audio inFrom the model's first token to the first audio the caller heard.
Caller waitedFrom the moment the caller stopped speaking to the moment they heard something. The slowest turn is marked slowest.

A dash is not a zero. It means that stage produced no measurement for that turn. When a wait can't be added up, Caller waited says which stage is missing ("no voice timing", "no model timing"): a partial sum reported as a whole would be smaller than the truth, and that is the one direction a latency figure must never be wrong in. Some transcribers report no per-turn recognition time; there the wait covers the model and the voice only, and says excl. recognition.

The first row is often Greeting rather than a number: the welcome message comes before the conversation, so it has a time to first audio and nothing to recognise or decide.

A reply spoken as several sentences is still one turn. First audio in is the first sentence, the moment sound arrives.

Connections

Every API the agent called during the call: which tool (What), whether it worked (Worked, No reply, or Refused with the status code and the error), how long the agent waited for it (Took), and where the request went (Where). When the turn table says the agent went quiet for four seconds, this says it was waiting on someone else's server, and what that server said back.

Webhooks

Every webhook the call sent and whether it landed: when, the event, the endpoint, the number of attempts and the result, with the failure reason where there is one. A customer whose CRM never received a call asks exactly one question, and this is the answer to it.

An empty tab means no endpoint was subscribed, not that delivery failed. Subscriptions live in Integrations.

Instructions

The system prompt this call ran on, with a badge saying which one you are looking at:

  • the agent's prompt when this call ran: the prompt as it stood when the call ran, kept with the call. It is not everything the model received: on each turn the agent also read what was added to it — remembered facts, the app screen, knowledge-base passages — and the conversation itself. What the agent knew, in Call History, shows how much each turn carried.
  • the agent's current prompt — it may have changed since this call: no copy was kept for this call, so the agent was read as it stands now. An agent edited since the call would otherwise present a prompt that was never sent, and look authoritative doing it.

Raw data

The numbers exactly as each part of the call recorded them, before any of the above is worked out from them, one section per service (Transcriber, Model, Voice, Knowledge base, Decisions for a flow agent's path choices, Audio delivery, Voice chunks). Each section says how many turns or entries it holds before you open it, and copies on its own; Copy all copies everything. When a reading looks wrong, this is the next step, and getting to it should not require a support ticket.

Downloading

  • Download logs: a .log file with the header, the connection (for an in-app call), the full event stream, the webhook table and the prompt, as text a person can paste into a thread.
  • JSON: everything behind the page, including fields the page does not draw.

Both are named execution-{execution_id} because that is what a ticket is filed under.

The API

The turn timing is also available to your own code:

curl https://api.graine.ai/v2/calls/6bd756e8-38b3-4514-a7eb-5b1dee9093cb/timing \
  -H "Authorization: Bearer $GRAINE_KEY"

It needs a key with calls:read. It is separate from GET /v2/calls/{execution_id} because it is large — a long call runs to hundreds of turn rows — and nothing wants it until someone opens a call and asks why it went the way it did. The event stream, the webhooks, the prompt and the raw numbers are in the dashboard view and its downloads.

{
  "execution_id": "6bd756e8-38b3-4514-a7eb-5b1dee9093cb",
  "measured": true,
  "not_measured_reason": null,
  "setup": { "transcriber_connect_ms": 18, "synthesizer_connect_ms": 101 },
  "turns": [
    { "index": -1, "greeting": true,
      "spoke": { "first_audio_ms": 115, "complete_ms": 240, "segments": 1 },
      "round_trip_ms": null, "round_trip_partial": true, "round_trip_missing": ["thought"] },
    { "index": 1,
      "heard":   { "settled_ms": 842, "text": "यस, कंटिन्यू प्लीज।", "forced": null },
      "thought": { "first_token_ms": 782, "complete_ms": 961 },
      "spoke":   { "first_audio_ms": 13, "complete_ms": 657, "segments": 3 },
      "round_trip_ms": 1637, "round_trip_partial": false, "round_trip_missing": null }
  ],
  "summary": { "turns": 10, "measured_turns": 3,
               "median_round_trip_ms": 1637, "slowest_round_trip_ms": 2241,
               "slowest_turn": 2 }
}

When there is nothing to show

measured is false and not_measured_reason says why. The three cases mean genuinely different things and should not be reported to a user as one:

not_measured_reasonMeaning
in_progressThe call has not finished — it is scheduled, queued, ringing or live. Timing appears when it ends; poll again.
never_recordedNo timing exists and none will: the call never connected, or it predates timing capture. A gap in history — nobody should go looking.
no_turns_completedThe call connected and no turn completed. Nobody spoke before it ended, which is a real finding about the call.

Where the numbers come from

The agent measures each turn as it runs and reports the timings when the call ends, for web and phone calls alike. A later report can add to the record but never take anything out of it.

The transcriber, the model and the voice each number turns their own way. The timeline lines them up, which is why a turn number here is the turn of the conversation rather than any one provider's counter.

On this page