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
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
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.2.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
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
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
{
"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
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
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
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.