Cloud execution audit and PostHog observability¶
Sonaloop Cloud records Remote-MCP execution in a tenant-bound local audit ledger and can project a privacy-reduced view into PostHog. The local record is the authority: PostHog is an operational view for traces, product analytics, dashboards and alerts, not the source of truth for replay.
There are two execution planes:
| Plane | What Cloud observes | PostHog events |
|---|---|---|
| External MCP host (for example Claude, Mistral or ChatGPT) | Authenticated request transport phases and calls that cross Sonaloop's MCP tool boundary | One sonaloop_mcp_request_phase per durable lifecycle phase, plus one $mcp_tool_call and one $ai_span per durable tool audit row |
| Cloud-hosted agent | Every provider wire attempt and the internal tools Cloud executes | One $ai_generation per attempt under one run trace, with child $ai_span tool operations |
PostHog documents the tool and AI event contracts in MCP
Analytics and AI
Observability;
sonaloop_mcp_request_phase is Sonaloop's custom transport event. Sonaloop uses
its own exporter because the Remote-MCP route has a custom stateless session
manager. Adding PostHog's native beta MCP wrapper as a second path would give it
the wrong transport ownership and double-count tool calls.
The authoritative local ledgers¶
Every Remote-MCP tools/call produces an append-only row in
cloud_mcp_execution_audit and its delivery receipt in one database transaction. Each row is bound to exactly one workspace and
contains the correlation and outcome needed to investigate the tool boundary:
- audit, request, transport-trace, operation and stable workflow-trace ids;
- tool name, MCP profile, status, error code and duration;
- project and run ids when the call produced or referenced them;
- client, server and protocol version metadata; and
- bounded, recursively redacted argument/result summaries and their digests.
The two trace identities have different scopes. A W3C/HTTP transport trace belongs to one request.
The deterministic sltrace_* workflow id belongs to one research project and is stamped on its run,
sessions, assets, reports, exports, MCP response metadata and semantic product events. Use
cloud_get_research_job_trace(workflow_trace_id=...) to resolve the complete locally observable job;
use trace_id=... for one transport attempt. Raw workflow ids remain in tenant-local domain/audit
state; PostHog receives only the workspace-HMAC pseudonym sonaloop_workflow_trace_id.
Authorization headers, cookies and secret-shaped fields never enter the row. The default policy replaces arbitrary strings with type, length and SHA-256 digest metadata. Local content capture is a separate, explicit decision.
Job creator attribution is not an analytics identity. Cloud derives the original creator from the
authenticated active-workspace membership and keeps it with workspace-owned project provenance.
Raw creator user ids, names and email addresses are never copied into MCP audit summaries or exported
to PostHog. Metadata-only observability continues to correlate a job through purpose-separated HMAC
pseudonyms for workspace, request, operation, project and run; those pseudonyms must not be decoded or
joined to manufacture a creator label. A legacy job without creator provenance remains unknown in
support and replay rather than being guessed from later audit activity. An exceptional owner-attested
legacy correction is workspace domain data in a separate local audit receipt: it is not an MCP event,
AI span or analytics identity, and neither the approving nor target member's opaque identity is
projected to PostHog.
In addition, every authenticated Remote-MCP request gets a unique lifecycle in
cloud_mcp_transport_lifecycle. The table is append-only, protected by the
same workspace-scoped Postgres row-level security as the tool ledger, and uses
deterministic phase identities so duplicate transport notifications cannot be
counted twice. A lifecycle can contain the following phases:
| Phase | What it proves |
|---|---|
received |
Authentication, live workspace membership and MCP profile checks succeeded, and Cloud entered the observed request boundary. Requests rejected before that boundary are not lifecycle rows. |
body_read |
ASGI delivered the complete HTTP request body to Cloud. Only byte and chunk counts are retained; the lifecycle ledger never stores the body. |
rpc_parsed |
The MCP SDK parsed one JSON-RPC message. Cloud records only bounded structural metadata such as method, tool name and safe correlation ids. |
tool_started |
The SDK dispatched one tool invocation into Sonaloop's tool manager. |
tool_completed / tool_failed |
That invocation returned successfully, returned a technical error result, or raised. |
receipt_committed |
The corresponding cloud_mcp_execution_audit row and this phase were committed atomically. It proves the local audit receipt, not delivery to or rendering by the external host. |
response_started |
Cloud handed the HTTP response status and headers to the ASGI server. |
responded |
Cloud successfully handed the final response-body message to the ASGI server. This is not a TCP/client acknowledgement and does not prove that Le Chat, Claude or another host displayed the result. |
client_disconnected |
ASGI reported that the client disconnected. |
transport_error |
The receive, handler or send path failed before Cloud could prove a normal terminal hand-off. |
Cancellation and a handler that returns without any other terminal signal are
retained as the explicit exceptional terminals request_cancelled and
request_aborted. Tool and parsed-message phases have invocation-specific
phase keys, so a batched request remains reconstructable without overwriting
earlier phases.
The request-body receive pump stays active while the MCP SDK handles the
request. It therefore detects an http.disconnect that arrives after
body_read—the important case where a host uploaded a complete tool call and
then gave up while the tool was executing or the response was being prepared.
This narrows the server-visible causes of a host error such as Le Chat 6101; a
failure inside the host before it sends HTTP remains unobservable. Phases are
buffered per request and committed in a small number of transactions over a
bounded PostgreSQL pool. Completion plus receipt_committed stay atomic, while
abandonable three-second deadlines prevent an unavailable audit database from
holding the connector response indefinitely. Audit DB calls use a dedicated
hard-bounded four-worker executor whose permit is released only when the worker
really ends, so abandoned diagnostics cannot grow without bound or consume the
worker budget used by synchronous customer tools.
As with every audit write, lifecycle persistence is fail-soft: an audit outage
cannot replace the customer's tool result. A missing phase remains a visible
local replay gap rather than being inferred later.
SONALOOP_MCP_AUDIT_ENABLED=1
SONALOOP_MCP_AUDIT_RETENTION_DAYS=30
SONALOOP_MCP_AUDIT_CAPTURE_CONTENT=0
SONALOOP_MCP_AUDIT_MAX_STRING_CHARS=512
SONALOOP_MCP_AUDIT_EXPORT_WORKER=1
SONALOOP_MCP_AUDIT_EXPORT_INTERVAL_SECONDS=2
SONALOOP_MCP_AUDIT_EXPORT_BATCH_SIZE=25
SONALOOP_MCP_AUDIT_EXPORT_LEASE_SECONDS=60
SONALOOP_MCP_AUDIT_EXPORT_MAX_ATTEMPTS=10
SONALOOP_MCP_AUDIT_EXPORT_BACKOFF_SECONDS=2
SONALOOP_MCP_AUDIT_EXPORT_BACKOFF_MAX_SECONDS=3600
SONALOOP_MCP_AUDIT_RETENTION_DAYS=0 disables global metadata/legacy pruning.
It does not make approved content indefinite: every content receipt freezes
the workspace's 14–30 day retention and expires on that schedule. Export is fail-soft: a PostHog outage or
bad exporter configuration cannot replace, repeat or fail a successful MCP
tool call. The request path commits only the local ledger and outbox receipt; it performs no
PostHog network I/O. A lifespan-managed worker later claims receipts through a bounded Postgres
lease, exports them, flushes the SDK, and marks them delivered. Crashed leases are reclaimed;
failures use bounded exponential backoff and become explicit dead letters after the configured
attempt limit. The worker continues across tenant failures and every claim remains protected by
Postgres row-level security.
If ledger persistence itself fails, Cloud logs a structured fallback error but
still preserves the customer tool result; treat that log as an observability
gap and alert on it.
Important
"Authority" does not mean that metadata-only rows contain a hidden copy of prompts or tool content. They are the authoritative record of what Cloud observed under the configured policy. Exact content reconstruction is possible only where that content was lawfully captured elsewhere, such as the Cloud-hosted agent journal.
Configure PostHog capture¶
PostHog export is off by default. For an EU project, configure the capture path from the deployment's secret/configuration store:
SONALOOP_MCP_AUDIT_EXPORTERS=sonaloop_cloud.posthog_exporter:exporter
POSTHOG_PROJECT_API_KEY=<PostHog project API key>
POSTHOG_HOST=https://eu.i.posthog.com
SONALOOP_TELEMETRY_HMAC_KEY=<independent key of at least 32 bytes>
SONALOOP_TELEMETRY_HMAC_KEY_VERSION=v1
# Keep both off for the metadata-only default.
SONALOOP_POSTHOG_CAPTURE_CONTENT=0
SONALOOP_POSTHOG_HOSTED_ENABLED=0
SONALOOP_LLM_TRACE_CAPTURE_CONTENT=0
Do not commit literal values or paste them into support messages. Keep
SONALOOP_TELEMETRY_HMAC_KEY independent from SONALOOP_CLOUD_SECRET and
stable across deploys. Rotating it deliberately breaks correlation between
historical and new workspace/session pseudonyms.
Generate an independent key without reusing an application or provider secret:
python3 -c "import secrets; print(secrets.token_urlsafe(48))"
The version is an identity epoch. Each durable receipt freezes its version. Before rotation, pause new export work and drain all pending receipts, then change both key and version together. If an old receipt meets a newer configured version, the exporter fails closed and leaves it undelivered rather than creating a second identity/UUID for the same operation. Repair the configuration or perform an explicit migration and requeue; never silently retry it under the new epoch.
POSTHOG_PROJECT_API_KEY is the phc_… project key used to ingest events. It
is the only PostHog credential needed by the running capture path.
POSTHOG_HOST is an ingestion endpoint, not the PostHog UI or query API host.
The following settings are optional and are not used to send events:
| Variable | Use |
|---|---|
POSTHOG_PERSONAL_API_KEY |
Operator-only API readback; never use it as the project API key |
POSTHOG_PROJECT_ID |
Project selected by an automated readback check |
POSTHOG_API_HOST |
Query/API origin, normally https://eu.posthog.com for EU Cloud |
Prefer keeping readback credentials in a separate restricted verification job rather than the capture process. Interactive verification in the PostHog UI does not require these environment variables.
Event model and correlation¶
One Remote-MCP ledger row has two deliberate projections:
$mcp_tool_callpowers MCP usage, error and latency analysis. It carries the tool name, duration, error state and the Sonaloop audit correlation.$ai_spanplaces the same observed tool boundary in an AI trace. It carries the shared trace id, an audit-derived span id, latency and input/output state at the configured privacy level.
The pair shares sonaloop_audit_event_id and $ai_trace_id; it is one observed
operation in two PostHog surfaces, not two tool executions. Dashboards that
count tool executions should use $mcp_tool_call only.
Each durable request-lifecycle row has one separate, content-free projection:
sonaloop_mcp_request_phasecarries the closed phase/status, elapsed request time, bounded body byte/chunk counts where applicable, HTTP status, tool/method labels and HMAC-scoped correlation ids.- Method labels come from a closed MCP vocabulary and tool labels must match the server's actual tool registry. A client-controlled unknown value is omitted; it cannot become a plaintext channel into the ledger or PostHog.
- It does not claim to be an LLM generation or an additional tool execution.
Count distinct
sonaloop_transport_lifecycle_idvalues for requests; counting these phase events directly counts transitions, not requests. - The event is emitted through the same durable outbox worker as the existing
$mcp_tool_calland$ai_spanevents. PostHog delivery can therefore be replayed without replaying the customer operation.
Declared MCP connector identity¶
During MCP initialize, Cloud reduces params.clientInfo.name to a closed,
low-cardinality connector family such as mistral, claude, chatgpt,
codex, cursor or unknown. It binds a recognized family to the current
workspace bearer until that bearer's expiry, capped at 90 days, so later tool
requests can carry the same family even when their HTTP user agent is generic.
The binding key is a domain-separated workspace/token HMAC.
Raw bearer tokens and raw clientInfo name/version values are never stored or
exported. The local binding retains only lengths and protected fingerprints;
PostHog receives the closed family and its classification source. A user-agent
family is a per-request fallback. When one bearer was reused by different
initialize declarations, its durable binding remains ambiguity-closed, while a
recognized wire User-Agent can still classify that individual analytics event with
family_source=user_agent. That fallback never qualifies as evidence for immutable
Job-creator attribution. If the User-Agent is also unrecognized, the call remains
unknown.
The local bearer binding is keyed with SONALOOP_CLOUD_SECRET; exported
workspace/request/lifecycle identities remain keyed independently with
SONALOOP_TELEMETRY_HMAC_KEY. Do not substitute either key for the other.
This identity describes the MCP connector host, not the inference model or
provider. For example, a declaration matching Le Chat can truthfully produce
connector family mistral; it does not prove which Mistral model handled the
conversation, whether the host retried a provider call, or even that every
request under that connector used the same model.
At the first successful begin_research_job, a recognized token-bound initialize.clientInfo family
can also be frozen as
sonaloop.ingress_client_snapshot.v1 in the private Job ingress. It contains only the closed family
and evidence-source enum—never raw clientInfo, User-Agent, bearer or model text. Public Job
projections map it to fixed labels such as Mistral, ChatGPT, Claude or Codex for a
quiet via … creator byline. Unknown/ambiguity-closed observations and legacy Jobs remain unlabeled;
exact and logical retries preserve the first snapshot. provider_declared and model_declared remain
unverified trace declarations and are never used for this display.
Authenticated report-view analytics¶
Cloud records sonaloop_artifact_viewed only after an authenticated report/synthesis route has
successfully rendered real HTML. The server boundary covers full-page navigation, SPA navigation,
the synthesis drawer and a Job URL whose d=/syntheses/<id> drawer is rendered in the initial page.
It does not rely on browser autocapture or broad raw-URL $pageview events. Full-page, SPA and drawer
representations of the same viewer/report interaction collapse within a five-minute dedupe window.
The durable local projection and PostHog event contain only HMAC-pseudonymous viewer, workspace,
project, report and revision ids plus a closed allowlist: artifact kind, view mode, structural report
status, empty-section count, age since ready, first-view boolean and
sonaloop_job_creation_client_bucket. That last field describes the connector that created the Job,
not the viewer's browser or the inference provider. Person-profile processing is disabled. Names,
emails, titles, URLs, query strings, report text and raw database ids are never exported. The event
uses the existing retryable outbox; a PostHog outage cannot affect the rendered response, and local
first-view state follows the configured telemetry retention.
If the Job has an immutable authenticated created_by.id, Cloud may also emit the optional Boolean
sonaloop_viewer_is_job_creator. It compares that id with the current OIDC subject locally. Neither
raw identifier enters the projection or PostHog; an absent, legacy or malformed creator omits the
property, with no email, name or display-label fallback.
sonaloop_report_status=done is likewise content-derived rather than a copy of a legacy status flag.
It requires a final stored state plus Core's complete report-handoff shape: a non-empty lead, at least
one section and no empty section body. A legacy done scaffold with an empty section is exported as
running (partial), keeps its sonaloop_empty_section_count and does not receive
sonaloop_age_since_ready_ms. Ready-age exists only after the structural handoff is complete.
This event makes future authenticated render requests queryable. It cannot reconstruct views from
before the instrumentation was deployed, and MCP get_synthesis calls are operational reads rather
than proof that a human opened the report. Even for new events, it proves an authenticated server-side render
request—not that the browser painted the response, that the tab remained open or that the person read
or understood the report.
Audit schema v3 adds one privacy-reviewed semantic result contract. A technically
successful record_cohort_preflight can expose sonaloop_outcome as exactly one
of pass, needs_deepening, needs_reselection, overridden, or the fixed
fallback unknown. The exporter revalidates the tool, technical status and exact
contract version before adding the property to both projections. It never exports
an arbitrary result status, rationale or malformed version string. This is
low-cardinality structural metadata, does not authorize content capture and is
absent on legacy receipts.
Technical failures stay separate from semantic outcomes. $mcp_error_type and
the paired $ai_error.code use PostHog's closed categories (missing_context,
validation, permission, timeout, rate_limited, api_4xx, api_5xx, or
internal); raw local exception codes remain in the tenant ledger.
Weak-host Reaction-Test inputs get stable boundary codes such as
REMOTE_PRODUCT_UNDERSTANDING_VALIDATION, REMOTE_STIMULUS_VALIDATION and
REMOTE_COHORT_VALIDATION. The client response names bounded field paths and points back to the
same Job's current action; PostHog records only the closed validation category. Scanner timeouts,
missing scanner policy, authorization, storage and contract failures retain their operational code
and map to timeout, permission or internal instead. This distinction is what lets support say
whether a malformed Mistral/Claude/OpenAI tool call or a Sonaloop-side dependency stopped the Job.
The front-door tools also emit one low-cardinality, content-free setup outcome so a funnel can show
where an external host stopped: needs_stimulus, needs_capture_review, needs_flow_manifest,
needs_screen_inspection, needs_product_understanding, needs_cohort, or research_ready
(terminal outcomes remain separate). These values contain no URL, prompt, screen label, persona id or
human identity. They complement the durable tenant ledger; PostHog is an operational projection, not
the runtime authority.
PostHog's trace and session layers have separate meanings:
$ai_trace_idgroups one interaction's spans. A valid inbound W3C trace is preserved; otherwise Cloud creates a request trace. Every observed toolcall has a unique$ai_span_id, and its parent is retained when W3C context exists.$ai_session_idgroups the request traces belonging to one governed run, then falls back to project/session/operation correlation when no run is known.- MCP Analytics
$session_idrepresents explicit MCP session metadata, with a request-local fallback.$mcp_conversation_idcarries the governed run/project grouping. Domain objects that happen to be namedsession_idare not treated as protocol sessions.
Cloud returns a normal single-call response traceparent containing the
resulting tool span, so a capable host can continue the tree on its next request.
When a tool legitimately adopts an MCP _meta.traceparent in the absence of an
HTTP trace, its completion span can differ from the inbound request-lifecycle
trace. Both event families carry the same HMAC-scoped
sonaloop_transport_lifecycle_id, which is the exact join in that case.
If a future transport batches several toolcalls into one response, it returns the
request-local server span instead of choosing an arbitrary child. HTTP trace context takes
precedence over MCP _meta; an invalid-but-present HTTP traceparent starts a new
trace instead of trusting a second source. Raw tracestate values are neither
persisted nor returned; only bounded validity/count metadata enters the ledger.
All exported trace/span identifiers are workspace-HMAC scoped. Sonaloop spans can
therefore nest without exposing raw ids, but separately ingested raw OpenTelemetry
spans do not automatically join this privacy-scoped tree.
At the wire boundary, Sonaloop converts the ledger's ISO timestamp into the
timezone-aware datetime required by PostHog's Python SDK. MCP session ids use
the canonical ses_<32-hex> form. The credentialed smoke test must query both
reserved event types back; an HTTP 200 from ingestion alone is not the release
gate.
For a Cloud-hosted agent, set:
SONALOOP_POSTHOG_HOSTED_ENABLED=1
The common hosted provider runner emits a distinct observed $ai_generation for
every provider wire attempt. Retries share the logical turn and stable run trace but retain their own
attempt ordinal, latency, status, retryability, HTTP status, token usage, error classification and
pseudonymized client/provider request and response ids. The successful attempt becomes the parent
of any internal $ai_span tool calls it requested. This keeps a transient 429 followed by success
visible instead of flattening it into one misleading generation. The hosted-agent journal remains
the complete replay authority even if PostHog is unavailable.
Generation attempts and internal tool spans do not call PostHog on the agent
thread. Each becomes an immutable row in
cloud_hosted_telemetry_projection plus an idempotent receipt in the same
workspace/RLS-bound outbox used by Remote-MCP audit export. The shared worker
uses FOR UPDATE SKIP LOCKED, expiring leases, bounded exponential backoff and
dead letters. A stable, tenant-HMAC-bound PostHog event UUID makes replay after
a capture/flush failure or worker crash idempotent. The event is acknowledged
only after the SDK flush succeeds; until then it remains pending locally.
The exact privacy-frozen projection seed is committed inside the authoritative
provider-attempt/tool-result journal row before enqueue. At every worker tick,
Sonaloop reconciles journal seeds missing their projection/outbox row. Thus a
crash between those two local commits is repaired from the journal rather than
from PostHog.
PostHog's event relationships are described in its generation contract and span contract.
Identity and privacy boundary¶
No raw workspace, MCP session, request, operation, project or run id is sent in
the corresponding PostHog correlation fields. Sonaloop derives deterministic HMAC pseudonyms with
SONALOOP_TELEMETRY_HMAC_KEY. Locally generated content digests are
HMAC-protected again before export so that low-entropy values cannot be guessed
from an external SHA-256 digest. Person-profile processing is disabled for
these events; the external identity is a workspace pseudonym, not an
individual account.
Content has independent consent gates:
| Data | Default | Required to retain/export bounded redacted content |
|---|---|---|
| Remote-MCP local arguments and results | Metadata/digests | SONALOOP_MCP_AUDIT_CAPTURE_CONTENT=1 and persisted owner-approved mcp_content_capture purpose and explicit call/persisted-job consent |
| Remote-MCP content exported to PostHog | Metadata/digests | All local gates, SONALOOP_POSTHOG_CAPTURE_CONTENT=1, and the receipt's frozen posthog_content_export purpose approval |
| Cloud-hosted prompts, outputs, tool arguments and tool results in PostHog | Metadata/digests | Both content env switches and the event's frozen workspace approvals for posthog_content_export plus hosted_generation_tool_content |
Environment variables are deployment kill switches, never customer consent, and
a host-supplied Boolean is only the narrow call/job signal. The durable authority
is workspace_telemetry_policy.v1: an append-only owner-approved revision with
three exact purposes, separate allowed state, purpose, approver/timestamp and
14–30 day retention. Owners set it with the admin-only
cloud_set_workspace_telemetry_policy; members can inspect the safe, subject-redacted
state with cloud_get_workspace_telemetry_policy. Trusted operators have matching
sonaloop-cloud workspace-telemetry-policy-set and
workspace-telemetry-policy commands.
The setter is a complete, idempotent replacement: all three purposes must be present so an omitted field can never inherit an old approval. For example:
sonaloop-cloud workspace-telemetry-policy-set <workspace-id> '{
"mcp_content_capture": {
"allowed": true,
"purpose": "Reproduce explicitly approved support incidents",
"retention_days": 14
},
"posthog_content_export": {
"allowed": false,
"purpose": "",
"retention_days": 14
},
"hosted_generation_tool_content": {
"allowed": false,
"purpose": "",
"retention_days": 14
}
}' --approved-by '<operator-subject-or-change-id>'
Remote MCP does not accept --approved-by: the server always records the live
owner subject and ignores any caller attempt to choose the approver.
Each audit receipt freezes a privacy-reduced policy snapshot. The exporter validates that snapshot and fails closed; therefore an old/legacy event cannot acquire content just because a workspace opts in later. Revocation applies immediately to new receipts. An already-created receipt retains its lawful event-time snapshot until its frozen retention expires. Enabling local ledger content never authorizes external export. All content modes remain bounded and recursively redact authorization data, cookies, credentials and secret-shaped fields.
Hosted projection receipts follow the same rule and additionally retain a separate metadata-only property set. If either content deployment switch is disabled while a content-bearing receipt is pending, delivery automatically downgrades to that frozen metadata projection. A malformed or legacy snapshot is metadata-only. Later workspace enablement never upgrades an older event; revocation affects newly created events while already collected events keep their event-time decision until their 14–30 day frozen retention expires. Expiry removes the pending receipt and replaces the local projection payload with a content-free event-UUID tombstone. That minimal idempotency marker keeps the longer-lived authoritative journal from accidentally recreating expired telemetry content during reconciliation.
PostHog's AI Observability storage is deliberately not the durable replay archive. PostHog documents that large AI properties such as prompts and responses are removed after 30 days while trimmed event metadata remains. See AI data retention. Choose the local ledger/journal retention independently, record the workspace consent for any content capture, and make support tooling explain which source still retains which fields. A longer local retention must never be inferred from PostHog's policy or enabled merely because remote content has expired.
External-host limitation¶
When a customer connects Sonaloop to an external Claude, Mistral, ChatGPT or
other MCP host, that host owns inference. Cloud cannot see the host's original
conversation, system prompt, hidden reasoning, model output, provider retries or
model-token accounting. A front-door call may explicitly supply a user_request for
Sonaloop to store, but that proves only what the caller submitted—not equality with an
unobservable Mistral/Le Chat transcript. Sonaloop otherwise records only the MCP calls the host sends.
W3C traceparent, x-request-id, x-sonaloop-operation-id and
idempotency-key headers improve correlation when the host supports them;
equivalent operation/session metadata can travel in MCP _meta. Cloud
generates safe correlation ids when none are supplied. Calls without propagated
W3C context remain separate interaction traces but are grouped by the governed
run's AI session and MCP conversation. This makes the Sonaloop
portion of a job inspectable, but it does not invent an unobserved
$ai_generation. Full external-host replay requires tracing in that host and a
shared correlation contract. Cloud-owned agent runs can provide the full
observed generation tree because Sonaloop owns that provider call.
cloud_get_research_job_trace reconstructs the local, workspace-confined timeline without
depending on PostHog. It reports attempt ordinals, duplicate suppression, methodology, run/critic
state, evidence health, release commit and audit delivery health. For jobs created through the
retry-safe front door it also returns the workspace-owned verbatim initial request from project state;
“verbatim” here means the exact user_request supplied to Sonaloop, not an independently observed
copy of the provider chat. That text is not smuggled into metadata-only PostHog events. Any property outside the observed
boundary is explicitly unknown, not inferred from an incomplete trace.
Every replay also carries coverage: local_ledger is complete or
audit_gap, content is complete, redacted or audit_gap, and an external
MCP host is always external_boundary_only. The overall state becomes
audit_gap when receipts are incomplete; otherwise it remains
external_boundary_only because a complete Sonaloop ledger still cannot prove
hidden host generations. Support must not present a metadata-only or truncated
replay as a full conversation transcript.
For new attempts, local_ledger_granularity=request_lifecycle_v1 and the
audit_complete score verifies the immutable lifecycle rather than assuming a
tool receipt means the response arrived. The assessment requires received and
an observed transport terminal; after body_read, it requires either an
rpc_parsed receipt or an explicit protocol error. Every tool_started must
have a matching tool_completed/tool_failed and receipt_committed. A parsed
tool call that never reaches the dispatch seam is surfaced as a gap, as is a
truncated lifecycle page.
The replay exposes response_handed_to_asgi and deliberately reports
client_ack_observed=false. A passed lifecycle therefore means that Sonaloop's
locally observable boundary is internally complete; it never upgrades the
overall external-host coverage to a full replay. Legacy attempts without
lifecycle rows retain completion_receipts_only granularity and an unknown
transport assessment. These limitations are immutable facts of the captured
boundary, not fields that PostHog can fill in later.
If a project spans pre- and post-rollout traffic, replay reports
mixed_request_lifecycle_and_completion_receipts and remains unknown; one new
complete request never retroactively certifies its legacy attempts.
Metadata-only smoke test¶
Use a non-production test workspace first. The test needs no content capture and no personal API key.
- Put the project API key and independent HMAC key in the deployment's secret
store. Set the EU ingestion host and exporter target shown above. Leave all
content flags at
0, then restart Cloud. A workspace telemetry policy is not needed for this metadata-only smoke; its secure default is revision 0/all denied. - Establish a fresh authorized MCP connection so it sends
initialize, then call a harmless read tool such aslist_personasin exactly one known workspace. If the client supports custom headers, give the tool request a non-sensitive uniquex-request-id; otherwise retain thex-request-idreturned by Cloud. -
Verify the local tool row, request lifecycle and outbox receipts first, using an operator connection and both the exact workspace id and request id as filters:
SELECT id, workspace_id, request_id, trace_id, tool_name, status FROM cloud_mcp_execution_audit WHERE workspace_id = '<workspace-id>' AND request_id = '<request-id>'; SELECT lifecycle_id, phase, status, tool_name, created_at FROM cloud_mcp_transport_lifecycle WHERE workspace_id = '<workspace-id>' AND request_id = '<request-id>' ORDER BY created_at, id;Confirm the lifecycle has the expected
received→body_read→rpc_parsed→ tool →receipt_committed→ response path, or an honest disconnect/error terminal. Confirm correspondingmcp_auditandmcp_transportoutbox rows progress frompending/deliveringtodelivered. A response-time dependency on PostHog is a release blocker. -
In PostHog's live events/activity view, find the corresponding
sonaloop_mcp_request_phaseevents. Confirm theirsonaloop_transport_lifecycle_idis stable within the request, phase ids are distinct,sonaloop_capture_level=metadata, and the final phase agrees with the local lifecycle. Do not interpretrespondedas a client acknowledgement. - Find one
$mcp_tool_callwith the matchingsonaloop_transport_lifecycle_id. Request, operation, project and run ids are HMAC pseudonyms externally and therefore intentionally do not equal the local values. Confirm a corresponding$ai_spanhas the samesonaloop_audit_event_idand$ai_trace_id. Lifecycle and tool trace ids also match unless the tool adopted a distinct MCP_meta.traceparent. - If the client declared a recognized
initialize.clientInfo, confirm later request phases and tool events carry the expected closed$mcp_client_name. Verify the raw bearer and raw client name/version are absent locally and in PostHog. Treat the family as connector identity, not proof of the model or provider. - Make two harmless calls tied to the same governed run without an inbound
traceparent. Confirm they have different$ai_trace_idvalues but the same$ai_session_idon$ai_spanand the same$mcp_conversation_idon$mcp_tool_call. Then propagate the first responsetraceparentand confirm the next span joins that trace with the expected$ai_parent_id. - For a cohort-preflight canary, confirm both tool projections expose the same closed
sonaloop_outcome; verify that no rationale/result text is present. - Confirm every MCP event has
sonaloop_capture_level=metadata, that its distinct/session identities are HMAC pseudonyms, and that no raw prompt, workspace id, credentials or argument/result text appears. - Only if Cloud-hosted tracing is part of the rollout, enable
SONALOOP_POSTHOG_HOSTED_ENABLED=1and run a harmless canary automation. First verify a row incloud_hosted_telemetry_projectionand aevent_kind='hosted_posthog'outbox receipt progress todelivered. Verify one$ai_generationper provider attempt plus any tool$ai_spanchildren under the same trace. Keep hosted content capture at0for this test.
For automated readback, run steps 4–10 from a restricted operator job using the optional personal key, project id and API host. Never return the personal key in logs, test output or a browser response.
Operations and rollback¶
- Alert on
$mcp_tool_callerror rate and latency; use$ai_generationfor hosted provider latency, token and error analysis. - Alert separately on
sonaloop_mcp_request_phasevaluesclient_disconnectedandtransport_error, and on started tools without a terminal/receipt pair. A disconnect aftertool_completedandreceipt_committedbut beforerespondedmeans Sonaloop completed and recorded the call while the connector abandoned the HTTP exchange; it does not identify the connector's internal error code or retry policy. - Diagnose missing PostHog events from the local execution and transport ledgers first. Exporter errors are logged, while the customer operation remains successful. Inspect pending, delivering and dead-letter counts per workspace; after remediation, requeue only the selected dead receipts.
- For hosted traces, compare the durable agent journal with
cloud_hosted_telemetry_projection. A journal row without a projection row is a local journal-to-outbox gap that the next worker reconciliation must repair; PostHog is never the recovery source. - Disable remote export by clearing
SONALOOP_MCP_AUDIT_EXPORTERS. This leaves the tenant-bound local audit enabled. - Disable only hosted generation export with
SONALOOP_POSTHOG_HOSTED_ENABLED=0. Hosted receipts remain pending and are not claimed or charged retry attempts until the switch is re-enabled. - The managed worker stops and joins during normal Cloud shutdown. Use a graceful rollout so a claimed lease is either delivered or safely reclaimed.
- Apply PostHog access and retention rules separately from the local ledger's retention. Removing a local row does not delete an already exported event.