Skip to main content
The engine normalizes Mastra stream chunks into these events. The browser and the Mind Graph depend on this format, not on raw Mastra chunks, so a Mastra upgrade cannot break the UI. Common fields on every event:

Event types

usage = { "inputTokens": 441, "outputTokens": 28, "reasoningTokens": 2, "totalTokens": 469 }. It is only filled while streaming when the engine builds models with @ai-sdk/openai-compatible + includeUsage: true; with the nearai/<id> router string it is empty. costUsd = inputTokens × input price + outputTokens × output price, prices from GET https://cloud-api.near.ai/v1/model/list (cached by the engine). verification (on run.verified), computed with @nearai/inference-sdk (verifyResponse per completion, after Gateway and model attestation):
  • { "status": "verified", "level": "model_tee" | "gateway_tee", "completionIds": [...] }: every model step verified. model_tee only if every step was signed inside the model TEE (provider_tee); streaming completions are signed by the Gateway TEE, so a normal chat run is gateway_tee (measured 3 Nov).
    • proof (#254, optional: present when every step carried its evidence; absent on runs before it) = { checkedAt, steps: [{ id, signatureKind, signer: { algo, address }, signedText, signature, attestation }], attestations: { [attestation]: { kind: "gateway" | "model", signer: { algo, address }, nonce, intelQuote, tcbStatus, gpuEvidence?, composeHash?, osImageHash? } } }. Taken from the SDK’s own verifyResponse result, nothing fetched again, no API key. signedText is {request_hash}:{response_hash} (gateway) or {model}:{request_hash}:{response_hash}: hashes only. Each step’s attestation keys the attestation whose signer signed it; anyone can check the intelQuote at https://proof.t16z.com. About 12 KB per run (a 10,012-character quote for one gateway attestation, measured 11 Nov), so the backend stores it but leaves it out of the stream (chat, approve/decline, Telegram), GET /v1/runs/{runId} and the Mind Graph’s events, and serves it on its own at GET /v1/runs/{runId}/proof (#254).
  • { "status": "not_applicable", "reason": "incognito_model" | "no_completions" }: Incognito models (verifiable: false in the model list) never get the private badge.
  • { "status": "failed", "completionIds": [...], "errors": [...] }: show no badge.
UI wording: model_tee → “Verified private (model TEE)”; gateway_tee → “Verified via NEAR AI TEE gateway”; anything else → no badge. Builder runs (#20) send workflow.started → node.started/node.finished… → workflow.finished → run.verified. Inside an agent node the chat events appear between its node.started and node.finished (step.*, reasoning.delta, text.delta, tool.*), but no run.started / run.finished. A run with no agent node ends with run.verified { status: "not_applicable", reason: "no_completions" }. Condition nodes emit nothing; only the branch that ran appears. Example sequence for one tool call:

Not forwarded to the browser

tool-call-input-streaming-start, tool-call-delta, tool-call-input-streaming-end, text-start, text-end, reasoning-start, reasoning-end, raw. The engine drops them; nothing in the UI needs partial tool arguments.

Secrets in events

No event may contain a secret value. The engine redacts known secret values (not just field names) in every event field, including run.error, before emitting, replacing them with "••••".