Telemetry Channel
The telemetry channel is the way an agent host emits OpenTelemetry (OTel) data — logs, traces, and metrics — to AHP clients. It is a thin pass-through: payloads on the wire are OTLP/JSON values verbatim. AHP only adds the routing envelope.
This page is normative. The OTel data model itself is defined by opentelemetry-proto; AHP does not redeclare it.
URI Scheme
Section titled “URI Scheme”Telemetry channels use the ahp-otlp: scheme. The authority and path portion of the URI are implementation-defined. A host MAY also advertise an RFC 6570 URI template; the client expands the template using values from this spec (currently only {level} on the logs channel) and subscribes with the resulting concrete URI.
Clients MUST treat the URI as opaque apart from expanding well-known template variables defined here, and subscribe with the value the host advertised on InitializeResult.telemetry (after expansion).
There is no requirement that the URIs for the three signals share a common path, host, or any other structure. Each one is independent.
Discovery
Section titled “Discovery”The agent host advertises which OTel signals it emits — and on which channel URIs — on InitializeResult.telemetry:
// Server → Client (initialize response, excerpt){ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "0.3.0", "serverSeq": 0, "snapshots": [], "telemetry": { "logs": "ahp-otlp://logs{?level}", "traces": "ahp-otlp://traces", "metrics": "ahp-otlp://metrics" } }}Each field is optional. A host that emits no metrics simply omits metrics. A host that emits no telemetry at all omits telemetry entirely.Clients SHOULD subscribe only to the signals they can process.
Subscribing
Section titled “Subscribing”Telemetry channels are stateless*, subscribing returns an empty SubscribeResult. After the subscribe succeeds the client receives otlp/export* notifications for batches the host emits while the subscription is live.
// Client → Server{ "jsonrpc": "2.0", "id": 2, "method": "subscribe", "params": { "channel": "ahp-otlp://logs" } }
// Server → Client{ "jsonrpc": "2.0", "id": 2, "result": {} }Telemetry is not replayed on reconnect. After reconnect, clients re-subscribe and resume from the live edge.
Wire Format
Section titled “Wire Format”There is exactly one server → client notification method per OTel signal:
| Method | Channel | Payload |
|---|---|---|
otlp/exportLogs |
TelemetryCapabilities.logs |
OTLP/JSON ExportLogsServiceRequest |
otlp/exportTraces |
TelemetryCapabilities.traces |
OTLP/JSON ExportTraceServiceRequest |
otlp/exportMetrics |
TelemetryCapabilities.metrics |
OTLP/JSON ExportMetricsServiceRequest |
Each notification’s params have the shape:
{ "channel": "<the ahp-otlp: URI from InitializeResult.telemetry>", "payload": { /* OTLP/JSON ExportXxxServiceRequest, verbatim */ }}The channel field follows the universal AHP rule: every notification’s params carry the channel URI it scopes to. Clients route batches by channel, then parse payload as OTLP/JSON.
Example — logs
Section titled “Example — logs”{ "jsonrpc": "2.0", "method": "otlp/exportLogs", "params": { "channel": "ahp-otlp://logs", "payload": { "resourceLogs": [ { "resource": { "attributes": [ { "key": "service.name", "value": { "stringValue": "ahp-agent-host" } }, { "key": "ahp.session.id", "value": { "stringValue": "f1e3...e0" } } ] }, "scopeLogs": [ { "scope": { "name": "agent-host.tools" }, "logRecords": [ { "timeUnixNano": "1736870400000000000", "severityNumber": 9, "severityText": "INFO", "body": { "stringValue": "tool call started" }, "attributes": [ { "key": "tool.name", "value": { "stringValue": "read_file" } } ] } ] } ] } ] } }}Example — traces and metrics
Section titled “Example — traces and metrics”The traces and metrics notifications have the same envelope; only the top-level field name inside payload differs (resourceSpans for traces, resourceMetrics for metrics). Refer to opentelemetry-proto for the full data shapes.
Filtering
Section titled “Filtering”The logs channel supports optional subscriber-side severity filtering via an RFC 6570 URI template. A host that supports filtering advertises a template containing the {level} variable, e.g. "ahp-otlp://logs{?level}"; a host that does not support filtering advertises a literal URI.
| Variables in template | Meaning |
|---|---|
| (none) | All log records are delivered. |
{level} |
Minimum OTLP SeverityNumber to deliver, as a short name (case-insensitive): trace, debug, info, warn, error, fatal. The server delivers records whose severityNumber falls in the corresponding band or above (e.g. info → severityNumber >= 9, covering INFO/WARN/ERROR/FATAL). |
// Host advertises: "ahp-otlp://logs{?level}"// Client expands {level=info} and subscribes to:{ "jsonrpc": "2.0", "id": 7, "method": "subscribe", "params": { "channel": "ahp-otlp://logs?level=info" } }Each distinct expansion is its own subscription URI from the server’s point of view, so two clients subscribed at different levels receive independent, pre-filtered streams. Hosts that advertise a literal URI (no {level}) deliver all severities.
No filter variables are currently defined for traces or metrics.
Correlation
Section titled “Correlation”Hosts SHOULD use standard OpenTelemetry resource and record attributes to correlate telemetry with AHP entities — for example:
service.name(Resource): identifies the host.ahp.session.id(Resource or LogRecord/Span attribute): the session URI’s UUID.ahp.turn.id,ahp.tool_call.id(LogRecord/Span attribute): the turn or tool call the record belongs to.
These are conventions, not protocol fields; clients that want to slice telemetry by session/turn do so by attribute filtering on the receiving side.