Add a cross-client telemetry-names contract (+ Rust reference adoption)#237
Closed
joshmouch wants to merge 1 commit into
Closed
Add a cross-client telemetry-names contract (+ Rust reference adoption)#237joshmouch wants to merge 1 commit into
joshmouch wants to merge 1 commit into
Conversation
ea706e5 to
45034db
Compare
07b7794 to
ab8250c
Compare
joshmouch
added a commit
to joshmouch/agent-host-protocol
that referenced
this pull request
Jun 18, 2026
…to .NET holder Bring the shared cross-client telemetry source-of-truth files to the SAME bytes as the telemetry-names-contract PR (microsoft#237) so the two PRs stay byte-identical for cross-PR dedup, then regenerate the .NET telemetry holder. Byte-identical to microsoft#237: types/telemetry/registry.ts, registry.test.ts scripts/read-telemetry.test.ts, generate-kotlin.ts, generate-swift.ts package.json: hand-applied the shared additions (verify:generated script + test-glob types/telemetry/*.test.ts scripts/*.test.ts) while keeping this branch's own generate:dotnet entry. clients/dotnet .NET holder: regenerated via 'npm run generate:dotnet' so it gains the host-* stream constants (StreamHostEvent/Subscription/Resource/ Snapshot/Summaries) emitted from the synced registry. scripts/verify-generated.ts: body byte-identical to microsoft#237; HOLDERS list scoped to the .NET holder this branch actually ships (the go/rust/kotlin/swift telemetry holders are introduced by microsoft#237 and are not on this branch's lineage, so a byte-identical 4-holder list would fail 'npm test' here).
joshmouch
added a commit
to joshmouch/agent-host-protocol
that referenced
this pull request
Jun 18, 2026
…l example, DI interface Land the .NET telemetry best-practices audit follow-ups on the microsoft#237 self-instrumentation base: - MultiHostClient: route the 5 per-host drop tags (host-event/subscription/ resource/snapshot/summaries) through the generated AhpTelemetryNames.StreamHost* constants instead of raw literals, cached as static KeyValuePairs (no per-callback alloc). Regression-guarded by HostStreamDropTagTests (source gate + runtime proof). - MultiHostClient reconnect supervisor: emit the existing AhpTelemetry.Reconnects counter tagged ahp.outcome=ok on a successful reconnect and =error per failed attempt + on MaxAttempts exhaustion (previously uninstrumented). - TelemetryTests: add reconnect / dropped-events / malformed-frames coverage and tag-VALUE assertions (rpc.method, ahp.outcome, ahp.message.kind, ahp.stream); reference AhpTelemetryNames constants instead of "ahp.client.*" literals. - Extract IMultiHostClient interface; register it in AddAgentHostProtocol (same singleton). Add AhpServiceCollectionExtensions.TelemetrySourceName one-liner. - De-duplicate instrument descriptions: generate AhpTelemetryNames.*Description constants (generate-csharp.ts) as the single source; Telemetry.cs references them. - examples/OtelExport: Shape C consumer wiring OpenTelemetry (.AddSource/.AddMeter) with a console exporter over a self-contained loopback transport. All 356 dotnet tests pass (Debug + Release); verify:generated + verify:changelog + telemetry registry tests green; example builds and prints spans+metrics.
3e623af to
cea7757
Compare
A single source of truth (types/telemetry/registry.ts) for the self- instrumentation span / metric / attribute names every AHP client emits about its own operation, consumed by the existing scripts/generate-*.ts codegen to emit an idiomatic generated name-holder per client (Go / Rust / Swift / Kotlin / TypeScript), plus a generated-output freshness gate so the holders can't drift. The Rust client is the reference adopter: it emits the metrics through the metrics facade (a no-op until the host installs a recorder), named entirely from the generated holder, with a recorder-based emission test. Client self- instrumentation is distinct from the protocol's OpenTelemetry-over-AHP channel; see docs/specification/self-instrumentation.md.
cea7757 to
aab017e
Compare
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this belongs at the protocol layer
The self-instrumentation names (spans, metrics, attributes) an AHP client emits about its own operation are an interop surface, the same way the wire types are. They have to be byte-identical across clients or an operator running a fleet of mixed-language agents cannot write one OpenTelemetry dashboard or alert that works across all of them. That requirement is what makes the name contract a protocol concern rather than a per-client detail; the instrumentation code stays per-client and idiomatic, and only the names are shared. This is separate from the existing "OpenTelemetry over AHP" channel (
docs/specification/telemetry-channel.md), which carries OTel data over the protocol. The scope question is open in #239.What
Adds
types/telemetry/registry.tsas the single source of truth for those names, generates an idiomatic holder per client, and wires the Rust client to emit the metrics as a reference adopter.AhpTelemetryNames(static class)ActivitySource+Meter(in the .NET-client draft, #206)ahp_types::telemetry(pub const &str)metricsfacade (this PR) plustracingdiagnosticstelemetry.generated.go(consts)AhpTelemetryNames(caseless enum)AhpTelemetryNames(object)Full rationale and the per-client OTel-export story live in
docs/specification/self-instrumentation.md.How the names are represented
The names are string enums (
TelemetrySpan,TelemetryMetric,TelemetryAttribute,TelemetryOutcome), the same shape as the protocol enums (e.g.ChangesetOperationStatus). Each name's description is a JSDoc comment on its enum member, extracted by the generators the same way as any other enum (member.getJsDocs(), via the sharedscripts/read-telemetry.tshelper), so the description lives next to the name with no separate lookup table:Metric units are a small
Record<TelemetryMetric, string>map. The generators emit a flat string holder (telemetry names are consumed as raw strings byMeterandmetrics, not as language enums), carrying each member's description through as a doc comment on the generated constant.types/telemetry/registry.test.tsguards the invariants (dotted names, unique, every metric has a unit), and averify-generatedgate fails CI if a holder drifts from the contract.Rust reference adoption
The
ahpclient emits self-instrumentation metrics through themetricsfacade (messages sent and received, request duration and in-flight, reconnects, dropped events per stream, malformed frames, withrpc.*andahp.*attributes), all named fromahp_types::telemetry. The facade is a no-op until the host installs a recorder, so it is zero-cost when unobserved. A recorder-based integration test drives a request and asserts the metrics emit with the right attributes and that the in-flight gauge returns to zero on completion; a separate test asserts a cancelled request is taggedahp.outcome=cancelled.Verified
go buildplusgofmt -l,cargo buildpluscargo fmt --check,cargo test,cargo clippy,swift build,kotlin compileKotlin, the TypeScripttsc --noEmit, and thenpm testsuite (typecheck, lint, theverify-generatedfreshness gate,registry.test.ts, and theread-telemetrytest) all pass.Status
Draft until the scope question in #239 is decided. Open to adjusting the naming scheme, the per-language holder shape, which client adopts first, or dropping the Rust
metricsdependency in favor of holders-only.