Monitor self-hosted Intelligence with OpenTelemetry
Send traces, metrics, product events, and logs from a self-hosted CopilotKit Intelligence deployment to your own OpenTelemetry collector, and read installation health from them.
Open your coding agent in your project's folder, or in an empty folder for a new app.This runs in a coding agent on your computer.
Overview#
A self-hosted CopilotKit Intelligence deployment can send its telemetry to an OpenTelemetry collector that you run. From there, you forward it to the tracing and logging tools that your team already uses. This page lists each stream, what it contains, how to turn it on, and which signals show that the installation is healthy.
Intelligence does not install a collector. The streams on this page go only to the endpoints that you configure, and each stream is off until you configure one.
When should I use this?#
- You self-host Intelligence with the Helm chart or the ECS bundle.
- You want request traces, errors, and logs from Intelligence in your own observability tools.
- You want to see whether a new installation or an upgrade is healthy.
Cloud-hosted Intelligence does not export these streams to customers.
Prerequisites#
- A running self-hosted Intelligence deployment.
- An OpenTelemetry Collector (the
otel/opentelemetry-collector-contribdistribution) that the Intelligence services can reach over HTTP. Intelligence sends OTLP over HTTP only. It does not use gRPC. - For pod logs on Kubernetes: a collector that runs as a DaemonSet and can read
/var/log/podson each node.
The streams#
| Stream | Signal | Sent by | Turned on by | Status |
|---|---|---|---|---|
| Backend traces | OTLP traces | app API, realtime gateway, Automatic Learning scheduler | observability.tracing (Helm) or otlpEndpoint (ECS) | Stable |
| Scheduler metrics | OTLP metrics | Automatic Learning scheduler | observability.tracing (Helm) | Stable |
| Product events | OTLP logs | app API, Automatic Learning scheduler and reconciler | appApi.channelTelemetry (Helm) | Preview |
| Application logs | JSON lines on stdout | every service | config.logLevel (Helm) | Stable |
Application logs are not sent over OTLP. Your collector or log agent reads them from the container output.
Product events are a preview
The names and attributes of the eip.* product events can change between releases. Build dashboards on traces and logs if you need a stable contract.
Send traces and metrics#
Helm#
Set a full OTLP/HTTP traces endpoint. The path must end in /v1/traces.
observability:
tracing:
enabled: true
endpoint: http://otel-collector.observability.svc:4318/v1/traces
environment: productionenvironmentis required when tracing is on. Use 1 to 64 letters, numbers, dots, underscores, slashes, or hyphens.- The chart sends traces from the app API, the realtime gateway, and the Automatic Learning scheduler to this endpoint.
- The scheduler also sends its metrics to the same collector, at the same base URL with
/v1/metrics. helm installandhelm upgradefail if the endpoint does not end in/v1/traces.
ECS#
Set otlpEndpoint to the base URL of your collector, without a path.
{
"otlpEndpoint": "https://otel-collector.example.internal:4318"
}- The release stack sends traces from the app API and the realtime gateway to
<otlpEndpoint>/v1/traces. cpki-ecs deployremoves a trailing/or/v1/traces, so a value in that form also works.- The ECS bundle sets
deployment.environment.nametoecs. You cannot change it. - The collector must be reachable from the private subnets of the deployment.
What every span carries#
Each span has these resource attributes:
| Attribute | Value |
|---|---|
service.name | app-api, realtime-gateway, or learning-scheduler |
service.version | The image tag of the release |
deployment.environment.name | observability.tracing.environment on Helm, ecs on ECS |
The services trace inbound HTTP and WebSocket upgrades, outbound HTTP, PostgreSQL, and Redis commands that run inside a request. Useful span attributes include:
http.request.method,http.route, andhttp.response.status_codedb.operation.nameon PostgreSQL spanscpki.organization.idandcpki.project.idon requests from a known projectprovider.effectspans on the realtime gateway for Slack and Microsoft Teams calls, withcpki.provider.adapterandcpki.provider.outcomereplay.restorespans on the realtime gateway, withthread_id,organization_id, andproject_id
The services remove these attributes before a span leaves the process:
db.statementanddb.query.text(SQL text)http.url,url.full,url.path, andurl.query(full URLs, paths, and query strings)- every
http.request.header.*andhttp.response.header.*attribute
Health probe requests do not create spans.
Spans still identify clients
Server spans keep client.address (the client IP address) and user_agent.original. If your policy does not allow IP addresses in your tracing tool, delete them in the collector.
Send product events#
Product events are OTLP log records that describe product actions, for example a thread that was created or a skill that was approved. Use them to build your own usage reports.
appApi:
channelTelemetry:
endpoint: http://otel-collector.observability.svc:4318/v1/logs
tokenSecret:
name: otel-collector-token
key: token- The endpoint must end in
/v1/logs. - Set
tokenSecretorheaders. The chart fails if you set neither. - The app API sends the token as
Authorization: Bearer <token>. - To send other headers, set
headersto a literalkey=value,key=valuestring. If both are set, the token wins forAuthorization. - This stream has no ECS equivalent.
| Event | Sent when |
|---|---|
eip.thread.created | A thread is created |
eip.thread.run_requested | An agent run is requested on a thread |
eip.channel.created | A Channel is created |
eip.channel.attached | A Channel is connected to Slack or Microsoft Teams |
eip.channel.inbound | A Channel receives a new message event |
eip.learning.run_started | An Automatic Learning run starts |
eip.learning.run_completed | An Automatic Learning run ends |
eip.learning.skill_approved | A reviewer approves a skill |
eip.learning.skill_pulled | A runtime pulls a skill |
Every attribute name starts with copilotkit.. The key that you use to remove duplicates after a retry is different for each event family:
- Thread and Learning events:
copilotkit.event_id, in the form<event>:<organization>:<project>:<resource>. Oneip.learning.skill_pulled, the resource part is a new random ID for each pull. Two pulls of the same skill are two events. eip.channel.inbound:copilotkit.channel_event_id, the ID of the inbound message event.eip.channel.createdandeip.channel.attached: no event ID. Do not remove duplicates of these events by an ID.
Product events never contain message content, raw provider payloads, credentials, Channel names, thread names, skill text, or insight text. They do contain identifiers: organization, project, thread, run, and API key IDs, and the ID of the signed-in user. eip.channel.created and eip.channel.attached can also carry copilotkit.principal_email, the email address of the user who made the change.
Collect application logs#
The app API, the realtime gateway, the thread compaction worker, the connector outbox, and the thread culler write one JSON object per line to stdout. Two services write plain text:
- App frontend: Nginx access and error logs in the default Nginx text format.
- Embeddings: the text-embeddings-inference server log.
For the JSON services:
- Level: set it with
config.logLevelon Helm. - Request lines: the app API and the realtime gateway end each HTTP request with an
http.server.request.completedline that hasduration_ms,http.method,http.route,http.status_code, andoutcome. In the app API line,method,route, andstatus_codeare nested under anhttpobject. The frontend writes no such line. To see frontend errors, read the status code in its Nginx access log. - Trace links: app API lines carry
trace_idandspan_idwhen tracing is on. Realtime gateway lines carrydd.trace_idanddd.span_idin the Datadog decimal format. - Redaction: app API logs replace authorization headers, cookies, API keys, and fields named like
password,token, orsecretwith[REDACTED].
On Kubernetes, read the logs with the collector's filelog receiver on /var/log/pods. On ECS, the logs are in the CloudWatch log groups /ecs/<deploymentName>/....
Configure the collector#
This configuration receives all OTLP streams, reads pod logs, and forwards everything to your backend. Replace the exporter with the one for your tool.
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
filelog:
# Replace intelligence with the namespace of your release.
include: [/var/log/pods/intelligence_*/*/*.log]
include_file_path: true
operators:
- type: container
# Parse the JSON services. Nginx and embeddings lines stay as text.
- type: json_parser
if: 'body matches "^\\s*\\{"'
# Link app API lines to their traces.
- type: trace_parser
if: 'attributes.trace_id != nil'
trace_id:
parse_from: attributes.trace_id
span_id:
parse_from: attributes.span_id
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 25
# A second layer. The services already remove these attributes.
transform/redact:
error_mode: ignore
trace_statements:
- context: span
statements:
- delete_key(attributes, "db.statement")
- delete_key(attributes, "db.query.text")
- delete_key(attributes, "url.full")
- delete_key(attributes, "url.query")
batch: {}
exporters:
otlphttp:
endpoint: https://otlp.your-backend.example.com
debug:
verbosity: basic
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, transform/redact, batch]
exporters: [otlphttp]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlphttp]
logs:
receivers: [otlp, filelog]
processors: [memory_limiter, batch]
exporters: [otlphttp]- Point
observability.tracing.endpointathttp://<collector-service>.<namespace>.svc:4318/v1/traces. - Point
appApi.channelTelemetry.endpointat the same host with/v1/logs. - The
filelogoperators parse the JSON lines into attributes, so you can query fields such ashttp.status_code. Lines that are not JSON keep their text in the body. - The
trace_parsermaps the app APItrace_idandspan_idonto the log record. The realtime gatewaydd.trace_idis a decimal number, so it does not map to an OpenTelemetry trace ID. - To check the setup, add
debugto a pipeline'sexportersand read the collector output.
Check installation health#
Probes#
| Service | Liveness | Readiness | What readiness checks |
|---|---|---|---|
| app API | /api/health | /api/health | A PostgreSQL query. Returns 503 if the database fails. Returns 200 with "degraded" if Channels is on but its encryption key is missing. |
| realtime gateway | /health/live | /health/ready | Drain state and the license gate only |
| app frontend | /healthz | /healthz | The web server only |
With the marketplace build, the app API uses /api/health/live and /api/health/ready.
Signals to watch#
- Error rate: error spans,
http.server.request.completedlines withhttp.status_code500 or higher, and frontend Nginx access lines with a 5xx status. - Database: app API
app-api health check failedlog lines, and failedpostgresql.*spans. - Realtime gateway memory:
gateway_memorylines every 5 seconds, andgateway_memory_pressurewarnings when a container uses 80% or more of its memory limit. - Product Analytics pipeline:
realtime_gateway.analytics.projector.healthlines, when Product Analytics is on. The line logs at warning level when events wait in the queue, fail, or go to quarantine, and at error level when the gateway cannot read the queue. Watchanalytics_projector.queue_depth,analytics_projector.quarantine_depth, andanalytics_projector.oldest_queue_age_ms. - Configuration: a
telemetry.config_rejectedwarning at startup. It means that an endpoint was set but rejected, and that stream is off.
What is not checked#
- No probe checks Redis, object storage, or the connection between the app API and the realtime gateway. A Redis failure shows up only as request errors and error spans.
- No service exports health metrics or connection counts. Use the signals above in your own tools.
Next steps#
- Self-host on Kubernetes has the full Helm configuration reference.
- Self-host on ECS has the full ECS configuration.
- Product Analytics shows usage inside Intelligence.