Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

agnara-telemetry

Explicit OpenTelemetry metrics and span bridges for Agnara's execution hooks. This distribution is built and versioned with the synchronized workspace set. Which versions exist on an index is answered by its PyPI project page, not by this file: a README ships inside the artifact and cannot describe the state of a publication that happens after it is built.

It imports agnara and opentelemetry-api, never a sibling adapter or the SDK.

Composition

The application installs/configures its chosen SDK and supplies a meter:

from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader

from agnara.execution import ExecutionPlan
from agnara_telemetry import OpenTelemetryMetricsHook

# In-memory example: no network exporter or global provider installation.
reader = InMemoryMetricReader()
provider = MeterProvider(metric_readers=[reader], shutdown_on_exit=False)
try:
    hook = OpenTelemetryMetricsHook(provider.get_meter("agnara_telemetry"))
    plan = ExecutionPlan.compile(definition, registry, hooks=[hook])
    # Invoke plans during your application's owned lifecycle.
    # provider.force_flush() may be called by the application when needed.
finally:
    provider.shutdown()

definition and registry are the application's declared capability and DI registry. Reuse one hook across plans; duplicate hook registration intentionally duplicates measurements. A supplied NoOpMeterProvider().get_meter(...) works without SDK setup. The package neither selects a global provider nor owns flush, shutdown, background workers, network endpoints or exporter credentials.

Measurements

Instrument Kind Unit
agnara.invocation.count Counter 1
agnara.invocation.duration Histogram s

Each terminal callback records one count and the core elapsed duration, converted from monotonic nanoseconds to seconds. The start callback stores nothing. Attributes are restricted to agnara.capability.id and agnara.invocation.outcome. Outcomes are success, failure, timeout and cancellation; unrecognized direct events map to unknown. Negative elapsed durations are rejected before recording. Use static capability IDs to avoid unbounded metric cardinality.

No tracking IDs, arguments, return values, exception details or principal data are added. Applications still control SDK resource attributes, exemplars, readers and exporters, so review their full export independently. Metric names are custom Agnara names, not a protocol or GenAI semantic-convention claim.

Counts describe terminal deliveries, not every attempted invocation. Calls rejected before core emits a start event are excluded. An explicitly returned canonical Failure counts as a successful Python return in the existing core event semantics. Ordinary instrument errors are isolated by the core runtime; a failed instrument may leave partial measurements. There are no retries or exactly-once export promises.

Ownership and validation

The hook retains only instrument handles; nested/overlapping invocations and repeated tracking IDs need no correlation map. The supplied meter must support concurrent, synchronous, non-blocking recording. Do not mutate instruments while plans are active. A custom blocking instrument blocks invocation.

Development tests use the pinned OpenTelemetry API/SDK 1.44.0 with an in-memory reader and an in-memory span exporter: uv run pytest tests/telemetry tests/architecture. Tests cover exact values/units, attributes, span names, status mapping, parent/child nesting, released correlation state, redaction, runtime outcomes, nested and concurrent execution, thread sharing, API no-op behavior and application-owned lifecycle. They do not establish free-threading compatibility, network exporter conformance, screening of application-added SDK data or performance superiority.

Spans

OpenTelemetryTracingHook(tracer) opens one span per invocation and ends it on the matching terminal event:

from opentelemetry.sdk.trace import TracerProvider

from agnara_telemetry import OpenTelemetryTracingHook

provider = TracerProvider(shutdown_on_exit=False)
# The application adds its own span processor and exporter here.
try:
    hook = OpenTelemetryTracingHook(provider.get_tracer("agnara_telemetry"))
    plan = ExecutionPlan.compile(definition, registry, hooks=[hook])
finally:
    provider.shutdown()

The span is named after the capability, is INTERNAL, and carries only agnara.capability.id and agnara.invocation.outcome. success sets OK; failure and timeout set ERROR with the outcome word as the description; cancellation is recorded but left UNSET, because the caller withdrew rather than the capability failing. No exception text, argument, result, principal, transport field or tracking ID is attached, and no span events are recorded.

Pairing uses the runtime's invocation_id, never a caller tracking_id. That identity is not exported: it would be unbounded cardinality on every span, and the span ID already identifies the span.

