Skip to content

docs(telemetry): pin the span contract the TypeScript SDK already implements - #27

Merged
apucacao merged 4 commits into
mainfrom
ag/py-telemetry-contract
Aug 14, 2026
Merged

apucacao merged 4 commits into
mainfrom
ag/py-telemetry-contract

Conversation

@apucacao

@apucacao apucacao commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Writes down the span contract the TypeScript SDK already implements, so the six Python handler packages can be checked against one document instead of against six readings of another SDK.

No code. This is the specification the rest of the stack is built from.

  • Derived from js-ai-sdk at 5178db1. Where this file and that source disagree, the source wins and this file is the bug.
  • Every gen_ai.* and launchdarkly.* literal in the TypeScript source appears here, verified by diffing the two sets.
  • Two independent reviews, one against the TypeScript source and one against the Python source, found nine defects in an earlier draft. All nine are fixed here.

Three of those defects would have produced wrong spans across several handlers:

  1. gen_ai.response.model is the requested model on four of six handlers, not the answering model. Only openai-messages reads it off the response.
  2. The two OpenAI handlers use the Responses API, which has no finish_reason field. They derive the value and must not use the mapping table. An earlier draft told porters to build one table for all six.
  3. contextvars does cross asyncio.create_task. The claim that it does not was wrong, and a porter reasoning from it would draw bad conclusions elsewhere. Threads are the boundary that loses context.

Section 12 records the work an earlier draft left implicit, including the 348 existing test functions and how many assert on something this stack changes.

Where this sits

Bottom of the stack. Everything above it is checked against this file.


Note

Overview
Adds TELEMETRY-CONTRACT.md, a specification for making the six Python handler packages emit the same OpenTelemetry spans and attributes as the TypeScript SDK at js-ai-sdk commit 5178db1. No runtime code changes—this is the authority document for upcoming parity work.

The contract spells out the full span tree (invoke_agentchat {model} / execute_tool {tool_name}), verbatim gen_ai.* and launchdarkly.* keys, and handler-specific rules that correct common misconceptions: four of six handlers put the requested model in gen_ai.response.model (only openai-messages reads the answering model); OpenAI handlers derive finish reasons from Responses API output instead of a shared mapping table; Anthropic vs OpenAI/LangChain cache token folding must happen at call sites, not in shared writers.

It also documents streaming teardown (GeneratorExit, launchdarkly.stream.abandoned, generator aclose for claude-agents, run cancel for openai-agents), content capture carriers (canonical + OpenLLMetry + legacy events), LangChain gen_ai.system = langchain parity, retirement of set_openllmetry_*, and section 12’s implicit work (new SpanUsage, parse_usage shape, ~121 tests to rewrite).

Reviewed by Cursor Bugbot for commit 79f0697. Bugbot is set up for automated code reviews on this repo. Configure here.

…lements

The TypeScript SDK reshaped its OpenTelemetry output in July: a three-level
span tree, a cache-aware token vocabulary, one finish-reason enum, and
conversation content behind an opt-in flag. Python never got any of it, and
still emits the shape TypeScript retired.

Six handler packages have to change to close that. Six agents each deriving
the vocabulary from the TypeScript source would produce six slightly
different vocabularies, which is how the TypeScript span code drifted apart
the first time. So the vocabulary is written down once, here, and the handler
work is checked against this file rather than against a reading of the other
SDK.

Derived from js-ai-sdk at 5178db1. Where this file and the TypeScript source
disagree, the source wins and this file is a bug.

Two mechanical checks stand behind it. Every `gen_ai.*` and `launchdarkly.*`
literal in the TypeScript source appears here, verified by diffing the two
sets; the only absentees are the two keys inside `setOpenLLMetryCompletion`,
which has no call sites left. And two independent reviews, one against the
TypeScript source and one against the Python source, found nine defects in
an earlier draft. All nine are fixed here, including three that would have
produced wrong spans across several handlers:

  - `gen_ai.response.model` is the requested model on four of six handlers,
    not the answering model. Only openai-messages reads it off the response.
  - The two OpenAI handlers use the Responses API, which has no
    finish_reason field. They derive the value and must not use the mapping
    table. An earlier draft told porters to build one table for all six.
  - contextvars does cross asyncio.create_task. The claim that it does not
    was wrong, and a porter reasoning from it would draw bad conclusions
    elsewhere. Threads are the boundary that loses context.

Section 12 records the work the first draft left implicit, including the
348 existing test functions and how many assert on something this changes.
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 49c8fe8. Configure here.

…y ran

The request-model row claimed the requested name for all six handlers.
claude-agents writes the model the inference actually used on its chat span, the
same value it gives gen_ai.response.model, which the TypeScript source does
deliberately: the CLI reports the model it really ran and there is no separate
per-turn requested name to report.

Stated as it was, the document made a correct handler look like a defect. A
review of #31 raised it against the handler rather than against this file, which
is the failure mode a specification has when it overgeneralises.

Its root span still writes the requested name, so only the chat span differs.
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

…ts in

The contract described what the writer does with a value, that a string passes
through unchanged and anything else becomes JSON, and said nothing about what a
call site should hand it. A comment in the openai-agents handler read that line as
a reason to pass a provider's raw JSON string straight through, which made an
OpenAI span describe a tool call differently from an Anthropic one.

State the call-site rule: arguments hold the object the provider means, not the
encoding it chose. Name the two providers that send a string, and say what happens
to a string that does not parse.

Point at the cache folding in section 4 as the precedent, since it is the same
division: the shared writer cannot know whether a string is an encoding or a value,
so the provider's shape is the call site's business.
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 08b31d8. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 08b31d8. Configure here.

The rule said the root is the only span carrying launchdarkly.*, and that child
spans carry neither that nor the feature_flag event. The second half is right. The
first half was already untrue: the streaming paths have marked
launchdarkly.stream.abandoned on open chat and tool spans since the earlier rounds,
and the cancellation teardown now marks launchdarkly.run.cancelled the same way.

Both markers say why a span stopped, so they belong on whichever span was still open,
root or child. A chat span that ends with no status and no marker cannot be told
apart from a bug.

What the rule protects is unchanged, and is now stated: neither marker identifies a
config, so a config-scoped query still finds exactly one span per run. The
config-association attributes stay on the root alone, and the test that asserts it
runs a successful run, where no marker appears at all.

Found by Bugbot on the usage layer.
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 79f0697. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 79f0697. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 79f0697. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 79f0697. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 79f0697. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 79f0697. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 79f0697. Configure here.

@apucacao
apucacao marked this pull request as ready for review August 14, 2026 21:18
@apucacao
apucacao merged commit 6a82ef5 into main Aug 14, 2026
7 checks passed
@apucacao
apucacao deleted the ag/py-telemetry-contract branch August 14, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants