[mason] tracing: managed MLflow tracing, on by default, experiment_id-centric - #554
Conversation
…-centric Replace the UC-schema tracing surface with managed MLflow tracing, keyed entirely on the experiment id. Tracing is on by default: dev/deploy send an agent's traces to a per-app experiment (/Users/<user>/mason-traces/<app>), auto-created on first run. - tracing.py: commands are `configure [--experiment <id>]` (on/rebind, id-only override), `disable` (off), `list`, `get`. Drops UC schema/warehouse, `set_experiment_trace_location` linking, and `instrument`. `ensure_experiment` creates the experiment (with the parent workspace-folder fix) and returns its id. - Runtime binding is just MLFLOW_TRACKING_URI=databricks + MLFLOW_EXPERIMENT_ID=<id>. - agent_project.py: a single `[tracing]` table (`experiment_id` / `disabled`) with `configure_tracing` / `disable_tracing`. - deploy.py/dev.py: resolve/create the experiment via `resolve_trace_experiment` (best-effort; never blocks a run). Deploy grants the app's SP write access by declaring the experiment as an app resource (CAN_EDIT) — no manual SQL grant. - store_access.py: `apply_experiment_resource` (the platform-managed grant). - Rewrote tracing/deploy/dev tests for the managed model. Co-authored-by: Isaac <no-reply@databricks.com>
Isaac Review + live dev/deploy verification against e2-dogfood surfaced three real bugs (all now fixed, with regression tests): - dev/deploy keyed the experiment differently: deploy used the mason-prefixed deployment name, dev the source-dir name, so a normal agent traced to two experiments. deploy now keys on source_dir.name too, matching dev. - tracing provisioning is genuinely best-effort now: dev/deploy catch any Exception (not just AgentCliError) from experiment creation, so an MLflow/network/permission error no longer aborts the run. - `mason dev` no longer builds the workspace client eagerly outside the tracing try, so a purely-local run with no auth/stores still runs (tracing degrades to off). - list: use search_traces(locations=...) instead of the deprecated experiment_ids= (was leaking a FutureWarning into CLI output). Live-verified on e2-dogfood: dev traces land in the auto-created per-app experiment; the deployed app's SP writes traces via the experiment app-resource grant with no manual grant. Co-authored-by: Isaac <no-reply@databricks.com>
…-only for now) `mason tracing configure --experiment <id>` now detects a UC-backed experiment (via its `mlflow.experiment.databricksTraceDestinationPath` tag) and errors with "UC-backed MLflow tracing is not supported by mason." rather than silently pinning a config that later fails (reads need a SQL warehouse; a deployed app's SP needs UC grants mason doesn't set up yet). Managed experiments are unaffected. Supporting UC-backed experiments (require a warehouse + grant the app SP UC access on the schema) is deferred to a follow-up. Co-authored-by: Isaac <no-reply@databricks.com>
# Conflicts: # integrations/mason/src/databricks_mason/agent_project.py # integrations/mason/src/databricks_mason/deploy.py
| trace_experiment_id: str | None = None, | ||
| trace_disabled: bool = False, |
There was a problem hiding this comment.
is it possible to derive trace_disabled from trace_experiment_id == None?
There was a problem hiding this comment.
Not cleanly - trace_experiment_id and disabled are orthogonal, so folding one into the other loses a state:
experiment_idanswers which experiment.None= "use the per-app default", not "off".disabledanswers on/off. Tracing is on by default, so a fresh/absent[tracing](experiment_id == None) must mean on-with-default. IfNonealso meant disabled, we could never express on-by-default. And clearingexperiment_idto turn tracing off would not stick - the nextdev/deployjust re-derives the default and turns it back on.
That said, your underlying point (the special-cased "recompute the default each run" was weird) is fixed in 107ec39: resolve_trace_experiment now pins the resolved default id into agent.toml on the first dev/deploy, so later runs reuse it by id. After first run there is no separate "default" state - just a pinned id or disabled = true.
There was a problem hiding this comment.
can we create the toml with the trace_experiment_id set to the default experiment path always? and dev and deploy never overwrite trace_experiment_id in agent.toml? then we can remove disabled, if trace_experiment_id is unset then the user must have disabled it
There was a problem hiding this comment.
the question here is when to create the default experiment and get back the id to write to the toml file. While we can derive the experiment name (path) from the mason project name, we cannot get the id until we actually create the experiment which is done in mason dev / deploy and not mason init.
There was a problem hiding this comment.
ah I see... makes sense. What about setting the experiment name in toml file? as the agent author it would be nice to see the experiment name rather than the id in my configuration file.
There was a problem hiding this comment.
also i think similar motivation to your first comment, having both trace_disabled and trace_experiment_id is a little confusing to me.
There was a problem hiding this comment.
What about setting the experiment name in toml file
I chose id since it's the canonical identifier as mlflow experiment names can be changed.
having both trace_disabled and trace_experiment_id is a little confusing to me
Good call out. I went back and forth on this and open to ideas. The reason I kept both is I wanted to provide a knob to disable tracing even if it was bound to an experiment previously.
- deploy: `trace_env` -> `mlflow_tracing_config` returning a typed `MlflowTracingConfig` dataclass (`.env()` renders the two MLflow env vars) instead of a bare dict. - tracing: `_trace_json` -> `_trace_to_json`; reword the configure hints to "use the mason default". - dev: reword the on-by-default docstring; "Running without traces" -> "Proceeding without tracing". Co-authored-by: Isaac <no-reply@databricks.com>
…irst run `resolve_trace_experiment` now writes the auto-created per-app experiment id back into `agent.toml [tracing] experiment_id` on the first `mason dev` / `mason deploy`. Later runs read the pinned id instead of re-deriving the default from the app name, so there is no special-cased "default" state once tracing has run once. The `disabled` flag is retained: it remains the only representation of tracing-off (a fresh/absent config still means on-by-default, which cannot be encoded by a cleared experiment id). Co-authored-by: Isaac <no-reply@databricks.com>
…app" The default tracing experiment is keyed on the source directory's basename (the Mason project name), not the deployed app name (which is `mason-<name>`). Rename the parameter (`resolve_trace_experiment`'s `app` -> `project_name`, `default_experiment`'s `app` -> `project`) and align all user-facing help, docs, and comments on "per-project" so the terminology matches what the value actually is. No behavior change: the experiment path is still `/Users/<you>/mason-traces/<project>`. Co-authored-by: Isaac <no-reply@databricks.com>
| only checks a bound store still exists (a typo or unbound clone fails here, not at runtime) and | ||
| returns the trace env. | ||
| def validate_stores(client, *, memory_store: Optional[str], session_store: Optional[str]) -> None: | ||
| """Validate the agent's bound stores exist. Shared by `mason deploy` and `mason dev`. |
There was a problem hiding this comment.
follow up: move this and _upsert_manifest_env to some shared util file and resolve_trace_experiment to mason.tracing?
There was a problem hiding this comment.
will do as a follow up
| return _DEFAULT_EXPERIMENT | ||
| return f"/Users/{user}/mason-traces/{app}" | ||
|
|
||
| def default_experiment(user: str, project: Optional[str]) -> str: |
There was a problem hiding this comment.
can this be used in agent_project.py as the default experiment?
There was a problem hiding this comment.
see above comment that we store the experiment_id in the project but this default is the experiment_name (that is derived from the mason project name)
| trace_experiment_id: str | None = None, | ||
| trace_disabled: bool = False, |
There was a problem hiding this comment.
can we create the toml with the trace_experiment_id set to the default experiment path always? and dev and deploy never overwrite trace_experiment_id in agent.toml? then we can remove disabled, if trace_experiment_id is unset then the user must have disabled it
| return _DEFAULT_EXPERIMENT | ||
| return f"/Users/{user}/mason-traces/{app}" | ||
|
|
||
| def default_experiment(user: str, project: Optional[str]) -> str: |
There was a problem hiding this comment.
| def default_experiment(user: str, project: Optional[str]) -> str: | |
| def default_experiment_name(user: str, project: Optional[str]) -> str: |
| if warehouse_id: | ||
| os.environ["MLFLOW_TRACING_SQL_WAREHOUSE_ID"] = warehouse_id | ||
|
|
||
| def ensure_experiment(profile: Optional[str], client, name: str) -> str: |
There was a problem hiding this comment.
| def ensure_experiment(profile: Optional[str], client, name: str) -> str: | |
| def create_experiment_idempotent(profile: Optional[str], client, name: str) -> str: |
| trace_experiment_id: str | None = None, | ||
| trace_disabled: bool = False, |
There was a problem hiding this comment.
the question here is when to create the default experiment and get back the id to write to the toml file. While we can derive the experiment name (path) from the mason project name, we cannot get the id until we actually create the experiment which is done in mason dev / deploy and not mason init.
| only checks a bound store still exists (a typo or unbound clone fails here, not at runtime) and | ||
| returns the trace env. | ||
| def validate_stores(client, *, memory_store: Optional[str], session_store: Optional[str]) -> None: | ||
| """Validate the agent's bound stores exist. Shared by `mason deploy` and `mason dev`. |
There was a problem hiding this comment.
will do as a follow up
| return _DEFAULT_EXPERIMENT | ||
| return f"/Users/{user}/mason-traces/{app}" | ||
|
|
||
| def default_experiment(user: str, project: Optional[str]) -> str: |
There was a problem hiding this comment.
see above comment that we store the experiment_id in the project but this default is the experiment_name (that is derived from the mason project name)
Rename tracing helpers so a function's name states whether it deals in an experiment *name* (workspace path) or an *id*, and what it does: - default_experiment -> default_experiment_name (returns the name/path) - ensure_experiment -> create_experiment_idempotent (creates-or-gets, returns id) - resolve_trace_experiment -> resolve_trace_experiment_id (returns the id) - _configure -> _set_tracking_uri (it sets MLflow's tracking uri; the old name collided with the user-facing `tracing configure` command) Pure rename, no behavior change. Co-authored-by: Isaac <no-reply@databricks.com>
Follow-up to b627196, which by mistake committed only dev_test.py. This adds the actual source renames and the other test files, so the branch is consistent: - default_experiment -> default_experiment_name - ensure_experiment -> create_experiment_idempotent - resolve_trace_experiment -> resolve_trace_experiment_id - _configure -> _set_tracking_uri Pure rename, no behavior change. Co-authored-by: Isaac <no-reply@databricks.com>
Summary
mason tracing configureto bind to an existing experimentWhat changed
mason tracing:configure [--experiment <id>](on / rebind, id-only override),disable(off),list,get. Droppedsetup --catalog/--schema/--warehouse-id, the UCset_experiment_trace_locationlinking, andinstrument.mason devandmason deploysend traces to a per-app experiment (/Users/<user>/mason-traces/<app>), auto-created on first run. No--with-tracesflags.MLFLOW_TRACKING_URI=databricks+MLFLOW_EXPERIMENT_ID=<id>.agent.toml: a single[tracing]table (experiment_id/disabled).AppResourceExperiment(experiment_id, CAN_EDIT)app resource so the app's service principal can write traces with no manual grant.Managed-only for now (UC-backed rejected)
masonmanages managed (non-UC) experiments. Ifconfigure --experiment <id>is pointed at a UC-backed experiment, it errors clearly (UC-backed MLflow tracing is not supported by mason.) rather than silently wiring a config that would fail at read/deploy - reading UC traces needs a SQL warehouse and a deployed app's SP needs UC grants mason doesn't set up. Supporting UC-backed experiments (require a warehouse + grant the app SPUSE_CATALOG/USE_SCHEMA/SELECT/MODIFYon the schema) is deferred to a follow-up.Testing done
Unit:
401 passed,tyclean,ruffclean.Live (e2-dogfood, fresh env - installed the CLI, scaffolded,
dev,deploy):mason dev: the per-app experiment was auto-created and the two MLflow env vars wired intoapp.yaml; invoking the local agent produced a trace thatmason tracing listshows.mason deploy: the deploy reportedTrace access - granted to app service principal(the experiment app-resource), and invoking the deployed app via OAuth produced a second trace in the same experiment - written by the app's service principal with no manual grant. Both traces (dev + deployed) appear inmason tracing list.configure --experiment <it>errors cleanly; a managed experiment still configures fine.Isaac Review found three MAJOR bugs that unit tests and the initial live run missed - dev/deploy keying the experiment on different names, a non-
AgentCliErrorfrom provisioning aborting the deploy, and an offlinemason devregression - each now fixed and covered by a regression test.Note
This supersedes the UC-only tracing PR #545 (different branch); that approach is abandoned in favor of this simpler managed model.
This pull request and its description were written by Isaac.