A nested invocation becomes a child span, because the started span is attached to the OpenTelemetry context; invocations in sibling tasks are unrelated, because each task holds its own copy of that context. Register the hook at most once per plan — a second registration opens a second span for one invocation. Delivering events to this hook by hand, out of order, is outside the runtime's nesting guarantee: the spans still end, but the previous context is not restored.

Metrics and span hooks compose on one plan and can be registered together.

Joining a caller's trace

Agnara reads no propagation header. A capability span is parented by whatever OpenTelemetry context is current when the invocation starts, so it joins a caller's distributed trace exactly when the application has propagated that caller's context — typically with the ASGI or client instrumentation the application already runs.

The consequence worth stating plainly: a request carrying traceparent still produces an unlinked root span if nothing extracted it. Linking is opt-in.

This is a deliberate boundary, not an omission. Honouring a caller-supplied traceparent lets that caller choose the trace identity their operation is recorded under, which is reasonable inside a trusted perimeter and rarely so at an untrusted edge. That judgment belongs to a deployment, not to a framework default. See proposed ADR 0056.

Verified over the real HTTP dispatcher and the real MCP invoker: with propagation the capability span carries the caller's trace ID and parent span; without it, a root span. Malformed headers are inert rather than fatal, and an unknown traceparent version links, because W3C requires forward compatibility and forbids only ff.

Semantic conventions

Agnara emits agnara.-namespaced names plus exactly one OpenTelemetry convention attribute: error.type, on a span whose outcome is failure or timeout, carrying that same outcome word. It is the only stable convention attribute that applies to a capability invocation. It is not an exception type: exception text is never exported, and the convention asks for a low-cardinality identifier. A cancellation carries none, because the caller withdrew rather than the capability failing. Metric attributes are unchanged.

The GenAI and MCP vocabularies are not emitted. gen_ai.operation.name with the value execute_tool would assert that every capability is a tool, which is false for an HTTP request made by a human, and the tool call argument and result attributes are payloads this package never exports. Both vocabularies are also still incubating.

If your application genuinely serves capabilities as MCP tools and you want a backend's GenAI views, add mcp.* and gen_ai.* at your MCP layer, where a tool identity and a method name actually exist. A transport-neutral capability span is the wrong place to claim them.

Two tests check every attribute both hooks emit against the installed opentelemetry-semantic-conventions package: each must be agnara.-namespaced or stable, and no incubating name may appear. See proposed ADR 0057.

No-op benchmarks remain E9.6. See proposed ADR 0054 and proposed ADR 0055.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agnara_telemetry-0.1.0a8.tar.gz (10.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agnara_telemetry-0.1.0a8-py3-none-any.whl (12.5 kB view details)

Uploaded Python 3

File details

Details for the file agnara_telemetry-0.1.0a8.tar.gz.

File metadata

  • Download URL: agnara_telemetry-0.1.0a8.tar.gz
  • Upload date:
  • Size: 10.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for agnara_telemetry-0.1.0a8.tar.gz
Algorithm Hash digest
SHA256 d9e304dfc536ed23dd777e31dea8a1246339b94a4eb5d1fd5a51b6b41e5e1522
MD5 84d32ff3c123708315ba1980c8b9d6b0
BLAKE2b-256 c02793d080ff984ee14fe2f25d59118b5b2671c0c89b1637206db995c5790c04

See more details on using hashes here.

Provenance

The following attestation bundles were made for agnara_telemetry-0.1.0a8.tar.gz:

Publisher: release.yml on Blandskron/agnara

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agnara_telemetry-0.1.0a8-py3-none-any.whl.

File metadata

File hashes

Hashes for agnara_telemetry-0.1.0a8-py3-none-any.whl
Algorithm Hash digest
SHA256 512f4fc8f0eac596f1d659a71d5a6231896dd49b0ca6d98630d21fffa383dceb
MD5 8ef0cd290a9882da9d2ba01368d6ab2b
BLAKE2b-256 c76dbce6a325055adfdda0a8bdd7f201171ad096f4e661a4ce7ec58348620926

See more details on using hashes here.

Provenance

The following attestation bundles were made for agnara_telemetry-0.1.0a8-py3-none-any.whl:

Publisher: release.yml on Blandskron/agnara

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0a8 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page