SIGN IN SIGN UP

feat(datagen): add a sidecar that replays a recorded trace corpus (#15614)

* feat(datagen): recording toolkit and hand-recorded OpenInference corpora

Adds scripts/datagen (mock OpenAI-compatible provider plus two PEP 723
scenario scripts using real OpenInference instrumenters) and two recorded
corpora (openai_chat_sessions, langchain_agent_rag) as OTLP protobuf-JSON
lines with manifests under src/phoenix/datagen/corpora.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* feat(datagen): add OTLP corpus replayer and phoenix datagen CLI

Adds src/phoenix/datagen (corpus loader for local paths and URLs,
session-aware replayer with ID/timestamp rewriting, contamination-mixture
anomaly injection with ground-truth manifests, OTLP/HTTP export) and wires
a lazily imported 'phoenix datagen' subcommand with env-var/flag config.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* fix(datagen): group corpus spans across requests

Spans are grouped by recorded trace_id across all corpus lines, so corpora
batched one-request-per-span replay as intact traces; manifest validation
now counts distinct trace ids and total spans.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* fix(datagen): preserve replay fidelity and package corpora

Bundles the recorded corpora in the wheel, restores parent-child end
containment after latency redraws, rebases span-event timestamps, honors
PHOENIX_CLIENT_HEADERS, preserves dangling recorded parents, and pins the
numeric, export, and corpus-fidelity contracts in tests.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* fix(datagen): address acceptance findings

Adds --project/PHOENIX_PROJECT_NAME with a datagen-<corpus> default emitted
as the openinference.project.name resource attribute, derives emitted IDs
from the seed plus a per-run nonce so same-seed reruns stay comparable
without colliding, and retries OTLP export with capped exponential backoff
instead of exiting on transport errors.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* feat: add optional datagen deployment recipes

Adds an opt-in docker-compose datagen profile, a disabled-by-default Helm
datagen deployment, a kustomize datagen overlay, a commented-out Render
worker, and a self-hosting docs page covering local, Compose, Helm,
Kustomize, Render, Railway, and Cloud Run flows.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* refactor(datagen): rename corpora to datagen assets and scenarios

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* feat(datagen): fragment banks, generation lanes, session composer, distribution

Adds the v2 fragment-bank schema and loader, a resumable USD-capped
generation control plane with OpenAI Batch support, deterministic fake
tools, self-play and scripted recording lanes, six archetype recorders,
MinHash dedup and quality gating with atomic bank packaging, an
archetype-safe replay session composer with lognormal session/gap knobs,
checksum-verified bank fetch/pull with lazy resolution, a datagen-assets
release workflow (--latest=false), a wheel starter-assets size gate, and
checksum-pinned container asset baking.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* fix(datagen): make generation-tooling tests importable without PYTHONPATH

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi

* fix(datagen): resolve recorder environments and verify offline recording

Every recorder script's PEP 723 block now resolves under the repository's
three-day `exclude-newer` window, and each recorder was run end-to-end,
keyless, against the in-repo mock provider to confirm it emits its
archetype's span kinds with session.id attached.

- tool_agent, graph_multi_agent: langchain-core 1.5.6, langchain-openai
  1.5.1, openinference-instrumentation-langchain 0.1.70. The previous
  langchain-openai/openai pins were mutually unsatisfiable, and the
  2024-era LangChain instrumenter predates get_attributes_from_context.
- langchain_agent_rag: llama-index-core 0.14.23, the newest release
  outside the freshness window.
- guardrailed_app: guardrails-ai 0.5.0. Every published OpenInference
  Guardrails instrumenter supports only `>=0.4.5,<0.5.1`, so the 0.6.7
  pin left the instrumentor disabled and no GUARDRAIL span was recorded.
- Each recorder that imports `openinference.instrumentation` directly now
  declares it, and openai is pinned at one version across the directory.

The mock provider now synthesizes tool-call arguments from the caller's
own declared tool schema instead of a fixed delivery-estimate shape, so
the tool-agent registry validates them, and it serves a server-sent-event
stream when a request asks for one, which the plain-chat recorder needs.

* chore(datagen): re-record starter assets under current instrumenter pins

Claude-Session: https://claude.ai/code/session_01MjZmsruXdy3Wg2KQYQUCVo

* feat(datagen): move assets to GCS

* feat(datagen): application profiles, profile-scoped matrix, structured model backends

Versioned application-profile contract with canonical run snapshots;
matrix v2 draws every conversational field inside one profile with
reproducible per-field streams, 10% targeted-seed cells, and Beta(2,8)
seed intensities; structured OpenAI and codex-exec backends with
provider-aware attempt accounting (priced vs subscription); scripted and
self-play lanes consume profile draws.

Claude-Session: https://claude.ai/code/session_01Jrru1FDRB5uKGFGq6Rwxst

* feat(datagen): add customer support profiles

* feat(datagen): add coding agent application profiles

* feat(datagen): add data analyst application profiles

* feat(datagen): add deep research profiles

* feat(datagen): deterministic seed mechanics and materialized environments

Profile seeds gain authored subtle/moderate/strong effect variants with a
closed vocabulary (corpus edits, tool-result overlays, simulator traits);
a pure materializer maps (cell_id, seed_id, intensity) to one variant and
projects a seed-metadata-free environment into both lanes; fake tools
overlay successful results after native handling; targeted cells add only
a natural route; transcripts reject internal seed language.

Claude-Session: https://claude.ai/code/session_01Jrru1FDRB5uKGFGq6Rwxst

* test(datagen): add seed mechanics to generation fixture

* feat(datagen): add deep research seed mechanics

* feat(datagen): add data analyst seed mechanics

* feat(datagen): add coding agent seed mechanics

* feat(datagen): add customer support seed mechanics

* feat(datagen): judged outcomes with engagement-based routing

Immutable judging-input and judgment sidecars keyed by cell and fragment;
recorded seed-engagement signals (tool overlay applications, perturbed-
document serving, trait seeds by construction) determine ambient proximity;
deterministic routing judges every trap-proximate fragment plus a 5%
stratified baseline; versioned three-label rubric (survived/degraded/failed)
through the frontier ModelBackend binding; outcomes project into schema-v2
quality metadata and never gate acceptance.

Claude-Session: https://claude.ai/code/session_01Jrru1FDRB5uKGFGq6Rwxst

* fix(datagen): align profile and composition boundaries

* fix(datagen): make asset publication owner-run

* fix(datagen): reject invalid conversation structure

* feat(datagen): replay rate schedule, backfill, and error injection

Add --rate-schedule (flat|business-hours), --timezone, --backfill, and
--error-rate to phoenix datagen. The replayer paces a virtual timeline that
follows weekday/weekend hourly tiers, can start in the past and catch up at
exporter throughput, and can mark recorded LLM/TOOL spans as failed with an
exception event and ancestor status propagation. Anomaly manifest rows gain an
additive kind and timing fields. Flat defaults leave emitted requests unchanged.

Claude-Session: https://claude.ai/code/session_01EvQfhu4vtHASNDPReHB5t9

* feat(datagen): supplemental fault runs and bank merge

Runs can allocate one provider or tool fault per matrix cell
(--fault-fraction, --fault-modes) with base-bank lineage
(--base-scenario-name, --base-archive-sha256). The mock provider injects a
selected fault once so the real SDK and instrumenters record retry and
exception topology; tool exceptions cross the agent loop as error tool
messages. Every fault fragment is judged through a dedicated route.
bank.py gains package and merge commands that union a validated supplement
into its base archive, rebuilding aggregates and recording per-input
instrumenter provenance. README documents the supplemental procedure and the
prepare-only publication handoff.

Claude-Session: https://claude.ai/code/session_01EvQfhu4vtHASNDPReHB5t9

* refactor(datagen): trim runtime verification to its floor

Verify scenario bytes once, where they can actually change: size and
sha256 at download, member safety and manifest metadata at extract.
Cache freshness is now a checksum-file presence and size check instead
of a full re-hash on every start, and the loader validates only what
replay needs (schema v2, fragment parse, unknown trace ids).

- publish the cache directory by rename-aside instead of rmtree, so a
  concurrent reader never sees a half-deleted scenario
- drop the unsound PID-based lock reclaim; a stale lock now fails with
  the file path to remove
- drop the unverified HTTP-directory scenario source and schema-v1
  support; test fixtures upgraded to schema v2
- collapse composer defaults to the dataclass fields plus CLI flags,
  removing the manifest channel that duplicated them
- rename asset vocabulary to scenario (ScenarioFetchError,
  load_scenario_index, scenario_base_url,
  PHOENIX_DATAGEN_SCENARIO_BASE_URL); published index format unchanged

Claude-Session: https://claude.ai/code/session_01Jb1jAxuoy8BeYgAuchjH3L

* refactor(datagen): remove the cost plane and the batch lane

The generation lane no longer models money or batch submission. Deleted the
price catalog, budget pools, reserve/reconcile accounting, the cost-invariant
latch, and the OpenAI Batch adapter, along with the costs and jobs journals
and the CLI flags that fed them. Attempts keep their lane cap, which never
depended on pricing.

Callers in the judging and self-play paths lose their prices arguments; the
scripted lane keeps its direct half. Tests pinning budgets, reservations and
batch submission are removed; the codex_exec sandbox argv is now pinned as an
exact sequence.

* refactor(datagen): one shared serialization module for the sidecar scripts

The datagen scripts carried seven JSON canonicalizers across three
incompatible serialization policies, three journal readers and two deep-copy
helpers. serialization.py now holds one of each: canonical_bytes,
plain_json, json_copy, write_immutable_bytes, write_immutable_json,
append_json and read_jsonl.

The single policy is ensure_ascii=False, which writes real UTF-8 into
journals and archives instead of escapes. Seed digests move for non-ASCII
application state; run directories are disposable, and no test fixture
carries non-ASCII seed state.

The journal helpers take the caller's exception class as an error argument,
so each module keeps raising its own error type. The module imports only the
standard library, since the recorders in that directory run without the
phoenix package on the path.

* refactor(datagen): share the transcript hygiene names across the guards

The reserved-phrase tuple was duplicated between the scripted and self-play
recorders, and the bare-role-name check was written inline at three sites
plus a fourth set in the quality gate. transcript.py now holds one copy of
each, along with the strip-and-casefold predicate the guards were repeating.

The four guards stay where they are and keep their own exception types and
messages, since each defends a different boundary. The shared name set is
the four-name superset, so a message whose whole visible content is 'system'
or 'tool' is now rejected at the scripted decoder and both self-play
boundaries; the two new tests cover that widening.

* refactor(datagen): rename bank to scenario and enforce judged outcomes once

The archive module is scenario.py and speaks scenario vocabulary throughout.
Its reader extracts the archive and calls the runtime loader for manifest,
fragment and OTLP parsing, keeping only the checks publish time actually
owns: per-file size and digest, fragment/trace/span counts, span_kinds
equality and fragment trace ownership. validate_archive no longer parses
every archive twice through two independent implementations.

The judged-outcome coupling now has one enforcement site,
GenerationRun.record_judgment, which is the sole writer of judgments.jsonl.
The archive projection, the two record parsers and the route builder drop
their copies of the check. New manifests no longer carry composer_defaults.

The atomic archive write with read-back-before-replace is unchanged; it is
what makes a published scenario immutable.

* refactor(datagen): scenario vocabulary and one owner per publish check

prepare_publication no longer writes the staged index and reads it back to
compare it against the record it was just serialized from. validate_archive
drops its schema-v1 branch and the flag narrows to 2, matching a runtime that
no longer loads v1. The duplicate instrumenter-version parser is gone.

The workflow stops re-implementing the archive-name regex and the schema
version guard that publish validate already applies, and drops the v1 option
its dropdown could no longer produce a passing run for.

The README now states the actual reason publication is safe: the tool holds
no credentials and makes no network write, so nothing reaches the bucket
until someone runs the two printed commands, and --no-clobber on the
digest-namespaced archive is what keeps a published scenario immutable.
Documented commands and env var names are corrected to what exists.

* style(datagen): format test_codex_exec.py

Applies the repo formatter to a file this change already touches.

* refactor(datagen): default the destination project to phoenix-datagen

The default project name no longer derives from the scenario manifest;
zero-config replay always lands in the phoenix-datagen project, and
--project / PHOENIX_PROJECT_NAME still override it.

Claude-Session: https://claude.ai/code/session_01Jb1jAxuoy8BeYgAuchjH3L

* refactor(datagen): zero-config replay with bundled or sole published scenario

phoenix datagen now runs with no configuration: it replays the scenario
bundled into the installation (Docker images bake one in at build time)
or, failing that, the sole scenario in the published index, into the
phoenix-datagen project. When the index holds several scenarios the
command asks for --scenario instead of silently picking one.

- delete every PHOENIX_DATAGEN_* environment variable; rate, epsilon,
  seed, burstiness, scenario, and the anomaly manifest are flags only
- drop the base-URL override and the "default" scenario name
  convention along with its alphabetical fallback
- phoenix datagen pull with no argument primes the sole published
  scenario
- add the bundled-assets placeholder index the Docker bake stage copies
- deploy configs (compose, helm, kustomize, render, docs) pass flags
  instead of the removed environment variables

Claude-Session: https://claude.ai/code/session_01Jb1jAxuoy8BeYgAuchjH3L

* refactor(datagen): drop the seven session-shape tuning flags

The session-shape distributions (fragments per session, fragment gaps,
archetype mix) keep their built-in defaults; the CLI no longer exposes
per-distribution tuning. Programmatic callers and tests can still pin
distributions through Replayer's composer_config parameter.

Claude-Session: https://claude.ai/code/session_01Jb1jAxuoy8BeYgAuchjH3L

* refactor(datagen): remove backfill, rate schedules, and the anomaly manifest

The replay loop is one shape again: emit, export, sleep. The virtual-
cursor loop, the business-hours rate profile with its timezone handling,
the backfill duration parsing, and the anomaly-manifest JSONL writer are
gone. Anomaly and error ground truth remains available in memory on each
EmittedTrace for tests and programmatic callers.

Claude-Session: https://claude.ai/code/session_01Jb1jAxuoy8BeYgAuchjH3L

* Relax datagen replay validation and cache checks

* Trim datagen generation checks and tests

* Flatten datagen's published banks into a single corpus

* Trim the datagen replayer to its live paths

* feat(datagen): simplify corpus archive pipeline

* refactor(datagen): simplify trace replay

* feat(datagen): replace generation runs with recorder fixtures

* refactor(datagen): record archetypes from fixed fixtures

* refactor(datagen): align deployment with corpus replay

* fix(datagen): satisfy repository type checks

* feat(datagen): add recorder condition materialization

* feat(datagen): add conditioned live recording lane

* fix(datagen): skip llama-index recorder test when instrumenter is absent

Claude-Session: https://claude.ai/code/session_01BWBfzCgBofoRLpGua3kihU

* fix(datagen): skip guardrail recorder test when framework is absent

Claude-Session: https://claude.ai/code/session_01JXjpZ2FuYsz35M2ndhLqCC

* test(datagen): trim suite to one happy path per surface

Collapse the datagen suite to a single executable example per public
surface: loader, fetcher, composer, replayer, exporter, CLI run and
pull, corpus pipeline, conditions materialization, recording
reset/append, and one per recorder entry point.

Deleted test_mock_openai_provider.py and test_fake_tools.py — both
helpers are exercised through the recorder and conditions tests, and
the provider test pinned an internal response counter. Removed the
signature-introspection tests on the graph and guardrail recorders,
the offline pointer fallback, the live-client error path, and the
invalid-tool-path and multi-intensity condition parametrizations.

Merged coverage that was worth keeping into the survivors: the
tool-agent test now records under the shipped repository condition and
asserts both framework span kinds and the authored tool result; the
recording test proves reset truncates before append preserves; the CLI
test drives register -> parse -> run instead of pinning the private
_resolve_config helper.

Claude-Session: https://claude.ai/code/session_01EJA3HS5EzqKqcHuLzHdRSN

* feat(datagen): add iterative coding tool traces

* feat(datagen): enrich authored corpus inputs

* feat(datagen): report corpus depth statistics

* feat(datagen): simulate live chat follow-up users

* fix(datagen): suppress simulated user spans

* fix(datagen): resolve luna recorder model

* fix(datagen): configure luna tool calls

* feat(datagen): vary simulated user dispositions

* feat(datagen): add manual agent phase spans

* Weight replay session sampling by fragment count

The composer picked its archetype uniformly (then domain uniformly
within it), so a 3-fragment archetype fired as often as a 110-fragment
one and coding sessions replayed at ~14% despite being 25% of the
corpus. Sample the (archetype, domain) cell proportionally to its
fragment count so replay traffic tracks the corpus's authored
composition.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Add a fat-tail slow-span outlier to replay jitter

Recorded latencies top out around 12s, so the ±10% duration jitter never
produces the slow outliers real latency distributions carry. With small
probability per trace, stretch its longest leaf span by a lognormal
factor (median 4x); parent end-time extension propagates the stall
upward, yielding occasional 20s+ spans for latency filters to find.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* docs: replace internal vocabulary with plain terms

The recorder playbook and phoenix-cli skill docs had accumulated dense
insider vocabulary. Swap "grain" for "level" and "legislates" for
"enforces" throughout the filter-DSL skill docs, and in the recorder
README define fragment/archetype/domain at first use, name the fragment
row fields, drop "recording contract"/"generation stream"/"operating
agent" phrasing, and render the intensity thresholds as a table.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Fix CI: formatting, redundant cast, and datagen script type checking

Format scripts/update_kustomize.py; drop a cast mypy flags as redundant
in the composer; skip mypy import-following for the PEP 723 recorder
scripts, whose dependencies are not installed in the development
environment; and guard two recorder tests with importorskip so
collection succeeds where langchain is absent.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Fix datagen container start commands for the distroless image

The Phoenix image's ENTRYPOINT is the Python interpreter and its
console scripts carry interpreter paths that are not valid in the final
image, so overriding the command with "phoenix datagen" cannot start.
Use module arguments instead: compose overrides CMD, Kubernetes
manifests use args to keep the ENTRYPOINT, and Render/Railway/Cloud Run
instructions invoke python3 -m phoenix.server.main. Also align the
kustomize datagen image tag with the base (20.4.0); release automation
now bumps both together.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Prefix replayed session ids with their domain

Session ids read customer_support-<hex> instead of datagen-<hex>, so
the sessions view identifies what kind of conversation each row holds
at a glance.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Give each archetype its own session-length profile

One global median of two fragments per session made every replayed
session read like a one-or-two-question visit: 38 of 150 sampled
sessions held a single trace. Real sessions differ by application
shape, so the composer now draws fragments per session from a
per-archetype lognormal profile: agent work sessions string together a
median of six episodes (p90 ~15 traces), chat and retrieval
conversations run ~10 turns at the median, extraction stays batch-like.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Steer conversation length organically and diversify coding seeds

Live plain-chat conversations now run until the simulated user closes
them: a per-fixture target turn count controls when the simulator is
told to wrap up once its concern is addressed, so lengths cluster near
the target while every conversation ends at a natural closing message.
Conversational archetypes accordingly compose sessions from one
recorded conversation instead of concatenating unrelated ones.

Coding seed diversity: eight new tool-agent coding tasks against an
expanded fake repository (metrics, config, broker, changelog files with
matching issues and tests), plus authored prompt paraphrases picked per
live run so repeated recordings do not open with identical text. The
corpus packer now reports distinct opening inputs per domain so thin
seed variety is visible at packaging time. Adds the terra live-model
alias.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Let chat sessions chain a few whole conversations

Multi-topic sessions chaining recorded conversations are acceptable
realism; keep the chain short now that each conversation records at
full length.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Apply ruff formatting to datagen recorder and test

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Vary chat conversation openings per live run

Chat fixtures carry authored opening phrasings; live recording picks
one per run so the session list does not repeat identical first
messages. Conditioned runs keep their materialized opening.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Restore scripts/ in the unit-test checkout and pin the chat test opening

The datagen unit tests import the recorder modules under scripts/, but
the unit-test job's sparse checkout dropped that directory when the DSL
compile checks moved to their own job, so collection failed with
ModuleNotFoundError. Re-add it. Also pin the live chat test to the
authored opening so the new per-run phrasing choice cannot make its
assertions flaky.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Scope the scripted coding-agent test to fixtures with scripted episodes

The test passed every coding fixture to the scripted recorder, which
raises for the new live-only tasks. Select only the fixtures that carry
a deterministic scripted episode.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Format the coding-agent test selection

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Move datagen tooling tests out of the unit suite

The recorder and corpus-pipeline tests import scripts.datagen.*, which
forced the Unit Tests CI job to check out all of scripts/ and forced a
mypy override for the un-followable script modules. That put dev tooling
on the wrong side of the product test boundary.

- Move the ten scripts-importing test files to scripts/datagen/tests/
  with their own conftest and fragment_bank fixture copy
- Restore the Unit Tests sparse-checkout to main's list
- Add a path-filtered Datagen Tooling Tests job that runs
  uv run pytest scripts/datagen/tests
- Drop the scripts.datagen.* mypy override

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Narrow this PR to the datagen replayer runtime

Split per review feedback: the generation tooling (scripts/datagen)
and the deployment integration + docs (compose, kustomize, helm,
render, self-hosting page) move to follow-up PRs. This PR keeps the
feature that stands alone: the phoenix.datagen runtime (fetcher,
loader, composer, replayer, exporter), the datagen server subcommand,
and their unit tests. Reverts the langchain test pins and mypy
carve-outs the generation tests had pushed into the shared dev
environment.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* fix(ci): cap pydantic-ai-slim below 2.34 in unit test requirements

The unit-test tox env installs with -U and resolves pydantic-ai-slim
fresh, so it picked up 2.34.0, whose source no longer matches the
vendored data-stream protocol types in src/phoenix/db/types.
test_data_stream_protocol_compatibility now fails on every PR
(verified: 2.33.0 passes, 2.34.0 fails the request-type and schema
parity tests). Cap the unit env at 2.33 parity until the vendored
types are re-synced.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Keep jittered token totals consistent when one component is missing

A span carrying only prompt and total counts (the embedding-span shape)
fell through to the independent total jitter, so the emitted total
could drop below the jittered prompt count. Recompute the total from
the jittered components whenever either one is present, leaving the
independent jitter for spans that carry only a total.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Drop the pydantic-ai-slim unit-test cap after the vendored re-sync

Main upgraded pydantic-ai-slim to 2.34 and re-synced the vendored
data-stream types (#15714), so the stopgap cap this branch carried is
no longer needed.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Inline PHOENIX_CLIENT_HEADERS parsing in the datagen command

Main removed phoenix.utilities.re with the legacy client cleanup
(#15709), which deleted parse_env_headers from the server package.
Parse the W3C Baggage-style header string locally instead of reaching
into the client package.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

* Mark datagen as internal tooling and move it under experimental

Review feedback: hide the datagen subcommand from the top-level help
(help=SUPPRESS, matching db) while documenting it for direct --help
invocation, add the no-compatibility disclaimer to the package
docstring, and move the package to phoenix.experimental.datagen so the
wheel's import surface carries the same signal.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
D
Dustin Ngo committed
b86acacca3f70b58e1eac2e685ecd0cd82e424af
Parent: be603a8
Committed by GitHub <noreply@github.com> on 8/28/2026, 7:11:59 PM