Skip to content

feat: Add span origin provenance#2226

Open
Stephen Belanger (Qard) wants to merge 1 commit into
mainfrom
agent/span-origin-provenance
Open

feat: Add span origin provenance#2226
Stephen Belanger (Qard) wants to merge 1 commit into
mainfrom
agent/span-origin-provenance

Conversation

@Qard

Copy link
Copy Markdown
Collaborator

Summary

Adds span origin provenance to the TypeScript SDK direct logging and OpenTelemetry paths, including .env.braintrust environment detection.

Validation

  • ./node_modules/.bin/vitest run in integrations/otel-js/otel-v1
  • ./node_modules/.bin/vitest run in integrations/otel-js/otel-v2
  • aggregate git diff --check

@Qard
Stephen Belanger (Qard) force-pushed the agent/span-origin-provenance branch 2 times, most recently from d25d857 to efa7db3 Compare July 15, 2026 16:43
@Qard
Stephen Belanger (Qard) marked this pull request as ready for review July 15, 2026 17:11

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efa7db3e0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread integrations/otel-js/src/otel.ts Outdated
Comment thread .changeset/span-origin-provenance.md
@lforst Luca Forstner (lforst) changed the title Add span origin provenance feat: Add span origin provenance Jul 16, 2026
Comment thread js/src/span-origin.ts Outdated
Comment thread js/src/span-origin.ts
@Qard
Stephen Belanger (Qard) force-pushed the agent/span-origin-provenance branch 2 times, most recently from fb49185 to f3358a5 Compare July 16, 2026 11:44
@Qard
Stephen Belanger (Qard) force-pushed the agent/span-origin-provenance branch from f3358a5 to 8ccc89e Compare July 16, 2026 12:55
Abhijeet Prasad (AbhiPrasad) pushed a commit to braintrustdata/braintrust-sdk-python that referenced this pull request Jul 16, 2026
## Summary

Every span an integration creates now carries a stable identifier in
`context.span_origin.instrumentation.name` — `openai-auto`,
`anthropic-auto`, `temporal-auto`, and so on — matching the naming
convention in the Braintrust instrumentation spec's `span_origin`
example. Previously all integration spans were tagged with the shared
`braintrust-python-logger` default, which made per-integration filtering
on `span_origin` impossible.

Builds on #570 (which added the `span_origin` scaffolding).

## API surface

`start_span` (and every provider-level `start_span` method) gains an
`internal: dict | None = None` kwarg reserved for Braintrust SDK
internals. Integrations pass `internal={"instrumentation":
"<provider>-auto"}` to stamp the span origin; the `internal` name
signals to external callers that they shouldn't touch it and its keys
can change without notice.

```python
from braintrust import start_span

with start_span(name="my-op", internal={"instrumentation": "my-integration-auto"}) as span:
    ...
```

- Threaded through every span-creation surface: top-level `start_span`,
`Span.start_span`, `SpanImpl.start_span`, `Experiment.start_span`,
`Dataset.start_span`, `Logger.start_span`, and `_NoopSpan.start_span`.
- When set, `SpanImpl` reads `internal["instrumentation"]` and stamps it
into `context.span_origin.instrumentation.name` via
`merge_span_origin_context`.
- When unset, spans fall back to the channel default —
`braintrust-python-logger` for direct logging, `braintrust-python-otel`
for the OTel processor.
- **Does NOT propagate through the parent/child edge.** Each
`start_span` call independently decides its own value, so a user's
`@traced` scorer nested inside a wrapped provider call keeps the default
identifier — the integration's name only lands on spans the integration
itself opens. This matches the spec's "the module that _directly
created_ the span" definition and mirrors how Sentry's `origin` and
OTel's `InstrumentationScope` behave.

Suggested for external Braintrust integrations authored outside this
repo:

