feat: deliver Logfire tracing as an explicit Instrumentation capability - #838
Open
mpfaffenberger wants to merge 2 commits into
Open
feat: deliver Logfire tracing as an explicit Instrumentation capability#838mpfaffenberger wants to merge 2 commits into
mpfaffenberger wants to merge 2 commits into
Conversation
Promote OpenTelemetry/Logfire tracing from the process-global Agent.instrument_all default (installed by logfire.instrument_pydantic_ai) to a first-class pydantic-ai Instrumentation capability on the agents code_puppy constructs itself. - New code_puppy/agents/_instrumentation.py: build_instrumentation() snapshots the effective global settings at build time and returns a stock Instrumentation capability carrying the exact settings object logfire installed; empty list when uninstrumented. - Both construction sites (main builder + sub-agent invoker) declare it explicitly; pydantic-ai's run layer skips its own injection when an explicit Instrumentation capability is present, so spans are identical. - The global default is deliberately retained: plugin-built agents (wiggum judge, btw side-query) get their spans from it, and ctx.tracer resolution stays consistent. Explicit-when-ours, fallback-for-guests. - One documented divergence, pinned by test: the capability is a build-time snapshot, so clearing instrument_all after a build keeps an already-built agent traced until rebuild. Nothing flips the default after startup. 12 contract tests including span-parity (old global-only path vs new explicit path produce identical span name/op sequences) and a no-double-instrumentation guarantee. Full suite: 7609 passed.
- Span-parity test now compares fully normalized spans: status codes, parent topology (by span name), complete attribute key sets, and stable attribute values — equality also rules out duplicated request/tool spans wholesale (findings 1+2). - New ctx.tracer contract test: a probe capability observes the run context tracer and pins identity with settings.tracer, locking the documented rationale for retaining the global default (finding 3). - Reworded fresh-capability test rationale and added a direct for_run isolation test pinning upstream's replace-copy contract (finding 5). - Module docstring: ctx.tracer resolution claim scoped to classic run/iter, with the realtime-session nuance noted (finding 3 nuance). 14 contract tests; full suite 7611 passed.
Owner
Author
|
Review trail (code-puppy clone, session pr-838-review):
Final state: 14 contract tests, full suite 7611 passed, 0 failed. |
This was referenced Aug 21, 2026
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.
What
Tenth in the capability-conversion series (#828–#836). Promotes OpenTelemetry/Logfire tracing from the process-global mutable default (
logfire.instrument_pydantic_ai()→Agent.instrument_all(settings)) to a first-class, explicitly-declaredInstrumentationcapability on the agents code_puppy constructs.pydantic-ai 2.31.0 itself treats the capability as the canonical form —
instrument_all's own docstring reads "for all agents that don't explicitly add anInstrumentationcapability", and the run layer skips its internal injection whenever an explicit one is present ("explicit-capability-wins").How
code_puppy/agents/_instrumentation.py—build_instrumentation()resolves the effective global default at build time (mirroringAgent._resolve_instrumentation_settings) and returns a stockInstrumentationcapability carrying the exact settings object logfire installed (True→ default-constructed settings; unset/False→ empty list)._builder.py) and sub-agent invoker (subagent_invocation.py) splat*build_instrumentation()into theircapabilities=[...]blocks. List position is inert: the capability declaresposition='outermost'ordering — the same slot the run layer's internal prepend targets.observability.configure_logfire()unchanged in behaviour; docstring now documents the two delivery paths.Why the global default is deliberately retained
Agent.instrument_all. Removing it would silently kill their telemetry — a parity break.ctx.tracerconsistency. pydantic-ai resolves the run context's tracer from global/instance settings independently of explicit capabilities; keeping both in sync means capabilities/toolsets observingctx.tracernever see aNoOpTracerwhile spans exist.configure_logfireruns carries no capability and degrades to exactly the old behaviour via the run-layer fallback.Explicit-when-ours, fallback-for-guests.
Divergence (documented + pinned)
The capability is a build-time snapshot; the global default is read per run. If
Agent.instrument_all(False)were called after a build, that agent would keep tracing until rebuilt (previously it would go quiet on the next run). Nothing in code_puppy or its plugins flips the default after startup. Pinned bytest_snapshot_survives_later_global_disable.Tests
12 contract tests in
tests/agents/test_instrumentation_capability.py:True-normalisation/fresh-per-call)Agent._instrument_default(a pydantic-ai rename fails loudly here; runtime degrades soft to the fallback)position='outermost'ordering pingen_ai.operation.namesequences between the old global-only path and the new explicit path (in-memory OTel exporter)_invoke_agent_impl) deliver the verbatim settings objectFull suite: 7609 passed, 28 skipped, 1 xpassed, 0 failed — including the previously-eternal
test_render_version_check_current, healed on main by #837.