Skip to content

OpenTelemetry Open-Core ​

Export distributed traces and metrics from Intutic's own components to your OpenTelemetry collector.


Overview ​

Every long-running Intutic process — the Rust proxy, the control plane, and the MCP daemon — can export OTLP traces. A self-hosted gateway is the Rust proxy, so the proxy row applies to it: it exports when the variable is in its environment (on bare metal, the systemd env file). Export is opt-in: the proxy and the daemons start it only when OTEL_EXPORTER_OTLP_ENDPOINT is set, so a deployment that hasn't configured a collector pays no cost. The control plane also starts it whenever it runs with NODE_ENV=production, sending to http://localhost:4318 unless the variable says otherwise.

ComponentProtocolOTEL_EXPORTER_OTLP_ENDPOINTDefault OTEL_SERVICE_NAMEScope
Proxy (packages/proxy)OTLP/gRPCThe collector's gRPC address, e.g. http://otel-collector:4317intutic-proxyOpen-Core
Control planeOTLP/HTTPThe collector's HTTP base, e.g. http://otel-collector:4318 (a value ending in /v1/traces also works)control-planeEnterprise
MCP daemon (intutic-mcp-daemon)OTLP/HTTPAs the control planemcp-daemonOpen-Core

OTEL_SERVICE_NAME overrides the reported service name.

What's instrumented ​

The Node-based components (control plane, MCP daemon) use the standard @opentelemetry/auto-instrumentations-node package, which covers HTTP, Postgres, and Valkey/Redis calls automatically — filesystem instrumentation is disabled everywhere, since it's noisy and rarely useful for these workloads.

The Rust proxy uses tracing + tracing-opentelemetry, with AlwaysOn sampling and a batch exporter.

Metrics ​

Metrics ship over the same endpoint and the same opt-in gate as traces — OTLP push, nothing initializes without OTEL_EXPORTER_OTLP_ENDPOINT, and there is deliberately no /metrics pull endpoint on any component.

The proxy exports a small, deliberate instrument set (packages/proxy/src/metrics.rs — every exported name lives in that one file, next to the test that pins its Prometheus exposition name):

InstrumentKindLabelsWhat it measures
snip_compression_ratiohistograminput_type, strategyContext-compaction effectiveness per event
snip_input_bytes / snip_output_byteshistogramsinput_type, strategyCompaction sizes before/after
snip_compactedcounterinput_type, strategyCompaction event count
egress_denied / egress_would_denyobservable counters—Egress policy denials (enforcing and shadow)
policy_refusalscountersource ∈ anomaly|wasm|model_allowlist|spend_budget|key_rate_limit|key_rate_limit_unchecked, action ∈ kill|ask|reask|reask_exhausted|hold|unavailable|allowRequests governance actually refused (allow counts a request let through because the key's rate limit could not be checked)
upstream_retriescounterprovider, outcome (http_529, timeout, …)Upstream calls the proxy retried, by the failure that prompted each
upstream_fallbackscounterresult ∈ served|exhausted, providerRequests that ran their fallback chain, and whether a target answered
routing_targets_skippedcounterstage ∈ candidate|mirror, reason (wire_mismatch, model_not_allowed, budget, no_credential, not_listed, …)Models smart routing left out of a request's choice, and mirror copies not sent, by the check that excluded them

No instrument carries a workspace_id or per-detector label — per-workspace accounting is the control plane's job, and unbounded label values are how a metrics pipeline becomes the outage. The Node components export the auto-instrumentation metric set (HTTP/Postgres/Valkey durations and the like); they register no custom instruments.

Trace-context propagation ​

Every component that receives a traceparent header (W3C Trace Context) on an inbound request propagates it correctly to its own outbound calls and emitted spans — this is what lets a single trace span the harness, the proxy, and the control plane.

The CLI's sync daemon, which talks to a control plane, is a special case: it's software every open-core user installs locally, so it does not pull in the full auto-instrumentation package (that would meaningfully bloat install size and startup time for the vast majority who never configure a collector). Instead it does propagation only — if the CLI process itself was launched with an active trace context (a TRACEPARENT environment variable set by an already-instrumented parent process), it carries that context onto every request it makes to the control plane. It does not create its own spans.

Log correlation ​

@intutic/logger (the shared pino wrapper used by every TypeScript component) automatically injects trace_id and span_id into every log line when an active span exists, so logs and traces can be correlated in your observability backend without any extra configuration.

What's not covered yet ​

A logs pipeline is not packaged — traces and metrics only; log/trace correlation happens via the @intutic/logger trace-ID mixin described above rather than OTLP log export. services/sync-daemon as a library has no process of its own to instrument (see the CLI propagation note above); packages/mcp-proxy's stdio JSON-RPC proxy (intutic-mcp-proxy, as opposed to the daemon) is deliberately not instrumented with auto-instrumentation, since that process reserves stdout exclusively for protocol frames and any library that patches console/process behavior is a real risk on that channel.

The circuit breaker for AI agents