MLflow tracing as a backend for provevi
Research date: 2026-10-31. The current MLflow release is 3.17.2, from 2026-10-20 (CHANGELOG, PyPI).
The facts below come from three sources:
- Installed source. MLflow 3.17.2 and
mlflow-tracing3.17.2, installed in two scratch environments. Paths such asmlflow/entities/span.pyare relative tosite-packages. - Experiments. Two small scripts wrote traces into a SQLite file, with no server.
- Web sources. The web sources are the changelog, the docs at mlflow.org and the GitHub repository. Each claim links its source.
Terms used on this page:
- Span. A span is one timed operation, with a name, a start time, an end time, a parent and attributes. OpenTelemetry (OTel) defines it.
- Trace. A trace is a tree of spans that share one trace ID.
- Tracking store. The tracking store is the MLflow class that persists experiments, runs and traces.
SqlAlchemyStorewrites to a SQL database, andFileStorewrites to a folder. - OTLP. OTLP is the OpenTelemetry protocol for sending spans over HTTP or gRPC.
Summary
- Direct SQLite is not deprecated. In 3.17.2,
sqlite:///mlflow.dbis the default tracking URI, and Python writes to it in process with no server. MLflow retired the file store (./mlruns,file://). - The file store raises an error since 3.13.0. It needs
MLFLOW_ALLOW_FILE_STORE=true, and it is in maintenance mode. - Each span is one JSON row. The SQL store keeps each span as a JSON document in
spans.content, with some columns pulled out for search. - MLflow accepts OTLP spans. The server has
POST /v1/tracessince 3.6.0, and it translates GenAI semantic convention attributes to MLflow attributes. - Some clients cannot write to SQLite. The lightweight
mlflow-tracingpackage and the TypeScript plugins for Claude Code and Codex cannot write to SQLite. They need an HTTP server. The Python package drops spans in silence when you give it asqlite:URI.
1. The trace data model
Trace, TraceInfo and TraceData
A Trace holds a TraceInfo and a TraceData, and TraceData holds the list of spans. The schema version in 3.17.2 is 3 (mlflow.trace_schema.version: "3"). Below is the TraceInfo of a real trace from t.py, made shorter:
{"trace_id": "tr-fee99023d73b75f24da6a4258976baf9",
"trace_location": {"type": "MLFLOW_EXPERIMENT", "mlflow_experiment": {"experiment_id": "1"}},
"request_time": "2026-10-31T16:13:01.175Z", "state": "OK", "execution_duration_ms": 33,
"request_preview": "{\"q\": \"hi\"}", "response_preview": "{\"a\": \"yo\"}",
"trace_metadata": {"mlflow.sourceRun": "c660a66d...", "mlflow.trace.tokenUsage": "{\"input_tokens\": 3, ...}",
"mlflow.traceInputs": "...", "mlflow.traceOutputs": "...", "mlflow.user": "<user>",
"mlflow.source.name": "<script>", "mlflow.trace.sizeBytes": "2002"},
"tags": {"mlflow.traceName": "agent", "mlflow.trace.spansLocation": "TRACKING_STORE"},
"assessments": [ ... ]}- Trace ID. The MLflow trace ID is
tr-followed by the 32 hex digits of the OTel trace ID. - Metadata and tags. Metadata is immutable string pairs that MLflow and the user write when the trace starts. Tags are mutable string pairs.
- Trace location. A trace belongs to one experiment through
trace_location. In Databricks, the location can also be a Unity Catalog table, set withMLFLOW_TRACE_LOCATION(changelog 3.14.0).
Spans
An MLflow Span wraps an OTel span. Span.to_dict() gives the OTLP JSON shape:
{"trace_id": "/umQI9c7dfJNpqQliXa6+Q==", "span_id": "skRdJJD77cc=", "parent_span_id": "0cnaHvdxwZg=",
"name": "llm", "start_time_unix_nano": 1793463181208994000, "end_time_unix_nano": 1793463181209235000,
"events": [], "status": {"code": "STATUS_CODE_OK", "message": ""}, "links": [],
"attributes": {"mlflow.traceRequestId": "\"tr-fee99023...\"", "mlflow.spanType": "\"CHAT_MODEL\"",
"mlflow.spanInputs": "{\"messages\": [...]}", "mlflow.spanOutputs": "\"yo\"",
"mlflow.chat.tokenUsage": "{\"input_tokens\": 3, \"output_tokens\": 1, \"total_tokens\": 4}",
"mlflow.spanLogLevel": "20"}}- Attribute values are JSON strings. MLflow encodes each attribute value as JSON, so a string value carries its own quotes.
- Reserved attributes. MLflow reserves
mlflow.spanType,mlflow.spanInputs,mlflow.spanOutputs,mlflow.chat.tokenUsage,mlflow.llm.model,mlflow.llm.provider,mlflow.llm.costandmlflow.spanLogLevel. Log levels came in 3.13.0. - Span events and links. Spans keep OTel events, for example exceptions. Span links came in 3.13.0 (
LiveSpan.add_link()).
The SpanType constants in mlflow/entities/span.py (3.17.2) are LLM, CHAIN, AGENT, TOOL, CHAT_MODEL, RETRIEVER, PARSER, EMBEDDING, RERANKER, MEMORY, UNKNOWN, WORKFLOW, TASK, GUARDRAIL and EVALUATOR. The type is a free string, so a custom type such as EVIDENCE is stored as given.
Assessments
An assessment is a judgement attached to a trace, or to one span of a trace. MLflow has two kinds:
- Feedback.
mlflow.log_feedback(trace_id=..., name=..., value=..., rationale=..., source=...)records a judgement, for example a judge score or a thumbs up. - Expectation.
mlflow.log_expectation(...)records a ground truth value.
Each assessment has a source with a type (HUMAN, LLM_JUDGE or CODE) and an ID. It also has run_id, span_id, rationale, metadata, overrides and valid. A new assessment can override an old one, and the old one then has valid=false. See the assessments docs.
Links to runs and experiments
- Experiment. Each trace has exactly one experiment, which is
trace_info.experiment_idin SQL. - Run. A trace made inside
mlflow.start_run()gets the metadata keymlflow.sourceRun.MlflowClient.link_traces_to_runlinks traces to a run after the fact. These links go in theentity_associationstable. OTLP ingest takes an optionalx-mlflow-run-idheader since 3.14.0. - Session.
trace_info.session_idgroups the traces of one conversation. The UI has a session view since 3.6.0.
Mapping to OpenTelemetry
The mapping to OTel is direct. An MLflow span is an OTel span with MLflow attributes, and MLflow uses an OTel TracerProvider inside. Span.from_otel_proto() and Span.to_otel_proto() convert between the two forms. The attribute mapping page lists the mappings for each attribute schema.
2. Storage backends
Current backends (3.17.2)
| Tracking URI | Store class | Traces |
|---|---|---|
sqlite:///path.db, postgresql://, mysql://, mssql:// |
SqlAlchemyStore (or WorkspaceAwareSqlAlchemyStore with MLFLOW_ENABLE_WORKSPACES) |
Full support, in process, no server |
http(s)://host |
RestStore |
Full support, through a tracking server |
databricks, databricks://profile |
DatabricksRestStore |
Full support |
file:///path, ./mlruns |
FileStore |
Raises an error by default since 3.13.0. Has no log_spans, so OTLP ingest fails. |
A SQL store can also move old span data out to object storage, for example S3. The trace archival feature came in 3.13.0. The tag mlflow.trace.spansLocation says where the spans of a trace live (TRACKING_STORE or an archive).
How MLflow retired the file store
Direct SQLite with no server is not deprecated. A search of the 3.17.2 source and the changelog found no deprecation of it. Since 3.7.0 it is the default. Only the file store was retired, in these steps:
- 3.6.0 (2025-11-07). “[Model Registry] Add deprecation warnings for filesystem backends (#18524, @harupy)”.
- 3.7.0 (2025-12-05). Breaking change: “SQLite as Default Backend: MLflow now uses SQLite as the default backend instead of file-based storage, unless existing mlruns data is detected.” (#18497). The code is
_get_default_tracking_uri()inmlflow/tracking/_tracking_service/utils.py. It returns./mlrunsonly if./mlruns/<digits>/meta.yamlexists, and returnssqlite:///mlflow.dbin all other cases. - 3.10.0 (2026-02-20). “Add mlflow
migrate-filestorecommand (#20615)”. The migration guide says “The filesystem backend is in maintenance mode and will not receive further updates.” The tool writes only to SQLite, withmlflow migrate-filestore --source /path/to/mlruns --target sqlite:///path/to/mlflow.db. - 3.13.0 (2026-05-29). Breaking change: “Pointing the tracking or model registry store at a local file-system path now raises an error by default; set
MLFLOW_ALLOW_FILE_STORE=trueto keep using a file-based store. (#22773, @harupy)”.
A run with a file:///... tracking URI showed the exact error in 3.17.2, below. It comes from FileStore.__init__ in mlflow/store/tracking/file_store.py.
The filesystem tracking backend (e.g., './mlruns') is in maintenance mode and will not receive further updates. Please migrate to a database backend (e.g., 'sqlite:///mlflow.db') to access the latest MLflow features. The `mlflow migrate-filestore` tool migrates your existing data losslessly. See https://mlflow.org/docs/latest/self-hosting/migrate-from-file-store for migration guidance. If the filesystem backend is required for your workflow, set `MLFLOW_ALLOW_FILE_STORE=true` to opt out of this exception.No removal date is published. The backend store page calls the file store “still supported for backward compatibility”.
Why SQLite can seem to need a server. A client that is not the full mlflow Python package may be the cause. Section 6 shows that mlflow-tracing, the TypeScript SDK, the Claude Code plugin and the Codex plugin all reject or drop sqlite: URIs. For those clients, “SQLite” means “start mlflow server --backend-store-uri sqlite:///mlflow.db”.
3. The SQLite schema for traces (3.17.2)
The schema comes from sqlite3 <file> .schema on a new database that MLflow 3.17.2 made. The Alembic head is dc11669786a5, and there are 67 migration files. The trace tables are below.
CREATE TABLE trace_info (
request_id VARCHAR(50) PRIMARY KEY, -- "tr-<32 hex>"
experiment_id INTEGER NOT NULL REFERENCES experiments ON DELETE CASCADE,
timestamp_ms BIGINT NOT NULL, execution_time_ms BIGINT,
status VARCHAR(50) NOT NULL, -- OK / ERROR / IN_PROGRESS
client_request_id VARCHAR(50),
request_preview VARCHAR(1000), response_preview VARCHAR(1000),
db_payload_generation INTEGER DEFAULT 0 NOT NULL,
trace_name VARCHAR(4096), session_id VARCHAR(250),
input_tokens BIGINT, output_tokens BIGINT, total_tokens BIGINT,
cache_read_input_tokens BIGINT, cache_creation_input_tokens BIGINT,
cache_creation_input_tokens_above_1hr BIGINT,
input_cost FLOAT, output_cost FLOAT, total_cost FLOAT);
CREATE TABLE trace_tags (key VARCHAR(250), value VARCHAR(8000), request_id VARCHAR(50),
PRIMARY KEY (key, request_id)); -- FK trace_info ON DELETE CASCADE
CREATE TABLE trace_request_metadata (key VARCHAR(250), value VARCHAR(8000), request_id VARCHAR(50),
PRIMARY KEY (key, request_id)); -- FK trace_info ON DELETE CASCADE
CREATE TABLE spans (
trace_id VARCHAR(50) NOT NULL, -- the "tr-..." ID, FK trace_info
experiment_id INTEGER NOT NULL,
span_id VARCHAR(50) NOT NULL, parent_span_id VARCHAR(50),
name TEXT, type VARCHAR(500), status VARCHAR(50) NOT NULL,
start_time_unix_nano BIGINT NOT NULL, end_time_unix_nano BIGINT,
duration_ns BIGINT GENERATED ALWAYS AS (end_time_unix_nano - start_time_unix_nano) STORED,
content TEXT NOT NULL, -- the whole span as OTLP-style JSON
input_cost FLOAT, output_cost FLOAT, total_cost FLOAT,
model_name VARCHAR(500), model_provider VARCHAR(500),
PRIMARY KEY (trace_id, span_id));
CREATE TABLE span_metrics (trace_id, span_id, key VARCHAR(250), value FLOAT,
PRIMARY KEY (trace_id, span_id, key));
CREATE TABLE trace_metrics (request_id, key VARCHAR(250), value FLOAT, PRIMARY KEY (request_id, key));
CREATE TABLE assessments (
assessment_id VARCHAR(50) PRIMARY KEY, trace_id VARCHAR(50) NOT NULL,
name VARCHAR(250) NOT NULL, assessment_type VARCHAR(20) NOT NULL, -- feedback / expectation
value TEXT NOT NULL, error TEXT, rationale TEXT, assessment_metadata TEXT,
created_timestamp BIGINT NOT NULL, last_updated_timestamp BIGINT NOT NULL,
source_type VARCHAR(50) NOT NULL, source_id VARCHAR(250),
run_id VARCHAR(32), span_id VARCHAR(50), overrides VARCHAR(50), valid BOOLEAN NOT NULL,
experiment_id INTEGER, trace_timestamp_ms BIGINT, aggregate_value FLOAT,
is_numeric_value BOOLEAN DEFAULT 0 NOT NULL);- Span storage. Each span is one row. The full span, with all attributes, events and links, is a JSON blob in
spans.content, in theSpan.to_dict()shape. MLflow copies some fields into columns for search and indexes, for example name, type, status, times, model and cost. - Translation before storage.
SqlAlchemyStore.log_spans()callstranslate_span_when_storing(), and that function addsmlflow.*attributes for foreign schemas (section 4). - Analytics tables. The rollup tables hold totals and do not hold raw data. They are
sql_span_cost_daily_rollups,sql_trace_metric_daily_rollups,sql_assessment_daily_rollupsandsql_trace_rollup_rebuild_queue. - Related tables.
entity_associationsholds trace to run links. The same database also holds runs, the model registry, datasets, scorers, the gateway, MCP servers, workspaces and jobs, about 65 tables in all.
The trace migrations are in mlflow/store/db_migrations/versions/. Each one runs through Alembic when MLflow opens the database (Creating initial MLflow database tables... Updating database tables). For an existing database, run mlflow db upgrade <uri>.
| Migration | Created | Change |
|---|---|---|
867495a8f9d4_add_trace_tables |
2024-04-27 | Adds trace_info, trace_tags and trace_request_metadata |
770bee3ae1dd_add_assessments_table |
2025-06-23 | Adds assessments |
cbc13b556ace_add_v3_trace_schema_columns |
2025 | Adds the v3 trace columns |
a1b2c3d4e5f6_add_spans_table |
2025-08-03 | Adds spans. Before this migration, spans lived as a JSON artifact file. |
5b0e9adcef9c, 6f8d9c3b2a1e |
Add cascade deletes | |
c8d9e0f1a2b3_add_span_metrics_and_dimension_attributes, b7c8d9e0f1a2_add_trace_metrics_table |
Add the metric tables | |
75868b020152_add_sql_trace_analytics_schema, c1d2e3f4a5b6, 1971f2d9a75b, da6fb0208061 |
2026 | Add analytics, indexes and archival |
The schema changes often. Twelve trace migrations came in about 28 months, from April 2024 to August 2026. Also, the changelog for 3.13.0 lists a full rework of the permission tables.
4. OpenTelemetry
Ingest of OTLP spans
- Endpoint. The tracking server takes
POST /v1/traces(OTLP_TRACES_PATHinmlflow/tracing/utils/otlp.py, router inmlflow/server/otel_api.py). The headerx-mlflow-experiment-idis required. The headerx-mlflow-run-idis optional (3.14.0). - Version. The first ingest code came before 3.6.0. The 3.6.0 release (2025-11-07) announced “Full OpenTelemetry Support in OSS Server: … ingest OpenTelemetry traces into MLflow” (#18540, #18532, #18357).
- Encodings. OTLP/HTTP protobuf works since 3.6.0, and 3.7.0 fixed it to return protobuf (#18929). Gzip and deflate came in 3.7.0 (#19024). JSON OTLP came in 3.12.0 (#22408). There is no gRPC endpoint.
- Backend. The server calls
store.log_spans_async().FileStoredoes not implement it, so ingest needs a SQL backend. - Allow list. The server keeps
service.nameonly for an allow list (claude-code,codex_cli_rs,codex_vscode,gemini-cliandqwen-code). It keeps resource attributes since 3.14.0. - Docs. See the ingest docs and the OpenTelemetry overview.
Attribute translation on ingest
When MLflow stores a span, it maps these foreign attribute schemas to mlflow.spanType, inputs, outputs, token usage, model and provider. The translators are in mlflow/tracing/otel/translation/:
- Standards. OTel GenAI semantic conventions (
genai_semconv.py, which readsgen_ai.operation.nameandgen_ai.usage.*), OpenInference, Traceloop and OpenLLMetry. - Agent CLIs. Claude Code, Gemini CLI, Google ADK and Spring AI.
- Other tools. Langfuse, Laminar, Vercel AI, VoltAgent and LiveKit.
A test checked the GenAI translation with no server. It builds an OTel protobuf span with gen_ai.operation.name=chat, gen_ai.request.model, gen_ai.provider.name and gen_ai.usage.input_tokens/output_tokens. It calls Span.from_otel_proto() and then SqlAlchemyStore.log_spans(experiment_id, [span]) on t.db. The results:
- Trace info. MLflow made the
trace_inforow, withinput_tokens=10andoutput_tokens=5. - Span content.
contentgainedmlflow.spanType="CHAT_MODEL",mlflow.chat.tokenUsage,mlflow.llm.modelandmlflow.llm.provider. - Columns.
model_nameandmodel_providerwere filled. - Quirk. The
spans.typecolumn stayedNULLfor the translated span, althoughcontentsaysCHAT_MODEL. A search by span type can miss spans that came in with foreign schemas.
Export to an OTel collector
The settings below are in mlflow/environment_variables.py, and the export docs describe them.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT(withOTEL_EXPORTER_OTLP_TRACES_PROTOCOLand headers). If it is set, MLflow exports to the OTLP endpoint in place of the tracking store.MLFLOW_ENABLE_OTLP_EXPORTER(defaultTrue). Set toFalse, it turns off the OTLP export above, even when the OTel variable is set. It came in 3.5.0 (#17505).MLFLOW_TRACE_ENABLE_OTLP_DUAL_EXPORT(defaultFalse). It writes to both the tracking store and the collector.MLFLOW_ENABLE_OTEL_GENAI_SEMCONV(defaultFalse). It rewritesmlflow.*attributes to GenAI semantic conventions before OTLP export, since 3.11.0 (#21494). See the GenAI semconv docs.MLFLOW_USE_DEFAULT_TRACER_PROVIDER(defaultTrue, which gives an isolated provider). Set it toFalseto mix MLflow and plain OTel SDK spans in one trace.MLFLOW_TRACE_PROPAGATE_TO_OTEL_CONTEXTlets plain OTel libraries nest under MLflow spans.
5. Integrations
| Integration | How it works | Notes |
|---|---|---|
mlflow.litellm.autolog() |
Adds MLflow callbacks to litellm.success_callback and failure_callback (mlflow/litellm/__init__.py) |
Works with a direct SQLite URI |
mlflow.pydantic_ai.autolog() |
Patches Pydantic AI (autolog.py and autolog_v2.py) |
Pydantic AI 2.x needs 2.5.0 or newer |
mlflow.anthropic.autolog() |
Patches the Anthropic SDK, and also traces the Python Claude Agent SDK | Has a GenAI semconv converter |
mlflow.openai.autolog() |
Patches the OpenAI SDK and the OpenAI Agents SDK (_agent_tracer.py) |
Has a GenAI semconv converter |
The repo also has autolog modules for LangChain, LlamaIndex, DSPy, CrewAI, AutoGen, AG2, Agno, smolagents, Strands, Semantic Kernel, Haystack, Bedrock, Gemini, Groq, Mistral and MCP.
Claude Code
Current path (3.13.0 and later). Claude Code tracing is a Claude Code plugin, mlflow-tracing@mlflow-plugins. The plugin is the npm package @mlflow/claude-code, and its source is in libs/typescript/integrations/claude-code. Install it with these commands:
claude plugin marketplace add mlflow/mlflow --sparse .claude-plugin
claude plugin install mlflow-tracing@mlflow-plugins
/mlflow-tracing:setup # or: mlflow autolog claude- Hook. The plugin registers one Stop hook, in
hooks/hooks.json. The hook runsnode ${CLAUDE_PLUGIN_ROOT}/bundle/stop.cjswith a 120-second timeout. - Transcript. The hook reads the session transcript JSONL (
transcript.ts,processTranscript). It makes anAGENTroot span for each turn, withLLMandTOOLchildren and nestedAGENTspans for subagents. Token and cache usage go on each LLM span. - Write-ahead log. A write-ahead log, a local file of pending writes, keeps tracing from slowing the agent or losing traces (changelog 3.14.0).
- Tracking URI.
isValidTrackingUri()insrc/config.tsaccepts onlyhttp:,https:anddatabricksURIs. The plugin cannot write to SQLite, so it needsmlflow server. - History. Claude Code tracing came in 3.4 and 3.5 (#17521, #18022), as a Python hook that
mlflow autolog claudewrote into.claude/settings.json. In 3.12.0, the tracing moved to TypeScript (#22338). The 3.13.0 notes say “mlflow autolog claudeno longer installs the old Python autolog hook; Claude Code tracing is now provided by the official Claude plugin, which must be installed separately. (#23339)”. The Pythonmlflow/claude_code/tracing.pystill has the transcript parser. - OTel path. Claude Code also emits OTLP traces by itself, which is beta in Claude Code, to
/v1/traces. MLflow mapsclaude_code.interaction,claude_code.llm_requestandclaude_code.tooltoAGENT,LLMandTOOL(translation/claude_code.py). The content is<REDACTED>if the user does not setOTEL_LOG_USER_PROMPTS,OTEL_LOG_TOOL_DETAILSorOTEL_LOG_TOOL_CONTENT. - Agent SDK. For the TypeScript Claude Agent SDK, use
createTracedQuery(query). For the Python Claude Agent SDK, usemlflow.anthropic.autolog(). - Docs. See the Claude Code docs.
Codex
Codex tracing came in 3.12.0, as “Add TypeScript Codex CLI tracing via notify hook (#22410)”. Its parts are these:
- Notify hook. The npm package
@mlflow/codexaddsnotify = ["mlflow-codex", "notify-hook"]toconfig.toml. Codex calls the hook after each turn. The hook reads the rollout JSONL transcript and honorsCODEX_HOMEsince 3.16.0. Each turn gives anAGENTroot span withLLMandTOOLchildren. - Native OTel. The
[otel]table in~/.codex/config.tomlcan point athttp://host:5000/v1/traceswithx-mlflow-experiment-id. The docs warn that these spans show “internal execution details” and are not a clean conversation flow. - Server. The docs say “The MLflow server must be running to receive traces.”
- Docs. See the Codex docs.
The AI Gateway (3.13.0) can also trace Claude Code, Codex and Gemini CLI. It works as a proxy for model calls.
6. Writing traces with no server
All three APIs below work in 3.17.2 with mlflow.set_tracking_uri("sqlite:////abs/path.db") and the full mlflow package. A test ran each of them.
mlflow.start_span(name, span_type=...)is a context manager.@mlflow.traceis a decorator. Both make spans under the active trace, or start a new trace.MlflowClient().start_trace(name, span_type, inputs, attributes, tags, experiment_id, start_time_ns, run_id, links), withstart_span,end_spanandend_trace, takes explicit IDs and times. Use it to replay events that happened earlier.mlflow.log_trace(name, request, response, intermediate_outputs, attributes, tags, start_time_ms, execution_time_ms)writes a trace with a single root span.SqlAlchemyStore(uri, artifact_root).log_spans(experiment_id, spans)is a lower-level store method, not a public API. It takesmlflow.entities.Spanobjects, which can come fromSpan.from_otel_proto(). MLflow makes thetrace_inforow for you.mlflow.log_feedbackandmlflow.log_expectationwrite assessments.
Be careful with these behaviors:
- Asynchronous writes. Trace writes go through a background queue. If you read a trace in the same process, call
mlflow.flush_trace_async_logging()first. Without the flush, mylog_feedbackcall failed with “Trace … not found”. - Silent drops in the lightweight SDK.
mlflow-tracing3.17.2 (47 MB) has nosqlitestore. With asqlite:URI,start_spanwrites no file and gives no warning. The failure shows only at DEBUG level (KeyError: 'sqlite'in the store registry). Tracing fails open by design. The TypeScript@mlflow/coreSDK also needs HTTP or Databricks. - Install size. The full
mlflowpackage with its dependencies takes 509 MB in the venv. It pulls in pandas, scikit-learn, scipy, matplotlib, pyarrow, Flask, alembic and docker.
7. Recommendation
MLflow is a reasonable first backend for provevi, if provevi treats MLflow as one driver behind its own interface. Provevi must not take MLflow’s schema as its own.
Reasons to use MLflow:
- OTel shape. The span model is OTel. An MLflow span is an OTel span with
mlflow.*attributes, so OTel spans fit with no change. - Paths for OTel spans. OTel spans can enter through
/v1/traceswith a server. They can also enter in process throughSpan.from_otel_proto()andlog_spans()into SQLite with no server. The GenAI semconv translation is built in. - Evidence events. Provevi evidence events can map to spans with a custom
span_type, attributes and span events. Judgements about evidence map to assessments, which have a source, a rationale, an override chain and avalidflag. - Viewer. The UI, search, sessions, cost rollups and the Claude Code and Codex collectors come with it.
Risks, and how to handle each one:
- The schema changes often and is internal. Twelve trace migrations came in about 28 months, and analytics tables are new. Do not read or write the SQLite tables from provevi. Use the MLflow client APIs, or OTLP. Pin a minor version in the driver.
- The supported paths keep moving. MLflow retired the file store over 3.6.0 to 3.13.0. It replaced the Claude Code hook in 3.12.0 and 3.13.0. Keep the driver small, and test it against each MLflow minor release.
- The clients disagree about SQLite. Only full Python
mlflowwrites SQLite in process. The plugins for Claude Code and Codex, the TypeScript SDK andmlflow-tracingneed a server, andmlflow-tracingdrops spans in silence. For agent CLIs, plan to runmlflow server --backend-store-uri sqlite:///.... If a URI is wrong, make the provevi driver fail loudly. - The dependency is heavy. Full
mlflowtakes about 509 MB. Load it only inside the driver, as an optional extra. - IDs and identity. MLflow rewrites the trace ID to
tr-<hex>. Every trace needs an experiment. Attribute values are JSON strings. Keep provevi’s own IDs in attributes or metadata, so a round trip keeps them. - Losses on ingest.
service.nameis kept only for allow-listed clients. Thespans.typecolumn can stayNULLfor translated spans. Spans can move to an archive store. A driver test must read spans back throughget_traceand must not trust the columns. - Asynchronous writes. Writes are asynchronous by default. The driver must flush before it reads its own writes, and on shutdown.
Alternative. A small provevi SQLite driver of its own could store the OTLP JSON shape, one span per row, as MLflow does. An MLflow driver can then be the second backend, for the UI and the agent CLI collectors. With that order, provevi does not depend on MLflow’s rate of change.
Sources
- MLflow 3.17.2 source, installed from PyPI in a scratch environment. The files used are
mlflow/tracking/_tracking_service/utils.py,mlflow/store/tracking/file_store.py,mlflow/store/tracking/sqlalchemy_store.py,mlflow/entities/span.py,mlflow/server/otel_api.py,mlflow/tracing/otel/translation/*.py,mlflow/environment_variables.py,mlflow/claude_code/*.pyandmlflow/store/db_migrations/versions/*.py. - MLflow CHANGELOG, https://github.com/mlflow/mlflow/blob/master/CHANGELOG.md, and the raw file at https://raw.githubusercontent.com/mlflow/mlflow/master/CHANGELOG.md
- Migrate from the file store, https://mlflow.org/docs/latest/self-hosting/migrate-from-file-store
- Backend stores, https://mlflow.org/docs/latest/self-hosting/architecture/backend-store/
- OpenTelemetry overview, https://mlflow.org/docs/latest/genai/tracing/opentelemetry/ and the subpages
ingest/,export/,genai-semconv/andattribute-mapping/ - Claude Code tracing, https://mlflow.org/docs/latest/genai/tracing/integrations/listing/claude_code
- Claude Code plugin source, https://github.com/mlflow/mlflow/tree/master/libs/typescript/integrations/claude-code and https://github.com/mlflow/mlflow/blob/master/.claude-plugin/marketplace.json
- Codex tracing, https://mlflow.org/docs/latest/genai/tracing/integrations/listing/codex
- Lightweight SDK, https://mlflow.org/docs/latest/genai/tracing/lightweight-sdk/
- Trace archival, https://mlflow.org/docs/latest/genai/tracing/observe-with-traces/archive-traces