You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[FEATURE] Close three observability gaps: GenAI token metrics, controller reconcile Events, and a controller log→OTLP bridge (all gated default-OFF) #2148
I have searched the existing issues to avoid creating a duplicate
By submitting this issue, you agree to follow the Code of Conduct
📝 Feature Summary
Add three small, independent, default-OFF observability improvements that build directly on the OTel pipeline landed in #1433 / #1292:
GenAI token-usage metrics in the ADK (go/adk) — the first MeterProvider in kagent, emitting the OpenTelemetry GenAI-semconv instrument gen_ai.client.token.usage.
Kubernetes Events on reconcile for the Agent / ModelConfig / MCPServerTool / RemoteMCPServer controllers (go/core), mirroring the pattern the harness/substrate controller already uses.
Controller log → OTLP bridge (go/core) — an otelzap tee on the controller zap core so logs can be exported over OTLP additively, without changing stdout.
Each is additive and byte-identical to current behavior when its gate is unset. Happy to split this into three separate PRs (they're fully independent) — filing one issue first to align on approach per CONTRIBUTING.
❓ Problem Statement / Motivation
kagent already has solid tracing (controller distributed tracing + A2A propagation, #1433) and standardized OTel env-var naming (#1292), but three observability primitives are still missing on main:
No metrics pipeline at all. There is no MeterProvider anywhere in the repo (go/adk/pkg/telemetry/ contains only attributes.go + tracing.go). Every model adapter already computes token usage — go/adk/pkg/models/{openai,anthropic,bedrock,ollama,sapaicore}_adk.go all populate genai.GenerateContentResponseUsageMetadata (PromptTokenCount / CandidatesTokenCount / ThoughtsTokenCount) — but that data is never recorded to a metric, so operators can't graph or alert on token spend / cost per model or provider without scraping traces. This is the still-unchecked "Metrics: LLM calls/token-count" item from the (now closed) observability EPIC [EPIC] Observability improvements #130, and the abandoned Prometheus attempt in Enable /metrics endpoint with prometheus metrics #1412 (closed unmerged) shows the demand is real; this proposes a standards-aligned OTLP/semconv take instead of a scraped /metrics endpoint.
Reconcile outcomes are invisible via kubectl. The Agent, ModelConfig, MCPServerTool, and RemoteMCPServer controllers emit no k8s Events (none reference EventRecorder/.Event(). Users debugging a bad Agent/ModelConfig get nothing from kubectl describe, even though the agentharness-substrate-controller already demonstrates the Event pattern in this codebase.
Controller logs can't be shipped over OTLP.go/core/internal/telemetry/ has only tracing.go. The ADK side already honors OTEL_LOGGING_ENABLED, but the controller's zap logs remain stdout-only, so log/trace correlation isn't possible in the controller.
Who benefits: anyone running kagent in a cluster with an OTel collector / ClickHouse / Langfuse / Datadog backend who wants token-cost dashboards, kubectl-visible reconcile status, and correlated controller logs.
💡 Proposed Solution
Three independent changes, each gated and additive. All reuse existing conventions.
1. GenAI token metrics (go/adk)
New pkg/telemetry/metrics.go: a MeterProvider + OTLP metric exporter that reuses the existing resolveEndpoint(...) / resolveOTLPProtocol(...) helpers already in tracing.go, so protocol/endpoint resolution is identical to traces.
Emit gen_ai.client.token.usage — Int64 histogram, unit {token} — with attributes gen_ai.token.type (input/output), gen_ai.request.model, gen_ai.provider.name, per GenAI semantic conventions.
Record from the A2A executor loop where per-event usage metadata is already surfaced (pkg/a2a/executor.go): input = PromptTokenCount, output = CandidatesTokenCount + ThoughtsTokenCount. Model/provider labels resolved at startup (cmd/main.go).
Gate:OTEL_METRICS_ENABLED=true (+ the standard OTEL_EXPORTER_OTLP[_METRICS]_ENDPOINT/_PROTOCOL), matching the existing OTEL_TRACING_ENABLED / OTEL_LOGGING_ENABLED gates. Unit test included.
2. Kubernetes Events on reconcile (go/core)
Add an EventRecorder to the Agent / ModelConfig / MCPServerTool / RemoteMCPServer controllers: Normal Accepted / ToolsDiscovered, Warning ReconcileFailed / ValidationFailed — mirroring the existing agentharness-substrate-controller pattern. Nil-guarded so it's a no-op if no recorder is wired.
RBAC is already covered by the leader-election role (events: create,patch).
Gate: none needed — it's a purely additive status surface.
3. Controller log → OTLP bridge (go/core)
New internal/telemetry/logging.go: an otelzap tee installed on the controller zap core via RawZapOpts(zap.WrapCore(NewTee(...))). Stdout logging is preserved; OTLP is additive.
Gate:OTEL_LOGGING_ENABLED=true (same gate name already used ADK-side).
Acceptance bar: everything default-OFF, additive, unit-tested, make lint + make test green, DCO sign-off on every commit.
🔄 Alternatives Considered
Prometheus /metrics endpoint (Enable /metrics endpoint with prometheus metrics #1412). A prior attempt exposed agent LLM/token metrics via a scraped Prometheus endpoint; it was closed unmerged. OTLP histograms fit kagent's existing OTLP-first pipeline (same exporter/env-var machinery as traces), avoid a second telemetry transport, and align with GenAI semconv — so downstream backends (ClickHouse, Langfuse, Datadog, Honeycomb) get token metrics through the collector they already run.
Status conditions instead of Events (gap 2). Conditions and Events are complementary; Events give the kubectl describe timeline that's missing today and are cheap/additive. Conditions could be a follow-up.
Affected Service(s): Multiple services / System-wide (App Service + Controller Service)
📋 Prerequisites
📝 Feature Summary
Add three small, independent, default-OFF observability improvements that build directly on the OTel pipeline landed in #1433 / #1292:
go/adk) — the firstMeterProviderin kagent, emitting the OpenTelemetry GenAI-semconv instrumentgen_ai.client.token.usage.Agent/ModelConfig/MCPServerTool/RemoteMCPServercontrollers (go/core), mirroring the pattern the harness/substrate controller already uses.go/core) — anotelzaptee on the controller zap core so logs can be exported over OTLP additively, without changing stdout.Each is additive and byte-identical to current behavior when its gate is unset. Happy to split this into three separate PRs (they're fully independent) — filing one issue first to align on approach per CONTRIBUTING.
❓ Problem Statement / Motivation
kagent already has solid tracing (controller distributed tracing + A2A propagation, #1433) and standardized OTel env-var naming (#1292), but three observability primitives are still missing on
main:MeterProvideranywhere in the repo (go/adk/pkg/telemetry/contains onlyattributes.go+tracing.go). Every model adapter already computes token usage —go/adk/pkg/models/{openai,anthropic,bedrock,ollama,sapaicore}_adk.goall populategenai.GenerateContentResponseUsageMetadata(PromptTokenCount/CandidatesTokenCount/ThoughtsTokenCount) — but that data is never recorded to a metric, so operators can't graph or alert on token spend / cost per model or provider without scraping traces. This is the still-unchecked "Metrics: LLM calls/token-count" item from the (now closed) observability EPIC [EPIC] Observability improvements #130, and the abandoned Prometheus attempt in Enable /metrics endpoint with prometheus metrics #1412 (closed unmerged) shows the demand is real; this proposes a standards-aligned OTLP/semconv take instead of a scraped/metricsendpoint.kubectl. TheAgent,ModelConfig,MCPServerTool, andRemoteMCPServercontrollers emit no k8s Events (none referenceEventRecorder/.Event(). Users debugging a bad Agent/ModelConfig get nothing fromkubectl describe, even though theagentharness-substrate-controlleralready demonstrates the Event pattern in this codebase.go/core/internal/telemetry/has onlytracing.go. The ADK side already honorsOTEL_LOGGING_ENABLED, but the controller's zap logs remain stdout-only, so log/trace correlation isn't possible in the controller.Who benefits: anyone running kagent in a cluster with an OTel collector / ClickHouse / Langfuse / Datadog backend who wants token-cost dashboards,
kubectl-visible reconcile status, and correlated controller logs.💡 Proposed Solution
Three independent changes, each gated and additive. All reuse existing conventions.
1. GenAI token metrics (
go/adk)pkg/telemetry/metrics.go: aMeterProvider+ OTLP metric exporter that reuses the existingresolveEndpoint(...)/resolveOTLPProtocol(...)helpers already intracing.go, so protocol/endpoint resolution is identical to traces.gen_ai.client.token.usage— Int64 histogram, unit{token}— with attributesgen_ai.token.type(input/output),gen_ai.request.model,gen_ai.provider.name, per GenAI semantic conventions.pkg/a2a/executor.go):input = PromptTokenCount,output = CandidatesTokenCount + ThoughtsTokenCount. Model/provider labels resolved at startup (cmd/main.go).OTEL_METRICS_ENABLED=true(+ the standardOTEL_EXPORTER_OTLP[_METRICS]_ENDPOINT/_PROTOCOL), matching the existingOTEL_TRACING_ENABLED/OTEL_LOGGING_ENABLEDgates. Unit test included.2. Kubernetes Events on reconcile (
go/core)EventRecorderto theAgent/ModelConfig/MCPServerTool/RemoteMCPServercontrollers: NormalAccepted/ToolsDiscovered, WarningReconcileFailed/ValidationFailed— mirroring the existingagentharness-substrate-controllerpattern. Nil-guarded so it's a no-op if no recorder is wired.events: create,patch).3. Controller log → OTLP bridge (
go/core)internal/telemetry/logging.go: anotelzaptee installed on the controller zap core viaRawZapOpts(zap.WrapCore(NewTee(...))). Stdout logging is preserved; OTLP is additive.OTEL_LOGGING_ENABLED=true(same gate name already used ADK-side).Acceptance bar: everything default-OFF, additive, unit-tested,
make lint+make testgreen, DCO sign-off on every commit.🔄 Alternatives Considered
/metricsendpoint (Enable /metrics endpoint with prometheus metrics #1412). A prior attempt exposed agent LLM/token metrics via a scraped Prometheus endpoint; it was closed unmerged. OTLP histograms fit kagent's existing OTLP-first pipeline (same exporter/env-var machinery as traces), avoid a second telemetry transport, and align with GenAI semconv — so downstream backends (ClickHouse, Langfuse, Datadog, Honeycomb) get token metrics through the collector they already run.kubectl describetimeline that's missing today and are cheap/additive. Conditions could be a follow-up.📚 Additional Context
🙌 Contribution