Integration materials generated from this source version
Session replay and adapters
Read-only inspection of recorded events across Operations and Traces. Replay never re-executes a model, tool, payment, webhook or uploaded script. Open the Session replay tab immediately after Trace, enter the exact session ID, then step, play/pause, change speed or inspect an event. Playback advances one event per step, not a reconstruction of screen/video or deterministic AI output.
Collect immutable events
Use an opaque, non-secret business session ID, not an authentication token. Use a distinct Span ID per event, separate from ordinary mutable Spans. Persist the complete event before sending; retries reuse IDs, timestamps and payloads. Historical Traces without event capture cannot be reconstructed.
import { replayEventSpan } from '@rainlib/nexus-sdk';
const event = {
sessionId: 'support-session-42', eventId: 'message-1',
spanId: '1111111111111111', // Example only: generate a unique W3C Span ID.
traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', // Actual execution Trace ID.
sequence: 1, occurredAt: '2026-09-13T12:00:00Z', // Actual, stable occurrence time.
type: 'message.completed', schema: 'ai.chat.v1',
contentCapture: 'FULL' as const,
payload: { role: 'assistant', content: 'Hello' },
};
// client is a configured trusted SERVER-SIDE Nexus client.
await client.reportReplayEvent(operationId, event);
// Alternative: enqueue the event in your durable evidence transport instead.
const batchItem = { eventKey: event.eventId, span: replayEventSpan(event) };
const page = await client.listSessionEvents(event.sessionId, { limit: 50 });Python: client.report_replay_event(operation_id, session_id=..., event_id=..., span_id=..., trace_id=..., sequence=1, occurred_at=..., type=..., schema=..., content_capture="FULL", payload=...). The pure builder is nexus_instrumentation.replay.replay_event_span; query with list_session_events. Go: nexus.ReplayEventSpan, client.ReportReplayEvent, client.ListSessionEvents. Go payloads are json.RawMessage. Java has no typed replay helper yet. Source SDK support does not imply a published registry release.
The TypeScript, Python and Go replay builders require an actual calendar date with a four-digit year from 0001 through 9999, uppercase T, and an explicit timezone (Z or ±HH:MM). Fractional seconds are optional, with at most nine digits. For example, 2026-09-13T20:00:00.123456789+08:00 is valid; date-only values, timezone-less values, impossible dates and leap seconds are rejected locally. Builders preserve the original timestamp text, including its offset and fractional precision; persist and reuse it for retries rather than generating a new time. These syntax checks do not replace server-side event validation.
TypeScript replay payloads must be plain JSON data: null, booleans, finite numbers, strings, dense arrays and plain objects with enumerable string keys. Nested undefined, functions, symbols, BigInt, non-finite numbers, sparse arrays, accessors, hidden properties and custom objects such as Date/Map/Set are rejected instead of silently losing or changing evidence. Convert provider objects to an explicit JSON schema first (for example, a Date to an ISO timestamp string). The builder does not invoke getters or toJSON methods. It is not a sandbox for untrusted JavaScript objects such as Proxies. JSON containers may nest at most 64 levels; size limits apply to serialized UTF-8 bytes, including escaping. An omitted top-level payload remains absent; use null to record JSON null.
Python replay payloads likewise require plain built-in JSON data: None, bool, int, finite float, Unicode string, list and dict with string keys. Tuples, sets, bytes and custom subclasses must be converted explicitly. Non-string dict keys are rejected, not coerced (1 and "1" must not become duplicate JSON keys). Invalid Unicode, cycles, more than 64 nested containers and over-limit UTF-8 output fail before reporting. Valid payloads retain the previous Python encoder's spacing and insertion order, preserving retry identity. Extremely large integers may also exceed Python's conversion limit; represent exact large numeric IDs as strings for portable use across languages and viewers. Omitted payloads stay absent; None records JSON null. These SDK checks do not provide a sandbox for objects concurrently mutated by the caller.
Go's ReplayEvent.Payload accepts json.RawMessage. ReplayEventSpan and ReportReplayEvent reject duplicate object keys (including escaped equivalents), invalid UTF-8, unpaired surrogate escapes and more than 64 nested containers before reporting. Serialized limits are 8192 bytes for REDACTED and 1 MiB for FULL; DISABLED cannot carry a payload. Nil/empty bytes mean no body, while json.RawMessage("null") records JSON null. Valid body text is preserved exactly, including whitespace, number spelling and Unicode escapes; invalid identifier UTF-8 is rejected rather than replaced by the encoder. The completed span owns its body string, but callers must not concurrently mutate the input byte slice during building. Direct low-level span/batch calls still rely on server validation.
REST protocol
Send the builder output to existing POST /v1/metering/operations/{operation_id}/spans:report, or use the existing BatchReportEvidence/StreamEvidence transport and its per-item acknowledgements.
Batch acknowledgements echo the original request event_key; stream replies echo that frame's original stream_id and sequence even when rejecting it. The API retains its existing trimmed validation/deduplication, so whitespace variants do not create separate events. The source Go Evidence stream helper also preserves event keys across retries, compact ACK expansion and REST fallback. Use canonical keys in new integrations; legacy padded-key fixes need the updated API/helper together. This changes reply correlation, not stored usage identity or historical records.
The source Go Evidence stream helper also validates full and compact ACKs, including REST fallback replies, before reporting success. Missing/duplicate/ foreign event keys, inconsistent counts or contradictory success/error flags produce retryable EVIDENCE_ACK_INVALID. Retain pending evidence when this error survives bounded retries; an invalid reply cannot prove that storage failed or succeeded. The source Go REST BatchReportMeteringEvidence method now applies the equivalent full-ACK checks, rejects malformed/ambiguous JSON and unsolicited compaction, and retries inside its existing request deadline and attempt budget. It returns no partial result on invalid ACK exhaustion; retain pending evidence. Its APIError.StatusCode is the actual HTTP status, including 200/204 when an apparently successful response is invalid. The source Python NexusClient applies the same full-ACK safeguards to standard Evidence batch envelopes, including its batch helper. Invalid replies produce retryable NexusError with code EVIDENCE_ACK_INVALID and the actual HTTP status; its persistent spool retains/reschedules these records until valid accepted/duplicate ACKs. The source TypeScript batchReportMeteringEvidence also validates full ACKs inside the existing retry budget. It reads successful response bodies up to 4 MiB with strict streaming UTF-8 decoding, cancels excess/stalled reads, and projects only validated fields into camelCase results. Generic Go Do, generic TypeScript request, and custom Python transports bypassing the standard client are not covered by this claim, nor are previously published SDK versions.
The Python spool separately validates aggregate metadata from custom adapters: if any counter or compact flag is supplied, the reply must satisfy the complete batch contract. Invalid metadata reschedules still-owned rows with EVIDENCE_ACK_INVALID. Legacy per-item-only adapters may omit all aggregate metadata; only unambiguous matching ACKs retire rows, while missing/duplicate ACKs retry. Queue mutations use immutable validated outcomes and durable claimed keys, never mutable dictionaries retained by the custom client. Lease fencing continues to apply. This queue guard does not validate raw JSON bytes bypassed by a custom transport.
The three source REST SDKs use a shared v2 batch identity: full SHA-256 over length-framed UTF-8 operation ID and ordered original event keys. The 82-character nexus:evidence:v2: header prevents the old delimiter-boundary and TypeScript astral-character collisions. Upgrading changes the batch header, not evidence identities; finish/pin in-flight requests and review external gateway caches before rolling versions. TypeScript requires native Web Crypto SHA-256. See the [framing and upgrade contract](../../sdk/conformance/evidence-batch-keys.md).
The source Go, TypeScript and Python REST batch helpers reject empty batches or more than 100 items before encoding. Serialized JSON above 5 MiB is rejected before gzip/HTTP as non-retryable EVIDENCE_BATCH_TOO_LARGE (local status 413, not a received HTTP response). Escaping and UTF-8 bytes count; gzip cannot bypass the decoded-body ceiling. The server's separate 4 MiB protobuf-message limit still applies. Split batches while preserving event keys and acknowledgements; helpers do not silently split an existing batch. Go and TypeScript apply this guard after whole-body serialization; these checks do not change gRPC limits.
Python's standard REST batch encoder additionally accumulates at most the HTTP budget, encoding groups of up to four events and stopping before later groups once over budget. It preserves the previous compact ASCII JSON bytes. This avoids constructing an entire oversized batch; a group's encoding allocation and a low-level incremental encoder's largest scalar chunk remain separate memory costs. Small valid batches can incur extra CPU overhead; no API-throughput improvement is implied, and callers must not mutate inputs during encoding. All existing reporter, delegation and operation ownership checks apply.
{
"trace_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"span_id": "1111111111111111",
"span_key": "replay:message-1", "name": "message.completed",
"kind": "CUSTOM", "status": "SUCCEEDED", "sequence_no": 1,
"started_at": "2026-09-13T12:00:00Z", "ended_at": "2026-09-13T12:00:00Z",
"attributes_json": "{\"nexus.replay\":{\"session_id\":\"support-session-42\",\"event_id\":\"message-1\",\"sequence\":1,\"type\":\"message.completed\",\"schema\":\"ai.chat.v1\"}}",
"content_capture": "FULL",
"output_preview": "{\"role\":\"assistant\",\"content\":\"Hello\"}"
}sequence_no is always 1 and both timestamps are equal: an immutable event, not a changing execution snapshot. nexus.replay.sequence is a producer-assigned positive safe integer. Emit separate delta/completion events for streams. Identity is unique per tenant + Operation + session ID + event ID; prefer globally unique event IDs across Operations. Routing identifiers/type/schema have a 128-byte UTF-8 limit. The reserved object accepts only the five illustrated fields. Never place prompts, secrets or personal data in metadata/names/errors.
The server rejects duplicate object keys in replay metadata and event bodies, including equivalent escaped keys ("x" and "\u0078"), invalid UTF-8, unpaired Unicode surrogate escapes and more than 64 nested containers. Reserved metadata field names must match the documented lowercase spelling exactly. Validation does not normalize the body or convert its numbers to floating point. Large numeric values can still exceed a viewer's numeric precision; encode exact business identifiers as strings for portable rendering. These checks apply to new reports, not a repair of historical rows; raw REST/low-level producers must satisfy them even if their local JSON parser accepts a more permissive input. Invalid reports fail validation; retrying unchanged bytes cannot repair them.
Query GET /v1/metering/session-events?session_id=...&limit=50, with metering.view and an active environment. An optional customer_account_id cannot override customer-bound identity. Response: {data: MeteringSpanReply[], next_cursor: string}. Pages have at most 100 events, ordered by occurrence time, event sequence, then row ID. Preserve the cursor and scope between pages. Rows with malformed historical replay sequences (non-numeric, fractional, nonpositive or beyond the safe-integer range) are excluded from this replay listing; they are not renumbered or converted into valid events. Investigate missing evidence at its trusted producer rather than assuming playback is complete. The cursor is opaque and bound to the tenant, environment, resolved customer filter and session ID. Changing any of these requires restarting from page one. Legacy cursors issued before scope binding are rejected; discard them and restart the query. A cursor never grants access or replaces per-page authorization. Pagination is not a frozen snapshot: reload after late/offline uploads to include backfilled events. Clock skew and missing producer events prevent guaranteed complete playback. The Console caps a loaded view at 1,000 events; use the API for longer sessions.
Related GET /v1/metering/operations and GET /v1/metering/traces list cursors also bind to the tenant, active environment, resolved customer and normalized filters. Keep the same filters when loading another page; changing page size is allowed. Operation cursors include status and operation key; Trace cursors include trace status, operation statuses, entry point, operation type and search query. Reordering equivalent operation statuses does not change the Trace scope. Legacy or mismatched cursors return 400 REQUEST_INVALID: discard the cursor and restart from page one, rather than retrying it unchanged. These cursors are continuation positions, not credentials or frozen snapshots.
Lists contain no decrypted bodies. Read one body using GET /v1/metering/operations/{operation_id}/spans/{span_id}/content, requiring trace.content.view. SDK capture defaults to DISABLED. Bodies require explicit capture and effective server policy (FULL_CONTENT for full JSON). REDACTED previews allow 8 KiB, FULL 1 MiB. A stricter policy can discard bodies. Under REDACTED_PREVIEW, a reporter's FULL body is discarded and the stored mode becomes DIGEST_ONLY, retaining only digests already supplied. Shortening a full body is not redaction; the server neither relabels it as redacted nor stores a truncated JSON fragment. An explicitly REDACTED reporter must perform its own trusted redaction before uploading. Missing digests are not fabricated. Existing encryption, retention, legal holds and erasure apply. Missing, expired or erased content is unavailable, never reconstructed from guesswork.
This policy correction does not erase historical content. Review earlier FULL uploads retained as REDACTED under a preview-only policy and use the governed erasure workflow if needed. Retries of such old records can now return an idempotency conflict because their stored capture representation differs; do not mint new event IDs or blindly retry to bypass the conflict. Reconcile the retained record and policy with an authorized operator.
Trusted display adapters
Register statically imported adapters in apps/console/lib/session-replay.ts: each declares a stable id, explicit versioned schemas, and pure decode(payload): ReplayView. Adapters ship as reviewed application code, not uploaded executable plugins. Duplicate registrations fail; decode errors fall back to original JSON. No evaluation, remote script loading or tool execution.
| Schema | Payload | View |
|---|---|---|
ai.chat.v1 | {role,content,tool_calls?} or {messages:[...]} | Role-labelled messages and recorded tool calls |
ai.tool.v1 | {name,arguments,result} | Tool, arguments and recorded result |
json.v1 / unknown version | Any JSON value | Expandable JSON tree and raw text |
Native provider/LangChain/agent formats are not automatically equivalent. Map them during collection or supply a versioned decoder returning JSON/message/tool view models. New renderer kinds require a reviewed UI extension. Specialized views may omit unknown fields; Raw JSON retains the original authorized body. Strings are text, never HTML. URLs/attachments are not automatically fetched. JSON expands lazily, 50 children at a time with depth 12; use Raw JSON for deeper structures. Changing displayed data never mutates the original event.
Deployment and acceptance
Apply migration 118, deploy API and Console together, then release SDKs through the normal package workflow. No new third-party runtime dependency. Verify customer/tenant/environment isolation, idempotency, overwrite rejection, paging and delayed uploads, unknown/malformed schemas, permission denial, content erasure, quick event switching and GET-only playback.
中文说明
会话重放仅查看已采集事件,不会再次执行 AI、工具或交易。每个事件独立且不可覆盖, 通过会话 ID 关联多个 Trace/Operation。通用结构使用 JSON 树与原文;AI 对话和工具 调用由版本化适配插件展示。目前插件随代码审核和部署,不支持上传脚本直接运行。 未采集、过期、删除内容不能补造;正文继续受权限、加密、脱敏和留存策略控制。 SDK 已有源码支持,上线仍需数据库迁移、服务部署和包发布验证。