```python
from braintrust.logger import start_span as _bt_start_span

_INSTRUMENTATION = "my-provider-auto"

def start_span(*args, **kwargs):
    internal = dict(kwargs.get("internal") or {})
    internal.setdefault("instrumentation", _INSTRUMENTATION)
    kwargs["internal"] = internal
    return _bt_start_span(*args, **kwargs)
```

Every module-scope `start_span(...)` call then flows through the shadow
with zero further edits.

## Integration migration (23 integrations)

**21 shadow-based** (`adk`, `agentscope`, `agno`, `anthropic`,
`autogen`, `bedrock_runtime`, `claude_agent_sdk`, `cohere`, `crewai`,
`dspy`, `google_genai`, `huggingface_hub`, `instructor`, `langchain`,
`litellm`, `livekit_agents`, `llamaindex`, `mistral`, `openai`,
`openai_agents`, `openrouter`, `pydantic_ai`) — the shadow above at the
top of each `tracing.py`/`callbacks.py`.

**Explicit at call sites** (2 integrations):
- `temporal/plugin.py`: `internal={"instrumentation": "temporal-auto"}`
at each `logger.start_span(...)` site — Logger method calls don't go
through the module-level shadow.
- `openai_agents/tracing.py`: `internal={"instrumentation":
_INSTRUMENTATION}` on the three `current_context.start_span(...)` /
`self._logger.start_span(...)` / `parent.start_span(...)` sites.

## Tests

- `py/src/braintrust/test_otel.py` — new tests covering
`merge_span_origin_context` resolution and an end-to-end `SpanImpl`
assertion that child spans do NOT inherit the parent's instrumentation
name (child falls back to `braintrust-python-logger`).
- Per-integration inline assertion: each of the 23 integrations'
existing VCR tests now asserts
`span["context"]["span_origin"]["instrumentation"]["name"] ==
"<provider>-auto"` in place, next to the real provider-facing coverage.
If a shadow ever regresses, that specific integration's test suite fails
with a clear signal.

## Docs

- `.agents/skills/sdk-integrations/SKILL.md` gains a **Span origin**
section documenting the shadow pattern, the no-inheritance rule, and the
`<provider>-auto` naming convention.
- Same file relaxes the earlier strict "metadata MUST NOT capture
anything outside the spec" guidance to an allowlist-per-provider model —
spec-defined keys must be captured, provider-specific detail fields can
be included deliberately, no dumping raw request/response objects.
- Spec reference now links to the actual URL
(https://github.com/braintrustdata/braintrust-spec/blob/main/docs/instrumentation-guide.md).

## Validation

- `nox -s test_core`: **647 passed, 66 skipped, 12 xfailed**
- `nox -s test_types`: **16 passed** (pyright + mypy clean)
- `nox -s pylint`: successful (also ran ruff format + ruff check --fix
as part of pre-commit)
- **30 provider `(latest)` sessions** — every one whose Python 3.14
wheels are available. Zero failures. `test_livekit_agents` and
`test_crewai` skipped due to pre-existing Python 3.14 wheel gaps in
`onnxruntime` / `pydantic-core`, unrelated to this change.

## Follow-ups

- JS SDK's mirror PR (braintrustdata/braintrust-sdk-javascript#2226) is
still open with only `braintrust-js-logger` / `braintrust-otel-js`
hardcoded and no per-integration wiring. Aligning JS on the same
`<provider>-auto` names is worth doing before this ships so cross-SDK
dashboards work — the spec only gives `openai-auto` as an illustrative
example, so a coordinated commit-to-a-convention is needed on both
sides.
- Naming convention isn't currently prescribed by the spec
(`docs/instrumentation-guide.md` just shows `openai-auto` in a JSON
blob). Worth a spec PR to make it normative once JS and Python agree.

<!-- sfk:created-approved-by -->
Created by abhijeet

<!-- sfk:slack-thread -->
[Slack
thread](https://starfolkai.slack.com/archives/C0AQDETAVT3/p1784153314321549?thread_ts=1784153314.321549&cid=C0AQDETAVT3)

---------

Co-authored-by: Starfolk <noreply@starfolk.ai>
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