Repository: mlflow/mlflow
Pull request: R Tracking/Fluent API
This change splits the tracking feature into a "fluent" component and a "client" component to be consistent with other languages and fix #432.

The explicit Tracking Service/client API, which is a thin layer on top of the internal REST wrappers, is exposed to the user in `tracking-client.R`. Similar to their Python counterparts, these methods require an `mlflow_client` object (created in R via `mlflow_client()`) to be passed and do not depend on global state. The higher-level fluent API, which implicitly manages active runs and experiments, lives in `tracking-fluent.R`. These are the functions we expect most users to be using.

Note that the "connection" constructs, e.g. `mlflow_connect()` and the "active connection" constructs have been deprecated since `mlflow_client` now provides the necessary mechanism to track services.

This PR is WIP: There are a couple methods yet to be implemented, specifically `list_run_infos` and `download_artifacts`. The documentation also needs to be updated.

Problem statement:
Consider mapping Service API 1:1 in R
The tracking service API is currently implemented through the main API by providing an additional `run_uuid` parameter; this issue is tracking an open discussion on whether the API should be locked under a `mlflow_service_api()` block since it's meant to be used by package authors, for the most part.

Hints from issue discussion:
We are planning to map 1:1 the R API to Python/Java Service APIs using functions like:

- `mlflow_service_log_param(service, ...)`
- `mlflow_service_log_metric(service, ...)`

under a `tracking-service.R` or alike file name that can be easily updated across many programming languages, including R.

Commit message: add mlflow_get_experiment_by_name() and mlflow_set_experiment()

Patch to review:
diff --git a/mlflow/R/mlflow/.Rbuildignore b/mlflow/R/mlflow/.Rbuildignore
index d393c4592597d..cbd842cf8aa15 100644
--- a/mlflow/R/mlflow/.Rbuildignore
+++ b/mlflow/R/mlflow/.Rbuildignore
@@ -11,3 +11,4 @@ packrat
 document.R
 Reference_Manual_mlflow.md
 .lintr
+^man-roxygen/
diff --git a/mlflow/R/mlflow/NAMESPACE b/mlflow/R/mlflow/NAMESPACE
index ee8c158e2eb69..11546de189c6a 100644
--- a/mlflow/R/mlflow/NAMESPACE
+++ b/mlflow/R/mlflow/NAMESPACE
@@ -1,26 +1,36 @@
 # Generated by roxygen2: do not edit by hand
 
+S3method(mlflow_create_experiment,"NULL")
+S3method(mlflow_create_experiment,mlflow_client)
 S3method(mlflow_load_flavor,crate)
+S3method(mlflow_log_artifact,"NULL")
+S3method(mlflow_log_artifact,mlflow_client)
+S3method(mlflow_log_metric,"NULL")
+S3method(mlflow_log_metric,mlflow_client)
+S3method(mlflow_log_param,"NULL")
+S3method(mlflow_log_param,mlflow_client)
 S3method(mlflow_predict_flavor,crate)
 S3method(mlflow_save_flavor,crate)
+S3method(mlflow_set_tag,"NULL")
+S3method(mlflow_set_tag,mlflow_client)
 S3method(mlflow_ui,"NULL")
-S3method(mlflow_ui,character)
-S3method(mlflow_ui,mlflow_connection)
+S3method(mlflow_ui,mlflow_client)
 S3method(with,mlflow_active_run)
 export(crate)
-export(mlflow_active_experiment)
 export(mlflow_active_run)
 export(mlflow_cli)
-export(mlflow_connect)
+export(mlflow_client)
 export(mlflow_create_experiment)
 export(mlflow_create_run)
-export(mlflow_disconnect)
+export(mlflow_delete_experiment)
 export(mlflow_end_run)
 export(mlflow_get_experiment)
+export(mlflow_get_experiment_by_name)
 export(mlflow_get_metric)
 export(mlflow_get_metric_history)
 export(mlflow_get_param)
 export(mlflow_get_run)
+export(mlflow_get_tracking_uri)
 export(mlflow_install)
 export(mlflow_list_experiments)
 export(mlflow_load_flavor)
@@ -30,6 +40,7 @@ export(mlflow_log_model)
 export(mlflow_log_param)
 export(mlflow_param)
 export(mlflow_predict_flavor)
+export(mlflow_restore_experiment)
 export(mlflow_restore_snapshot)
 export(mlflow_rfunc_predict)
 export(mlflow_rfunc_serve)
@@ -37,16 +48,15 @@ export(mlflow_run)
 export(mlflow_save_flavor)
 export(mlflow_save_model)
 export(mlflow_server)
-export(mlflow_set_active_experiment)
-export(mlflow_set_active_run)
+export(mlflow_set_experiment)
+export(mlflow_set_tag)
+export(mlflow_set_terminated)
 export(mlflow_set_tracking_uri)
 export(mlflow_snapshot)
 export(mlflow_source)
 export(mlflow_start_run)
-export(mlflow_tracking_uri)
 export(mlflow_ui)
 export(mlflow_uninstall)
-export(mlflow_update_run)
 import(forge)
 import(jsonlite)
 import(swagger)
diff --git a/mlflow/R/mlflow/R/artifact.R b/mlflow/R/mlflow/R/artifact.R
deleted file mode 100644
index 6d055e640eae3..0000000000000
--- a/mlflow/R/mlflow/R/artifact.R
+++ /dev/null
@@ -1,67 +0,0 @@
-#' Log Artifact
-#'
-#' Logs an specific file or directory as an artifact.
-#'
-#' @param path The file or directory to log as an artifact.
-#' @param artifact_path Destination path within the run’s artifact URI.
-#' @param run_uuid The run associated with this artifact.
-#'
-#' @details
-#'
-#' When logging to Amazon S3, ensure that the user has a proper policy
-#' attach to it, for instance:
-#'
-#' \code{
-#' {
-#' "Version": "2012-10-17",
-#' "Statement": [
-#'   {
-#'     "Sid": "VisualEditor0",
-#'     "Effect": "Allow",
-#'     "Action": [
-#'       "s3:PutObject",
-#'       "s3:GetObject",
-#'       "s3:ListBucket",
-#'       "s3:GetBucketLocation"
-#'       ],
-#'     "Resource": [
-#'       "arn:aws:s3:::mlflow-test/*",
-#'       "arn:aws:s3:::mlflow-test"
-#'       ]
-#'   }
-#'   ]
-#' }
-#' }
-#'
-#' Additionally, at least the \code{AWS_ACCESS_KEY_ID} and \code{AWS_SECRET_ACCESS_KEY}
-#' environment variables must be set to the corresponding key and secrets provided
-#' by Amazon IAM.
-#'
-#' @export
-mlflow_log_artifact <- function(path, artifact_path = NULL, run_uuid = NULL) {
-  run_uuid <- run_uuid %||%
-    mlflow_active_run()$run_info$run_uuid %||%
-    stop("`run_uuid` must be specified when there is no active run.")
-
-  artifact_param <- NULL
-  if (!is.null(artifact_path)) artifact_param <- "--artifact-path"
-
-  if (as.logical(fs::is_file(path))) {
-    command <- "log-artifact"
-    local_param <- "--local-file"
-  } else {
-    command <- "log-artifacts"
-    local_param <- "--local-dir"
-  }
-
-  mlflow_cli("artifacts",
-             command,
-             local_param,
-             path,
-             artifact_param,
-             artifact_path,
-             "--run-id",
-             run_uuid)
-
-  invisible(NULL)
-}
diff --git a/mlflow/R/mlflow/R/cli.R b/mlflow/R/mlflow/R/cli.R
index 898e9c8d74035..9e8c37a8dc491 100644
--- a/mlflow/R/mlflow/R/cli.R
+++ b/mlflow/R/mlflow/R/cli.R
@@ -34,7 +34,7 @@ mlflow_cli <- function(..., background = FALSE, echo = TRUE) {
     PATH = paste(Sys.getenv("PATH"), python, sep = ":"),
     MLFLOW_CONDA_HOME = python_conda_home(),                      # devel version
     MLFLOW_MLFLOW_CONDA = file.path(python_conda_bin(), "conda"), # pip version (deprecated)
-    MLFLOW_TRACKING_URI = mlflow_tracking_uri()
+    MLFLOW_TRACKING_URI = mlflow_get_tracking_uri()
   )
 
   with_envvar(env, {
diff --git a/mlflow/R/mlflow/R/connection.R b/mlflow/R/mlflow/R/connection.R
deleted file mode 100644
index 5988b1955ec55..0000000000000
--- a/mlflow/R/mlflow/R/connection.R
+++ /dev/null
@@ -1,185 +0,0 @@
-mlflow_get_or_create_active_connection <- function() {
-  if (is.null(mlflow_active_connection())) {
-    tracking_uri <- mlflow_tracking_uri()
-    if (startsWith(tracking_uri, "http")) {
-      mc <- new_mlflow_connection(tracking_uri = tracking_uri, handle = NULL)
-    } else {
-      mc <- mlflow_connect(tracking_uri)
-    }
-
-    mlflow_set_active_connection(mc)
-  }
-
-  mlflow_active_connection()
-}
-
-mlflow_active_connection <- function() {
-  .globals$active_connection
-}
-
-mlflow_set_active_connection <- function(mc) {
-  if (!identical(mc, .globals$active_connection)) {
-    .globals$active_connection <- mc
-    .globals$tracking_uri <- mc$tracking_uri
-    mlflow_set_active_experiment(NULL)
-  }
-  invisible(mc)
-}
-
-#' @importFrom httpuv startDaemonizedServer
-#' @importFrom httpuv stopServer
-mlflow_port_available <- function(port) {
-  tryCatch({
-    handle <- httpuv::startDaemonizedServer("127.0.0.1", port, list())
-    httpuv::stopServer(handle)
-    TRUE
-  }, error = function(e) {
-    FALSE
-  })
-}
-
-#' @importFrom openssl rand_num
-mlflow_connect_port <- function() {
-  port <- getOption(
-    "mlflow.port",
-    NULL
-  )
-
-  retries <- getOption("mlflow.port.retries", 10)
-  while (is.null(port) && retries > 0) {
-    port <- floor(5000 + rand_num(1) * 1000)
-    if (!mlflow_port_available(port)) {
-      port <- NULL
-    }
-
-    retries <- retries - 1
-  }
-
-  port
-}
-
-mlflow_cli_param <- function(args, param, value) {
-  if (!is.null(value)) {
-    args <- c(
-      args,
-      param,
-      value
-    )
-  }
-
-  args
-}
-
-#' Run the MLflow Tracking Server
-#'
-#' Wrapper for `mlflow server`.
-#'
-#' @param file_store The root of the backing file store for experiment and run data.
-#' @param default_artifact_root Local or S3 URI to store artifacts in, for newly created experiments.
-#' @param host The network address to listen on (default: 127.0.0.1).
-#' @param port The port to listen on (default: 5000).
-#' @param workers Number of gunicorn worker processes to handle requests (default: 4).
-#' @param static_prefix A prefix which will be prepended to the path of all static paths.
-#' @export
-mlflow_server <- function(file_store = "mlruns", default_artifact_root = NULL,
-                          host = "127.0.0.1", port = 5000, workers = 4, static_prefix = NULL) {
-
-  if (is.null(default_artifact_root) || dir.exists(default_artifact_root)) {
-    file_store <- fs::path_abs(file_store)
-  }
-
-  args <- mlflow_cli_param(list(), "--port", port) %>%
-    mlflow_cli_param("--file-store", file_store) %>%
-    mlflow_cli_param("--default-artifact-root", default_artifact_root) %>%
-    mlflow_cli_param("--host", host) %>%
-    mlflow_cli_param("--port", port) %>%
-    mlflow_cli_param("--workers", workers) %>%
-    mlflow_cli_param("--static-prefix", static_prefix)
-
-  mlflow_verbose_message("MLflow starting: http://", host, ":", port)
-
-  handle <- do.call(
-    "mlflow_cli",
-    c(
-      "server",
-      args,
-      list(
-        background = getOption("mlflow.ui.background", TRUE)
-      )
-    )
-  )
-
-  tracking_uri <- getOption("mlflow.ui", paste(host, port, sep = ":"))
-  new_mlflow_connection(tracking_uri, handle, file_store = file_store)
-}
-
-#' Connect to MLflow
-#'
-#' Connect to local or remote MLflow instance.
-#'
-#' @param x (Optional) Either a URL to the remote MLflow server or the file store,
-#'   i.e. the root of the backing file store for experiment and run data. If not
-#'   specified, will launch and connect to a local instance listening on a random port.
-#' @param activate Whether to set the connction as the active connection, defaults to `TRUE`.
-#' @param ... Optional arguments passed to `mlflow_server()`.
-#' @export
-mlflow_connect <- function(x = NULL, activate = TRUE, ...) {
-  if (!is.null(x) && startsWith(x, "http")) {
-    mc <- new_mlflow_connection(tracking_uri = x)
-  } else {
-    dots <- list(...)
-    dots[["port"]] <- dots[["port"]] %||% mlflow_connect_port()
-    if (!is.null(dots[["file_store"]]) && !is.null(x))
-      stop("`x` and `file_store` cannot both be specified.", call. = FALSE)
-    dots[["file_store"]] <- dots[["file_store"]] %||% x
-    mc <- do.call(mlflow_server, dots)
-  }
-
-  if (activate) mlflow_set_active_connection(mc)
-
-  mc
-}
-
-new_mlflow_connection <- function(tracking_uri, handle, ...) {
-  mc <- structure(
-    list(
-      tracking_uri = if (startsWith(tracking_uri, "http")) tracking_uri else paste0("http://", tracking_uri),
-      handle = handle,
-      ...
-    ),
-    class = "mlflow_connection"
-  )
-
-  mlflow_connection_wait(mc)
-  mc
-}
-
-#' Disconnect from MLflow
-#'
-#' Disconnects from a local MLflow instance.
-#'
-#' @export
-mlflow_disconnect <- function() {
-  mc <- mlflow_active_connection()
-  if (is.null(mc)) {
-    message("Not connected to an MLflow service.")
-  } else {
-    if (mc$handle$is_alive()) invisible(mc$handle$kill())
-    mlflow_set_active_connection(NULL)
-    mlflow_set_active_experiment(NULL)
-    mlflow_set_active_run(NULL)
-  }
-  invisible(NULL)
-}
-
-mlflow_connection_url <- function(mc) {
-  mc$url
-}
-
-mlflow_connection_wait <- function(mc) {
-  wait_for(
-    function() mlflow_rest(mc = mc, "experiments", "list"),
-    getOption("mlflow.connect.wait", 10),
-    getOption("mlflow.connect.sleep", 1)
-  )
-}
diff --git a/mlflow/R/mlflow/R/entities.R b/mlflow/R/mlflow/R/entities.R
new file mode 100644
index 0000000000000..b9cfc7e973fda
--- /dev/null
+++ b/mlflow/R/mlflow/R/entities.R
@@ -0,0 +1,12 @@
+new_mlflow_entities_run <- function(l) {
+  run <- purrr::compact(l$run)
+  run <- purrr::map_at(run, "info", tidy_run_info)
+
+  structure(
+    list(
+     info = run$info,
+     data = run$data
+    ),
+    class = "mlflow_run"
+  )
+}
diff --git a/mlflow/R/mlflow/R/mlflow.R b/mlflow/R/mlflow/R/mlflow.R
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/mlflow/R/mlflow/R/flavor.R b/mlflow/R/mlflow/R/model-flavor.R
similarity index 90%
rename from mlflow/R/mlflow/R/flavor.R
rename to mlflow/R/mlflow/R/model-flavor.R
index 74c0defc703d5..16918e2894f8d 100644
--- a/mlflow/R/mlflow/R/flavor.R
+++ b/mlflow/R/mlflow/R/model-flavor.R
@@ -20,11 +20,11 @@ mlflow_save_flavor <- function(x, path = "model") {
 #' Loads an MLflow model flavor, to be used by package authors
 #' to extend the supported MLflow models.
 #'
-#' @param flavor_path The path to the MLflow model wrapped in the correct
+#' @param model_path The path to the MLflow model wrapped in the correct
 #'   class.
 #'
 #' @export
-mlflow_load_flavor <- function(flavor_path) {
+mlflow_load_flavor <- function(model_path) {
   UseMethod("mlflow_load_flavor")
 }
 
diff --git a/mlflow/R/mlflow/R/serve.R b/mlflow/R/mlflow/R/model-serve.R
similarity index 100%
rename from mlflow/R/mlflow/R/serve.R
rename to mlflow/R/mlflow/R/model-serve.R
diff --git a/mlflow/R/mlflow/R/swagger.R b/mlflow/R/mlflow/R/model-swagger.R
similarity index 100%
rename from mlflow/R/mlflow/R/swagger.R
rename to mlflow/R/mlflow/R/model-swagger.R
diff --git a/mlflow/R/mlflow/R/model.R b/mlflow/R/mlflow/R/model.R
index cd3aae9ca4deb..1116847a4fad3 100644
--- a/mlflow/R/mlflow/R/model.R
+++ b/mlflow/R/mlflow/R/model.R
@@ -39,6 +39,22 @@ mlflow_save_model <- function(x, path = "model", dependencies = NULL) {
   mlflow_write_model_spec(path, flavor_spec)
 }
 
+#' Log Model
+#'
+#' Logs a model in the given run. Similar to `mlflow_save_model()`
+#' but stores model as an artifact within the active run.
+#'
+#' @param fn The serving function that will perform a prediction.
+#' @param artifact_path Destination path where this MLflow compatible model
+#'   will be saved.
+#'
+#' @export
+mlflow_log_model <- function(fn, artifact_path) {
+  temp_path <- fs::path_temp(artifact_path)
+  mlflow_save_model(fn, path = temp_path)
+  mlflow_log_artifact(path = temp_path, artifact_path = artifact_path)
+}
+
 mlflow_timestamp <- function() {
   withr::with_options(
     c(digits.secs = 2),
@@ -51,7 +67,7 @@ mlflow_timestamp <- function() {
 
 mlflow_write_model_spec <- function(path, content) {
   content$time_created <- mlflow_timestamp()
-  content$run_id <- mlflow_active_run()$run_info$run_uuid
+  content$run_id <- active_run_id()
 
   write_yaml(
     purrr::compact(content),
@@ -65,7 +81,7 @@ mlflow_load_model <- function(model_path) {
   model_flavors <- gsub("^r_", "", names(spec$flavors))
 
   supported <- model_flavors %>%
-    Filter(function(e) paste("mlflow_load_flavor", e, sep = ".") %in% as.vector(methods(class = e)), .)
+    purrr::keep(function(e) paste("mlflow_load_flavor", e, sep = ".") %in% as.vector(utils::methods(class = e)))
 
   if ("crate" %in% model_flavors) {
     supported <- c("crate", supported)
@@ -78,7 +94,7 @@ mlflow_load_model <- function(model_path) {
       paste(model_flavors, collapse = ", "),
       ". Supported flavors: ",
       paste(
-        purrr::map_chr(model_flavors, ~ as.vector(methods(class = .x))),
+        purrr::map_chr(model_flavors, ~ as.vector(utils::methods(class = .x))),
         collapse = ", "
       )
     )
@@ -166,7 +182,6 @@ mlflow_rfunc_predict <- function(
 
 resolve_model_path <- function(model_path, run_uuid) {
   if (!is.null(run_uuid)) {
-    mlflow_get_or_create_active_connection()
     result <- mlflow_cli("artifacts", "download", "--run-id", run_uuid, "-a", model_path, echo = FALSE)
 
     gsub("\n", "", result$stdout)
diff --git a/mlflow/R/mlflow/R/param.R b/mlflow/R/mlflow/R/project-param.R
similarity index 100%
rename from mlflow/R/mlflow/R/param.R
rename to mlflow/R/mlflow/R/project-param.R
diff --git a/mlflow/R/mlflow/R/run.R b/mlflow/R/mlflow/R/project-run.R
similarity index 98%
rename from mlflow/R/mlflow/R/run.R
rename to mlflow/R/mlflow/R/project-run.R
index c26641f9f19bb..c69231bb91495 100644
--- a/mlflow/R/mlflow/R/run.R
+++ b/mlflow/R/mlflow/R/project-run.R
@@ -53,5 +53,5 @@ mlflow_run <- function(entry_point = NULL, uri = ".", version = NULL, param_list
 }
 
 clear_run <- function() {
-  .globals$run_params <- NULL
+  rlang::env_unbind(.globals, "run_params")
 }
diff --git a/mlflow/R/mlflow/R/snapshot.R b/mlflow/R/mlflow/R/project-snapshot.R
similarity index 100%
rename from mlflow/R/mlflow/R/snapshot.R
rename to mlflow/R/mlflow/R/project-snapshot.R
diff --git a/mlflow/R/mlflow/R/source.R b/mlflow/R/mlflow/R/project-source.R
similarity index 100%
rename from mlflow/R/mlflow/R/source.R
rename to mlflow/R/mlflow/R/project-source.R
diff --git a/mlflow/R/mlflow/R/remote.R b/mlflow/R/mlflow/R/remote.R
deleted file mode 100644
index 32c8de0432a6d..0000000000000
--- a/mlflow/R/mlflow/R/remote.R
+++ /dev/null
@@ -1,26 +0,0 @@
-#' Set Remote Tracking URI
-#'
-#' Specifies the URI to the remote MLflow server that will be used
-#' to track experiments.
-#'
-#' @param uri The URI to the remote MLflow server.
-#'
-#' @export
-mlflow_set_tracking_uri <- function(uri) {
-  .globals$tracking_uri <- uri
-  .globals$active_connection <- NULL
-  .globals$active_experiment <- NULL
-  .globals$active_run <- NULL
-
-  invisible(uri)
-}
-
-#' Get Remote Tracking URI
-#'
-#' @export
-mlflow_tracking_uri <- function() {
-  .globals$tracking_uri %||% {
-    env_uri <- Sys.getenv("MLFLOW_TRACKING_URI")
-    if (nchar(env_uri)) env_uri else fs::path_abs("mlruns")
-  }
-}
diff --git a/mlflow/R/mlflow/R/rest.R b/mlflow/R/mlflow/R/rest.R
deleted file mode 100644
index b789b31803838..0000000000000
--- a/mlflow/R/mlflow/R/rest.R
+++ /dev/null
@@ -1,70 +0,0 @@
-mlflow_rest_path <- function(version) {
-  switch(
-    version,
-    "2.0" = "ajax-api/2.0/preview/mlflow"
-  )
-}
-
-mlflow_rest_body <- function(data) {
-  data <- Filter(length, data)
-  paste0(
-    "\"",
-    gsub(
-      "\\\"",
-      "\\\\\"",
-      as.character(
-        jsonlite::toJSON(data, auto_unbox = TRUE)
-      )
-    ),
-    "\""
-  )
-}
-
-#' @importFrom httr add_headers
-mlflow_rest_headers <- function() {
-  add_headers("Content-Type" = "application/json")
-}
-
-#' @importFrom httr timeout
-mlflow_rest_timeout <- function() {
-  timeout(getOption("mlflow.rest.timeout", 1))
-}
-
-#' @importFrom httr content
-#' @importFrom httr GET
-#' @importFrom httr POST
-#' @importFrom jsonlite fromJSON
-#' @importFrom xml2 as_list
-mlflow_rest <- function(..., query = NULL, data = NULL, verb = "GET", version = "2.0") {
-  args <- list(...)
-
-  if (is.null(args$mc)) {
-    mc <- mlflow_get_or_create_active_connection()
-  } else {
-    mc <- args$mc
-    args$mc <- NULL
-  }
-
-  api_url <- file.path(
-    mc$tracking_uri,
-    mlflow_rest_path(version),
-    paste(args, collapse = "/")
-  )
-
-  response <- switch(
-    verb,
-    GET = GET(api_url, query = query, mlflow_rest_timeout()),
-    POST = POST(api_url,
-                body = mlflow_rest_body(data),
-                mlflow_rest_headers(),
-                mlflow_rest_timeout()),
-    stop("Verb '", verb, "' is unsupported.")
-  )
-
-  if (identical(response$status_code, 500L)) {
-    stop(as_list(content(response))$html$body$p[[1]])
-  }
-
-  text <- content(response, "text", encoding = "UTF-8")
-  jsonlite::fromJSON(text)
-}
diff --git a/mlflow/R/mlflow/R/tracking-client.R b/mlflow/R/mlflow/R/tracking-client.R
new file mode 100644
index 0000000000000..e1597d7fca57a
--- /dev/null
+++ b/mlflow/R/mlflow/R/tracking-client.R
@@ -0,0 +1,371 @@
+new_mlflow_client <- function(tracking_uri, server_url = NULL) {
+  structure(
+    list(
+      tracking_uri = tracking_uri,
+      server_url = server_url %||% tracking_uri
+    ),
+    class = "mlflow_client"
+  )
+}
+
+#' Initialize an MLflow client
+#'
+#' @param tracking_uri The tracking URI
+#'
+#' @export
+mlflow_client <- function(tracking_uri = NULL) {
+  tracking_uri <- tracking_uri %||% mlflow_get_tracking_uri()
+  server_url <- if (startsWith(tracking_uri, "http")) {
+    tracking_uri
+  } else if (!is.null(mlflow_local_server(tracking_uri)$server_url)) {
+    mlflow_local_server(tracking_uri)$server_url
+  } else {
+    local_server <- mlflow_server(file_store = tracking_uri, port = mlflow_connect_port())
+    mlflow_register_local_server(tracking_uri = tracking_uri, local_server = local_server)
+    local_server$server_url
+  }
+
+  new_mlflow_client(tracking_uri, server_url = server_url)
+}
+
+#' Create Experiment
+#'
+#' Creates an MLflow experiment.
+#'
+#' @param name The name of the experiment to create.
+#' @param artifact_location Location where all artifacts for this experiment are stored. If
+#'   not provided, the remote server will select an appropriate default.
+#' @template roxlate-client-optional
+#'
+#' @export
+mlflow_create_experiment <- function(name, artifact_location = NULL, client = NULL) {
+  UseMethod("mlflow_create_experiment", client)
+}
+
+#' @export
+mlflow_create_experiment.mlflow_client <- function(name, artifact_location = NULL, client = NULL) {
+  name <- forge::cast_string(name)
+  response <- mlflow_client_create_experiment(client, name, artifact_location)
+  invisible(response$experiment_id)
+}
+
+#' Create Run
+#'
+#' reate a new run within an experiment. A run is usually a single execution of a machine learning or data ETL pipeline.
+#'
+#' MLflow uses runs to track Param, Metric, and RunTag, associated with a single execution.
+#'
+#' @param experiment_id Unique identifier for the associated experiment.
+#' @param user_id User ID or LDAP for the user executing the run.
+#' @param run_name Human readable name for run.
+#' @param source_type Originating source for this run. One of Notebook, Job, Project, Local or Unknown.
+#' @param source_name String descriptor for source. For example, name or description of the notebook, or job name.
+#' @param start_time Unix timestamp of when the run started in milliseconds.
+#' @param source_version Git version of the source code used to create run.
+#' @param entry_point_name Name of the entry point for the run.
+#' @param tags Additional metadata for run in key-value pairs.
+#' @template roxlate-client
+#' @export
+mlflow_create_run <- function(
+  experiment_id, user_id = NULL, run_name = NULL, source_type = NULL,
+  source_name = NULL, entry_point_name = NULL, start_time = NULL, source_version = NULL,
+  tags = NULL, client
+) {
+  tags <- if (!is.null(tags)) tags %>%
+    purrr::imap(~ list(key = .y, value = .x)) %>%
+    unname()
+
+  run <- mlflow_client_create_run(
+    client, experiment_id, user_id, run_name, source_type,
+    source_name, entry_point_name, start_time, source_version, tags
+  )
+  new_mlflow_entities_run(run)
+}
+
+#' Delete Experiment
+#'
+#' Mark an experiment and associated runs, params, metrics, … etc for deletion. If the
+#'   experiment uses FileStore, artifacts associated with experiment are also deleted.
+#'
+#' @param experiment_id ID of the associated experiment This field is required.
+#' @template roxlate-client
+#' @export
+mlflow_delete_experiment <- function(experiment_id, client) {
+  mlflow_client_delete_experiment(client, experiment_id)
+}
+
+#' Restore Experiment
+#'
+#' Restore an experiment marked for deletion. This also restores associated metadata,
+#'   runs, metrics, and params. If experiment uses FileStore, underlying artifacts
+#'   associated with experiment are also restored.
+#'
+#' Throws RESOURCE_DOES_NOT_EXIST if experiment was never created or was permanently deleted.
+#'
+#' @param experiment_id ID of the associated experiment This field is required.
+#' @template roxlate-client
+#' @export
+mlflow_restore_experiment <- function(experiment_id, client) {
+  mlflow_client_restore_experiment(client, experiment_id)
+}
+
+#' Get Run
+#'
+#' Get meta data, params, tags, and metrics for run. Only last logged value for each metric is returned.
+#'
+#' @param run_uuid Unique ID for the run.
+#' @template roxlate-client
+#'
+#' @export
+mlflow_get_run <- function(run_uuid, client) {
+  response <- mlflow_rest("runs", "get", client = client, query = list(run_uuid = run_uuid))
+  new_mlflow_entities_run(response)
+}
+
+#' Log Metric
+#'
+#' API to log a metric for a run. Metrics key-value pair that record a single float measure.
+#'   During a single execution of a run, a particular metric can be logged several times.
+#'   Backend will keep track of historical values along with timestamps.
+#'
+#' @param key Name of the metric.
+#' @param value Float value for the metric being logged.
+#' @param timestamp Unix timestamp in milliseconds at the time metric was logged.
+#' @template roxlate-client-optional
+#' @export
+mlflow_log_metric <- function(key, value, timestamp = NULL, client = NULL, ...) {
+  UseMethod("mlflow_log_metric", client)
+}
+
+#' @rdname mlflow_log_metric
+#' @param run_id Run ID.
+#' @export
+mlflow_log_metric.mlflow_client <- function(
+  key, value, timestamp = NULL, client = NULL, run_id, ...
+) {
+  if (!rlang::inherits_any(value, c("character", "numeric", "integer"))) {
+    stop("Metric ", key, " must be a character or numeric but ", class(value), " found.")
+  }
+  mlflow_client_log_metric(
+    client, run_uuid = run_id, key = key, value = value, timestamp = timestamp
+  )
+}
+
+#' Set Tag
+#'
+#' Set a tag on a run. Tags are run metadata that can be updated during and
+#'  after a run completes.
+#'
+#' @param key Name of the tag. Maximum size is 255 bytes. This field is required.
+#' @param value String value of the tag being logged. Maximum size is 500 bytes. This field is required.
+#' @template roxlate-client-optional
+#' @export
+mlflow_set_tag <- function(key, value, client = NULL, ...) {
+  UseMethod("mlflow_set_tag", client)
+}
+
+#' @rdname mlflow_set_tag
+#' @param run_id Run ID.
+#' @export
+mlflow_set_tag.mlflow_client <- function(key, value, client = NULL, run_id, ...) {
+  mlflow_client_set_tag(client, run_uuid = run_id, key = key, value = value)
+}
+
+#' Log Parameter
+#'
+#' API to log a parameter used for this run. Examples are params and hyperparams
+#'   used for ML training, or constant dates and values used in an ETL pipeline.
+#'   A params is a STRING key-value pair. For a run, a single parameter is allowed
+#'   to be logged only once.
+#'
+#' @param key Name of the parameter.
+#' @param value String value of the parameter.
+#' @template roxlate-client-optional
+#' @export
+mlflow_log_param <- function(key, value, client = NULL, ...) {
+  UseMethod("mlflow_log_param", client)
+}
+
+#' @rdname mlflow_log_param
+#' @param run_id Run ID.
+#' @export
+mlflow_log_param.mlflow_client <- function(
+  key, value, client = NULL, run_id, ...
+) {
+  mlflow_client_log_param(client, run_uuid = run_id, key, value)
+}
+
+#' Get Param
+#'
+#' Get a param value.
+#'
+#' @return The param name and value as a data frame.
+#' @param param_name Name of the param. This field is required.
+#' @param run_id Run ID.
+#' @template roxlate-client
+#' @export
+mlflow_get_param <- function(param_name, client, run_id) {
+  response <- mlflow_client_get_param(client, run_id, param_name)
+  as.data.frame(response$parameter, stringsAsFactors = FALSE)
+}
+
+#' Get Metric
+#'
+#' API to retrieve the logged value for a metric during a run. For a run, if this
+#'   metric is logged more than once, this API will retrieve only the latest value logged.
+#'
+#' @param metric_key Name of the metric.
+#' @param run_id Run ID.
+#' @template roxlate-client
+#' @export
+mlflow_get_metric <- function(metric_key, client, run_id) {
+  response <- mlflow_client_get_metric(client, run_id, metric_key)
+  metric <- response$metric
+  metric$timestamp <- as.POSIXct(as.double(metric$timestamp) / 1000, origin = "1970-01-01")
+  as.data.frame(metric, stringsAsFactors = FALSE)
+}
+
+#' Get Metric History
+#'
+#' For cases that a metric is logged more than once during a run, this API can be used
+#'   to retrieve all logged values for this metric.
+#'
+#' @param metric_key Name of the metric.
+#' @param run_id Run ID.
+#' @template roxlate-client
+#' @export
+mlflow_get_metric_history <- function(metric_key, client, run_id) {
+  response <- mlflow_client_get_metric_history(client, run_id, metric_key)
+  metrics <- response$metrics
+  metrics$timestamp <- as.POSIXct(as.double(metrics$timestamp) / 1000, origin = "1970-01-01")
+  as.data.frame(metrics, stringsAsFactors = FALSE)
+}
+
+#' List Experiments
+#'
+#' Get a list of all experiments.
+#'
+#' @param view_type Qualifier for type of experiments to be returned. Defaults to `ACTIVE_ONLY`.
+#' @template roxlate-client
+#' @export
+mlflow_list_experiments <- function(
+  view_type = c("ACTIVE_ONLY", "DELETED_ONLY", "ALL"), client
+) {
+  view_type <- match.arg(view_type)
+  response <- mlflow_client_list_experiments(client, view_type)
+  exps <- response$experiments
+
+  exps$artifact_location <- mlflow_relative_paths(exps$artifact_location)
+  exps
+}
+
+#' Get Experiment
+#'
+#' Get meta data for experiment and a list of runs for this experiment.
+#'
+#' @param experiment_id Identifer to get an experiment.
+#' @template roxlate-client
+#' @export
+mlflow_get_experiment <- function(experiment_id, client) {
+  mlflow_client_get_experiment(client, experiment_id)
+}
+
+#' Get Experiment by Name
+#'
+#' Get meta data for experiment by name.
+#'
+#' @param name The experiment name.
+#' @template roxlate-client
+#' @export
+mlflow_get_experiment_by_name <- function(name, client) {
+  exps <- mlflow_list_experiments(client = client)
+  experiment <- exps[exps$name == name, ]
+  if (nrow(experiment)) experiment else NULL
+}
+
+#' Terminate a Run
+#'
+#' @param run_id Unique identifier for the run.
+#' @param status Updated status of the run. Defaults to `FINISHED`.
+#' @param end_time Unix timestamp of when the run ended in milliseconds.
+#' @template roxlate-client
+#' @export
+mlflow_set_terminated <- function(
+  run_id, status = c("FINISHED", "SCHEDULED", "FAILED", "KILLED"),
+  end_time = NULL, client
+) {
+  status <- match.arg(status)
+  response <- mlflow_client_update_run(client, run_id, status, end_time)
+  tidy_run_info(response$run_info)
+}
+
+#' Log Artifact
+#'
+#' Logs an specific file or directory as an artifact.
+#'
+#' @param path The file or directory to log as an artifact.
+#' @param artifact_path Destination path within the run’s artifact URI.
+#' @template roxlate-client-optional
+#'
+#' @details
+#'
+#' When logging to Amazon S3, ensure that the user has a proper policy
+#' attach to it, for instance:
+#'
+#' \code{
+#' {
+#' "Version": "2012-10-17",
+#' "Statement": [
+#'   {
+#'     "Sid": "VisualEditor0",
+#'     "Effect": "Allow",
+#'     "Action": [
+#'       "s3:PutObject",
+#'       "s3:GetObject",
+#'       "s3:ListBucket",
+#'       "s3:GetBucketLocation"
+#'       ],
+#'     "Resource": [
+#'       "arn:aws:s3:::mlflow-test/*",
+#'       "arn:aws:s3:::mlflow-test"
+#'       ]
+#'   }
+#'   ]
+#' }
+#' }
+#'
+#' Additionally, at least the \code{AWS_ACCESS_KEY_ID} and \code{AWS_SECRET_ACCESS_KEY}
+#' environment variables must be set to the corresponding key and secrets provided
+#' by Amazon IAM.
+#'
+#' @export
+mlflow_log_artifact <- function(path, artifact_path = NULL, client = NULL, ...) {
+  UseMethod("mlflow_log_artifact")
+}
+
+#' @rdname mlflow_log_artifact
+#' @param run_id The run associated with this artifact.
+#' @export
+mlflow_log_artifact.mlflow_client <- function(path, artifact_path = NULL, client = NULL, run_id = NULL, ...) {
+  artifact_param <- NULL
+  if (!is.null(artifact_path)) artifact_param <- "--artifact-path"
+
+  if (as.logical(fs::is_file(path))) {
+    command <- "log-artifact"
+    local_param <- "--local-file"
+  } else {
+    command <- "log-artifacts"
+    local_param <- "--local-dir"
+  }
+
+  mlflow_cli("artifacts",
+             command,
+             local_param,
+             path,
+             artifact_param,
+             artifact_path,
+             "--run-id",
+             run_id)
+
+  invisible(NULL)
+}
diff --git a/mlflow/R/mlflow/R/tracking-fluent.R b/mlflow/R/mlflow/R/tracking-fluent.R
new file mode 100644
index 0000000000000..3143c1b3dd5b4
--- /dev/null
+++ b/mlflow/R/mlflow/R/tracking-fluent.R
@@ -0,0 +1,144 @@
+#' @export
+mlflow_create_experiment.NULL <- function(name, artifact_location = NULL, client = NULL) {
+  client <- mlflow_client()
+  experiment_id <- mlflow_create_experiment.mlflow_client(name, artifact_location, client)
+  invisible(experiment_id)
+}
+
+#' Set Experiment
+#'
+#' Set given experiment as active experiment. If experiment does not
+#'  exist, create an experiment with provided name.
+#'
+#' @param experiment_name Name of experiment to be activated.
+#' @export
+mlflow_set_experiment <- function(experiment_name) {
+  client <- mlflow_client()
+  experiment <- mlflow_get_experiment_by_name(client = client, experiment_name)
+  exp_id <- if (!is.null(experiment)) {
+    experiment$experiment_id
+  } else {
+    message("`", experiment_name, "` does not exist. Creating a new experiment.")
+    mlflow_create_experiment(client = client, name = experiment_name)
+  }
+  mlflow_set_active_experiment_id(exp_id)
+}
+
+#' Start Run
+#'
+#' Starts a new run within an experiment, should be used within a \code{with} block.
+#'
+#' @param run_uuid If specified, get the run with the specified UUID and log metrics
+#'   and params under that run. The run's end time is unset and its status is set to
+#'   running, but the run's other attributes remain unchanged.
+#' @param experiment_id Used only when ``run_uuid`` is unspecified. ID of the experiment under
+#'   which to create the current run. If unspecified, the run is created under
+#'   a new experiment with a randomly generated name.
+#' @param source_name Name of the source file or URI of the project to be associated with the run.
+#'   Defaults to the current file if none provided.
+#' @param source_version Optional Git commit hash to associate with the run.
+#' @param entry_point_name Optional name of the entry point for to the current run.
+#' @param source_type Integer enum value describing the type of the run  ("local", "project", etc.).
+#'
+#' @examples
+#' \dontrun{
+#' with(mlflow_start_run(), {
+#'   mlflow_log("test", 10)
+#' })
+#' }
+#'
+#' @export
+mlflow_start_run <- function(run_uuid = NULL, experiment_id = NULL, source_name = NULL,
+                             source_version = NULL, entry_point_name = NULL,
+                             source_type = "LOCAL") {
+  active_run <- mlflow_active_run()
+  if (!is.null(active_run)) {
+    stop("Run with UUID ", active_run_id(), " is already active.",
+         call. = FALSE)
+  }
+
+  existing_run_uuid <- run_uuid %||% {
+    env_run_id <- Sys.getenv("MLFLOW_RUN_ID")
+    if (nchar(env_run_id)) env_run_id
+  }
+
+  run <- if (!is.null(existing_run_uuid)) {
+    client <- mlflow_client()
+    mlflow_get_run(client = client, existing_run_uuid)
+  } else {
+    experiment_id <- as.integer(
+      experiment_id %||% mlflow_get_active_experiment_id() %||% Sys.getenv("MLFLOW_EXPERIMENT_ID", unset = "0")
+    )
+
+    client <- mlflow_client()
+
+    mlflow_create_run(
+      client = client,
+      experiment_id = experiment_id,
+      source_name = source_name %||% get_source_name(),
+      source_version = source_version %||% get_source_version(),
+      entry_point_name = entry_point_name,
+      source_type = source_type
+    )
+  }
+
+  mlflow_set_active_run(run)
+}
+
+#' @rdname mlflow_log_metric
+#' @export
+mlflow_log_metric.NULL <- function(
+  key, value, timestamp = NULL, client = NULL, ...
+) {
+  active_run <- mlflow_get_or_start_run()
+  client <- mlflow_client()
+  mlflow_log_metric.mlflow_client(
+    client = client, key = key, value = value, timestamp = timestamp,
+    run_id = run_id(active_run)
+  )
+  invisible(value)
+}
+
+#' @rdname mlflow_set_tag
+#' @export
+mlflow_set_tag.NULL <- function(key, value, client = NULL, ...) {
+  active_run <- mlflow_get_or_start_run()
+  client <- mlflow_client()
+  mlflow_set_tag.mlflow_client(
+    key = key, value = value, client = client, run_id = run_id(active_run)
+  )
+}
+
+#' End a Run
+#'
+#' End an active MLflow run (if there is one).
+#'
+#' @param status Updated status of the run. Defaults to `FINISHED`.
+#' @export
+mlflow_end_run <- function(status = c("FINISHED", "SCHEDULED", "FAILED", "KILLED")) {
+  active_run <- mlflow_active_run()
+  if (!is.null(active_run)) {
+    client <- mlflow_client()
+    mlflow_set_terminated(run_id(active_run), status, client = client)
+    mlflow_set_active_run(NULL)
+  }
+  invisible(NULL)
+}
+
+#' @rdname mlflow_log_param
+#' @export
+mlflow_log_param.NULL <- function(key, value, client = NULL, ...) {
+  active_run <- mlflow_get_or_start_run()
+  client <- mlflow_client()
+  mlflow_log_param.mlflow_client(key, value, client, run_id(active_run))
+  invisible(value)
+}
+
+
+#' @rdname mlflow_log_artifact
+#' @export
+mlflow_log_artifact.NULL <- function(path, artifact_path = NULL, client = NULL, ...) {
+  active_run <- mlflow_get_or_start_run()
+  client <- mlflow_client()
+  mlflow_log_artifact.mlflow_client(path, artifact_path, client, run_id(active_run))
+}
diff --git a/mlflow/R/mlflow/R/tracking-globals.R b/mlflow/R/mlflow/R/tracking-globals.R
new file mode 100644
index 0000000000000..bf4e4520be908
--- /dev/null
+++ b/mlflow/R/mlflow/R/tracking-globals.R
@@ -0,0 +1,48 @@
+#' Active Run
+#'
+#' Retrieves the active run.
+#'
+#' @export
+mlflow_active_run <- function() {
+  .globals$active_run
+}
+
+mlflow_set_active_run <- function(run) {
+  .globals$active_run <- run
+  invisible(run)
+}
+
+mlflow_get_active_experiment_id <- function() {
+  .globals$active_experiment_id
+}
+
+mlflow_set_active_experiment_id <- function(experiment_id) {
+  if (!identical(experiment_id, .globals$active_experiment_id)) {
+    .globals$active_experiment_id <- experiment_id
+  }
+
+  invisible(experiment_id)
+}
+
+#' Set Remote Tracking URI
+#'
+#' Specifies the URI to the remote MLflow server that will be used
+#' to track experiments.
+#'
+#' @param uri The URI to the remote MLflow server.
+#'
+#' @export
+mlflow_set_tracking_uri <- function(uri) {
+  .globals$tracking_uri <- uri
+  invisible(uri)
+}
+
+#' Get Remote Tracking URI
+#'
+#' @export
+mlflow_get_tracking_uri <- function() {
+  .globals$tracking_uri %||% {
+    env_uri <- Sys.getenv("MLFLOW_TRACKING_URI")
+    if (nchar(env_uri)) env_uri else fs::path_abs("mlruns")
+  }
+}
diff --git a/mlflow/R/mlflow/R/tracking-rest.R b/mlflow/R/mlflow/R/tracking-rest.R
index 4c13b4cbc563d..24fe63854acd5 100644
--- a/mlflow/R/mlflow/R/tracking-rest.R
+++ b/mlflow/R/mlflow/R/tracking-rest.R
@@ -1,296 +1,187 @@
-#' List Experiments
-#'
-#' Retrieves MLflow experiments as a data frame.
-#'
-#' @examples
-#' \dontrun{
-#' library(mlflow)
-#' mlflow_install()
-#'
-#' # list local experiments
-#' mlflow_list_experiments()
-#'
-#' # list experiments in remote MLflow server
-#' mlflow_set_tracking_uri("http://tracking-server:5000")
-#' mlflow_list_experiments()
-#' }
-#'
-#' @export
-mlflow_list_experiments <- function() {
-  response <- mlflow_rest("experiments", "list")
-  exps <- response$experiments
+mlflow_rest_path <- function(version) {
+  switch(
+    version,
+    "2.0" = "ajax-api/2.0/preview/mlflow"
+  )
+}
 
-  exps$artifact_location <- mlflow_relative_paths(exps$artifact_location)
-  exps
+mlflow_rest_body <- function(data) {
+  data <- Filter(length, data)
+  paste0(
+    "\"",
+    gsub(
+      "\\\"",
+      "\\\\\"",
+      as.character(
+        jsonlite::toJSON(data, auto_unbox = TRUE)
+      )
+    ),
+    "\""
+  )
 }
 
-#' Create Experiment
-#'
-#' Creates an MLflow experiment.
-#'
-#' @param name The name of the experiment to create.
-#' @param activate Whether to set the created experiment as the active experiment. Defaults to `TRUE`.
-#' @param artifact_location Location where all artifacts for this experiment are stored. If
-#'   not provided, the remote server will select an appropriate default.
-#'
-#' @examples
-#' \dontrun{
-#' library(mlflow)
-#' mlflow_install()
-#'
-#' # create local experiment
-#' mlflow_create_experiment("My Experiment")
-#'
-#' # create experiment in remote MLflow server
-#' mlflow_set_tracking_uri("http://tracking-server:5000")
-#' mlflow_create_experiment("My Experiment")
-#' }
-#'
-#' @export
-mlflow_create_experiment <- function(name, artifact_location = NULL, activate = TRUE) {
-  experiments <- mlflow_list_experiments()
-  experiment_id <- if (name %in% experiments$name) {
-    message("Experiment with name \"", name, "\" already exists.")
-    experiments[experiments$name == name, ]$experiment_id
-  } else {
-    response <- mlflow_rest(
-      "experiments", "create", verb = "POST",
-      data = list(name = name, artifact_location = artifact_location)
-    )
-    response$experiment_id
+#' @importFrom httr add_headers
+mlflow_rest_headers <- function() {
+  add_headers("Content-Type" = "application/json")
+}
+
+#' @importFrom httr timeout
+mlflow_rest_timeout <- function() {
+  timeout(getOption("mlflow.rest.timeout", 1))
+}
+
+#' @importFrom httr content
+#' @importFrom httr GET
+#' @importFrom httr POST
+#' @importFrom jsonlite fromJSON
+#' @importFrom xml2 as_list
+mlflow_rest <- function(..., client, query = NULL, data = NULL, verb = "GET", version = "2.0") {
+  args <- list(...)
+  tracking_url <- client$server_url
+
+  api_url <- file.path(
+    tracking_url,
+    mlflow_rest_path(version),
+    paste(args, collapse = "/")
+  )
+
+  response <- switch(
+    verb,
+    GET = GET(api_url, query = query, mlflow_rest_timeout()),
+    POST = POST(
+      api_url,
+      body = mlflow_rest_body(data),
+      mlflow_rest_headers(),
+      mlflow_rest_timeout()
+    ),
+    stop("Verb '", verb, "' is unsupported.")
+  )
+
+  if (identical(response$status_code, 500L)) {
+    stop(as_list(content(response))$html$body$p[[1]])
   }
 
-  if (activate) mlflow_set_active_experiment(experiment_id)
-  invisible(experiment_id)
+  text <- content(response, "text", encoding = "UTF-8")
+  jsonlite::fromJSON(text)
 }
 
-#' Get Experiment
-#'
-#' Get meta data for experiment and a list of runs for this experiment.
-#'
-#' @param experiment_id Identifer to get an experiment.
-#' @export
-mlflow_get_experiment <- function(experiment_id) {
-  response <- mlflow_rest("experiments", "get", query = list(experiment_id = experiment_id))
-  response
+
+mlflow_client_create_experiment <- function(client, name, artifact_location) {
+  mlflow_rest(
+    "experiments", "create", client = client, verb = "POST",
+    data = list(
+      name = name,
+      artifact_location = artifact_location
+    )
+  )
 }
 
-#' Create Run
-#'
-#' reate a new run within an experiment. A run is usually a single execution of a machine learning or data ETL pipeline.
-#'
-#' MLflow uses runs to track Param, Metric, and RunTag, associated with a single execution.
-#'
-#' @param experiment_id Unique identifier for the associated experiment.
-#' @param user_id User ID or LDAP for the user executing the run.
-#' @param run_name Human readable name for run.
-#' @param source_type Originating source for this run. One of Notebook, Job, Project, Local or Unknown.
-#' @param source_name String descriptor for source. For example, name or description of the notebook, or job name.
-#' @param status Current status of the run. One of RUNNING, SCHEDULE, FINISHED, FAILED, KILLED.
-#' @param start_time Unix timestamp of when the run started in milliseconds.
-#' @param end_time Unix timestamp of when the run ended in milliseconds.
-#' @param source_version Git version of the source code used to create run.
-#' @param entry_point_name Name of the entry point for the run.
-#' @param tags Additional metadata for run in key-value pairs.
-#' @export
-mlflow_create_run <- function(user_id = mlflow_user(),
-                              run_name = NULL, source_type = NULL, source_name = NULL,
-                              status = NULL, start_time = NULL, end_time = NULL,
-                              source_version = NULL, entry_point_name = NULL,
-                              tags = NULL, experiment_id = NULL) {
-  experiment_id <- experiment_id %||% mlflow_active_experiment()
-  start_time <- start_time %||% current_time()
+mlflow_client_list_experiments <- function(client, view_type) {
+  mlflow_rest(
+    "experiments", "list", client = client, verb = "GET",
+    query = list(
+      view_type = view_type
+    ))
+}
 
-  tags <- if (!is.null(tags)) tags %>%
-    purrr::imap(~ list(key = .y, value = .x)) %>%
-    unname()
+mlflow_client_get_experiment <- function(client, experiment_id) {
+  mlflow_rest(
+    "experiments", "get", client = client,
+    query = list(experiment_id = experiment_id)
+  )
+}
 
-  response <- mlflow_rest("runs", "create", verb = "POST", data = list(
-    experiment_id = experiment_id,
-    user_id = user_id,
-    run_name = run_name,
-    source_type = source_type,
-    source_name = source_name,
+mlflow_client_create_run <- function(
+  client, experiment_id, user_id, run_name, source_type,
+  source_name, entry_point_name, start_time, source_version, tags
+) {
+  mlflow_rest(
+    "runs", "create", client = client, verb = "POST",
+    data = list(
+      experiment_id = experiment_id,
+      user_id = user_id,
+      run_name = run_name,
+      source_type = source_type,
+      source_name = source_name,
+      entry_point_name = entry_point_name,
+      start_time = start_time,
+      source_version = source_version,
+      tags = tags
+    )
+  )
+}
+
+mlflow_client_update_run <- function(client, run_uuid, status, end_time) {
+  mlflow_rest("runs", "update", client = client, verb = "POST", data = list(
+    run_uuid = run_uuid,
     status = status,
-    start_time = start_time,
-    end_time = end_time,
-    source_version = source_version,
-    entry_point_name = entry_point_name,
-    tags = tags
+    end_time = end_time
   ))
+}
 
-  tidy_run_info(response$run$info)
+mlflow_client_delete_experiment <- function(client, experiment_id) {
+  mlflow_rest(
+    "experiments", "delete", client = client, verb = "POST",
+    data = list(experiment_id = experiment_id),
+  )
 }
 
-#' Get Run
-#'
-#' Get meta data, params, tags, and metrics for run. Only last logged value for each metric is returned.
-#'
-#' @param run_uuid Unique ID for the run.
-#'
-#' @export
-mlflow_get_run <- function(run_uuid) {
-  response <- mlflow_rest("runs", "get", query = list(run_uuid = run_uuid))
-  run <- purrr::compact(response$run)
-  run %>%
-    purrr::map_at("info", tidy_run_info)
+mlflow_client_restore_experiment <- function(client, experiment_id) {
+  mlflow_rest(
+    "experiments", "restore", client = client, verb = "POST",
+    data = list(experiment_id = experiment_id),
+  )
 }
 
-#' Log Metric
-#'
-#' API to log a metric for a run. Metrics key-value pair that record a single float measure.
-#'   During a single execution of a run, a particular metric can be logged several times.
-#'   Backend will keep track of historical values along with timestamps.
-#'
-#' @param run_uuid Unique ID for the run.
-#' @param key Name of the metric.
-#' @param value Float value for the metric being logged.
-#' @param timestamp Unix timestamp in milliseconds at the time metric was logged.
-#' @export
-mlflow_log_metric <- function(key, value, timestamp = NULL, run_uuid = NULL) {
-  if (!rlang::inherits_any(value, c("character", "numeric", "integer"))) {
-    stop("Metric ", key, " must be a character or numeric but ", class(value), " found.")
-  }
+mlflow_client_get_run <- function(client, run_uuid) {
+  mlflow_rest(
+    "runs", "get", client = client, verb = "GET",
+    query = list(run_uuid = run_uuid),
+  )
+}
 
-  run_uuid <- mlflow_ensure_run_id(run_uuid)
-  timestamp <- timestamp %||% current_time()
-  response <- mlflow_rest("runs", "log-metric", verb = "POST", data = list(
+mlflow_client_log_metric <- function(client, run_uuid, key, value, timestamp) {
+  mlflow_rest("runs", "log-metric", client = client, verb = "POST", data = list(
     run_uuid = run_uuid,
     key = key,
     value = value,
     timestamp = timestamp
   ))
-
-  invisible(value)
 }
 
-#' Log Parameter
-#'
-#' API to log a parameter used for this run. Examples are params and hyperparams
-#'   used for ML training, or constant dates and values used in an ETL pipeline.
-#'   A params is a STRING key-value pair. For a run, a single parameter is allowed
-#'   to be logged only once.
-#'
-#' @param run_uuid Unique ID for the run for which parameter is recorded.
-#' @param key Name of the parameter.
-#' @param value String value of the parameter.
-#' @export
-mlflow_log_param <- function(key, value, run_uuid = NULL) {
-  run_uuid <- mlflow_ensure_run_id(run_uuid)
-  response <- mlflow_rest("runs", "log-parameter", verb = "POST", data = list(
+mlflow_client_set_tag <- function(client, run_uuid, key, value) {
+  mlflow_rest("runs", "set-tag", client = client, verb = "POST", data = list(
     run_uuid = run_uuid,
     key = key,
-    value = as.character(value)
+    value = value
   ))
-
-  invisible(value)
 }
 
-#' Get Param
-#'
-#' Get a param value.
-#'
-#' @return The param value as a named list.
-#' @param run_uuid ID of the run from which to retrieve the param value.
-#' @param param_name Name of the param. This field is required.
-#' @export
-mlflow_get_param <- function(param_name, run_uuid = NULL) {
-  mlflow_get_or_create_active_connection()
-  run_uuid <- run_uuid %||%
-    mlflow_active_run()$run_info$run_uuid %||%
-    stop("`run_uuid` must be specified when there is no active run.")
-
-  response <- mlflow_rest("params", "get", query = list(
+mlflow_client_log_param <- function(client, run_uuid, key, value) {
+  mlflow_rest("runs", "log-parameter", client = client, verb = "POST", data = list(
     run_uuid = run_uuid,
-    param_name = param_name
+    key = key,
+    value = cast_string(value)
   ))
-
-  as.data.frame(response$parameter, stringsAsFactors = FALSE)
 }
 
-#' Get Metric
-#'
-#' API to retrieve the logged value for a metric during a run. For a run, if this
-#'   metric is logged more than once, this API will retrieve only the latest value logged.
-#'
-#' @param run_uuid Unique ID for the run for which metric is recorded.
-#' @param metric_key Name of the metric.
-#' @export
-mlflow_get_metric <- function(metric_key, run_uuid = NULL) {
-  mlflow_get_or_create_active_connection()
-  run_uuid <- run_uuid %||%
-    mlflow_active_run()$run_info$run_uuid %||%
-    stop("`run_uuid` must be specified when there is no active run.")
-
-  response <- mlflow_rest("metrics", "get", query = list(
+mlflow_client_get_param <- function(client, run_uuid, param_name) {
+  mlflow_rest("params", "get", client = client, query = list(
     run_uuid = run_uuid,
-    metric_key = metric_key
+    param_name = param_name
   ))
-
-  metric <- response$metric
-  metric$timestamp <- as.POSIXct(as.double(metric$timestamp) / 1000, origin = "1970-01-01")
-  as.data.frame(metric, stringsAsFactors = FALSE)
 }
 
-#' Get Metric History
-#'
-#' For cases that a metric is logged more than once during a run, this API can be used
-#'   to retrieve all logged values for this metric.
-#'
-#' @param run_uuid Unique ID for the run for which metric is recorded.
-#' @param metric_key Name of the metric.
-#' @export
-mlflow_get_metric_history <- function(metric_key, run_uuid = NULL) {
-  mlflow_get_or_create_active_connection()
-  run_uuid <- run_uuid %||%
-    mlflow_active_run()$run_info$run_uuid %||%
-    stop("`run_uuid` must be specified when there is no active run.")
-
-  response <- mlflow_rest("metrics", "get-history", query = list(
+mlflow_client_get_metric <- function(client, run_uuid, metric_key) {
+  mlflow_rest("metrics", "get", client = client, query = list(
     run_uuid = run_uuid,
     metric_key = metric_key
   ))
-
-  metrics <- response$metrics
-  metrics$timestamp <- as.POSIXct(as.double(metrics$timestamp) / 1000, origin = "1970-01-01")
-  as.data.frame(metrics, stringsAsFactors = FALSE)
 }
 
-#' Update Run
-#'
-#' @param run_uuid Unique identifier for the run.
-#' @param status Updated status of the run. Defaults to `FINISHED`.
-#' @param end_time Unix timestamp of when the run ended in milliseconds.
-#' @export
-mlflow_update_run <- function(status = c("FINISHED", "SCHEDULED", "FAILED", "KILLED"),
-                              end_time = NULL,
-                              run_uuid = NULL) {
-  mlflow_get_or_create_active_connection()
-  run_uuid <- run_uuid %||%
-    mlflow_active_run()$run_info$run_uuid %||%
-    stop("`run_uuid` must be specified when there is no active run.")
-
-  status <- match.arg(status)
-  end_time <- end_time %||% current_time()
-
-  response <- mlflow_rest("runs", "update", verb = "POST", data = list(
+mlflow_client_get_metric_history <- function(client, run_uuid, metric_key) {
+  mlflow_rest("metrics", "get-history", client = client, query = list(
     run_uuid = run_uuid,
-    status = status,
-    end_time = end_time
+    metric_key = metric_key
   ))
-
-  tidy_run_info(response$run_info)
-}
-
-current_time <- function() {
-  round(as.numeric(Sys.time()) * 1000)
-}
-
-milliseconds_to_date <- function(x) as.POSIXct(as.double(x) / 1000, origin = "1970-01-01")
-
-tidy_run_info <- function(run_info) {
-  df <- as.data.frame(run_info, stringsAsFactors = FALSE)
-  df$start_time <- milliseconds_to_date(df$start_time %||% NA)
-  df$end_time <- milliseconds_to_date(df$end_time %||% NA)
-  df
 }
diff --git a/mlflow/R/mlflow/R/tracking-server.R b/mlflow/R/mlflow/R/tracking-server.R
new file mode 100644
index 0000000000000..3ae716f2f6391
--- /dev/null
+++ b/mlflow/R/mlflow/R/tracking-server.R
@@ -0,0 +1,113 @@
+#' @importFrom httpuv startDaemonizedServer
+#' @importFrom httpuv stopServer
+mlflow_port_available <- function(port) {
+  tryCatch({
+    handle <- httpuv::startDaemonizedServer("127.0.0.1", port, list())
+    httpuv::stopServer(handle)
+    TRUE
+  }, error = function(e) {
+    FALSE
+  })
+}
+
+#' @importFrom openssl rand_num
+mlflow_connect_port <- function() {
+  port <- getOption(
+    "mlflow.port",
+    NULL
+  )
+
+  retries <- getOption("mlflow.port.retries", 10)
+  while (is.null(port) && retries > 0) {
+    port <- floor(5000 + rand_num(1) * 1000)
+    if (!mlflow_port_available(port)) {
+      port <- NULL
+    }
+
+    retries <- retries - 1
+  }
+
+  port
+}
+
+mlflow_cli_param <- function(args, param, value) {
+  if (!is.null(value)) {
+    args <- c(
+      args,
+      param,
+      value
+    )
+  }
+
+  args
+}
+
+#' Run the MLflow Tracking Server
+#'
+#' Wrapper for `mlflow server`.
+#'
+#' @param file_store The root of the backing file store for experiment and run data.
+#' @param default_artifact_root Local or S3 URI to store artifacts in, for newly created experiments.
+#' @param host The network address to listen on (default: 127.0.0.1).
+#' @param port The port to listen on (default: 5000).
+#' @param workers Number of gunicorn worker processes to handle requests (default: 4).
+#' @param static_prefix A prefix which will be prepended to the path of all static paths.
+#' @export
+mlflow_server <- function(file_store = "mlruns", default_artifact_root = NULL,
+                          host = "127.0.0.1", port = 5000, workers = 4, static_prefix = NULL) {
+  file_store <- fs::path_abs(file_store)
+
+  args <- mlflow_cli_param(list(), "--port", port) %>%
+    mlflow_cli_param("--file-store", file_store) %>%
+    mlflow_cli_param("--default-artifact-root", default_artifact_root) %>%
+    mlflow_cli_param("--host", host) %>%
+    mlflow_cli_param("--port", port) %>%
+    mlflow_cli_param("--workers", workers) %>%
+    mlflow_cli_param("--static-prefix", static_prefix)
+
+  mlflow_verbose_message("MLflow starting: http://", host, ":", port)
+
+  handle <- do.call(
+    "mlflow_cli",
+    c(
+      "server",
+      args,
+      list(
+        background = getOption("mlflow.ui.background", TRUE)
+      )
+    )
+  )
+
+  server_url <- getOption("mlflow.ui", paste(host, port, sep = ":"))
+  new_mlflow_server(server_url, handle, file_store = file_store)
+}
+
+new_mlflow_server <- function(server_url, handle, ...) {
+  ms <- structure(
+    list(
+      server_url = if (startsWith(server_url, "http")) server_url else paste0("http://", server_url),
+      handle = handle,
+      ...
+    ),
+    class = "mlflow_server"
+  )
+
+  mlflow_validate_server(ms)
+  ms
+}
+
+mlflow_validate_server <- function(ms) {
+  wait_for(
+    function() mlflow_rest(client = ms, "experiments", "list"),
+    getOption("mlflow.connect.wait", 10),
+    getOption("mlflow.connect.sleep", 1)
+  )
+}
+
+mlflow_register_local_server <- function(tracking_uri, local_server) {
+  .globals$url_mapping[[tracking_uri]] <- local_server
+}
+
+mlflow_local_server <- function(tracking_uri) {
+  .globals$url_mapping[[tracking_uri]]
+}
diff --git a/mlflow/R/mlflow/R/tracking-ui.R b/mlflow/R/mlflow/R/tracking-ui.R
new file mode 100644
index 0000000000000..b42843afbdc64
--- /dev/null
+++ b/mlflow/R/mlflow/R/tracking-ui.R
@@ -0,0 +1,41 @@
+#' @importFrom utils browseURL
+mlflow_view_url <- function(url) {
+  getOption("page_viewer", browseURL)(url)
+
+  invisible(url)
+}
+
+#' MLflow User Interface
+#'
+#' Launches MLflow user interface.
+#'
+#' @examples
+#' \dontrun{
+#' library(mlflow)
+#' mlflow_install()
+#'
+#' # launch mlflow ui locally
+#' mlflow_ui()
+#'
+#' # launch mlflow ui for existing mlflow server
+#' mlflow_set_tracking_uri("http://tracking-server:5000")
+#' mlflow_ui()
+#' }
+#'
+#' @param x An `mlflow_client` object.
+#' @param ... Optional arguments passed to `mlflow_server()` when `x` is a path to a file store.
+#' @export
+mlflow_ui <- function(x, ...) {
+  UseMethod("mlflow_ui")
+}
+
+#' @export
+mlflow_ui.mlflow_client <- function(x, ...) {
+  mlflow_view_url(x$server_url)
+}
+
+#' @export
+mlflow_ui.NULL <- function(x, ...) {
+  client <- mlflow_client()
+  mlflow_ui(client)
+}
diff --git a/mlflow/R/mlflow/R/tracking-utils.R b/mlflow/R/mlflow/R/tracking-utils.R
new file mode 100644
index 0000000000000..bca4efb51a809
--- /dev/null
+++ b/mlflow/R/mlflow/R/tracking-utils.R
@@ -0,0 +1,88 @@
+mlflow_relative_paths <- function(paths) {
+  gsub(paste0("^", file.path(getwd(), "")), "", paths)
+}
+
+get_executing_file_name <- function() {
+  pattern <- "^--file="
+  v <- grep(pattern, commandArgs(), value = TRUE)
+  file_name <- gsub(pattern, "", v)
+  if (length(file_name)) file_name
+}
+
+get_source_name <- function() {
+  get_executing_file_name() %||% "<console>"
+}
+
+get_source_version <- function() {
+  file_name <- get_executing_file_name()
+  tryCatch(
+    error = function(cnd) NULL,
+    {
+      repo <- git2r::repository(file_name, discover = TRUE)
+      commit <- git2r::commits(repo, n = 1)
+      commit[[1]]@sha
+    }
+  )
+}
+
+mlflow_get_or_start_run <- function() {
+  mlflow_active_run() %||% mlflow_start_run()
+}
+
+#' @export
+with.mlflow_active_run <- function(data, expr, ...) {
+
+  tryCatch(
+    {
+      force(expr)
+      mlflow_end_run()
+    },
+    error = function(cnd) {
+      message(cnd)
+      mlflow_end_run(status = "FAILED")
+    },
+    interrupt = function(cnd) mlflow_end_run(status = "KILLED")
+  )
+
+  invisible(NULL)
+}
+
+
+run_id <- function(run) cast_nullable_string(run$info$run_uuid)
+
+active_run_id <- function() run_id(mlflow_active_run())
+
+current_time <- function() {
+  round(as.numeric(Sys.time()) * 1000)
+}
+
+milliseconds_to_date <- function(x) as.POSIXct(as.double(x) / 1000, origin = "1970-01-01")
+
+tidy_run_info <- function(run_info) {
+  df <- as.data.frame(run_info, stringsAsFactors = FALSE)
+  df$start_time <- milliseconds_to_date(df$start_time %||% NA)
+  df$end_time <- milliseconds_to_date(df$end_time %||% NA)
+  df
+}
+
+wait_for <- function(f, wait, sleep) {
+  command_start <- Sys.time()
+
+  success <- FALSE
+  while (!success && Sys.time() < command_start + wait) {
+    success <- suppressWarnings({
+      tryCatch({
+        f()
+        TRUE
+      }, error = function(err) {
+        FALSE
+      })
+    })
+
+    if (!success) Sys.sleep(sleep)
+  }
+
+  if (!success) {
+    stop("Operation failed after waiting for ", wait, " seconds")
+  }
+}
diff --git a/mlflow/R/mlflow/R/tracking.R b/mlflow/R/mlflow/R/tracking.R
deleted file mode 100644
index 945cb2bcf3933..0000000000000
--- a/mlflow/R/mlflow/R/tracking.R
+++ /dev/null
@@ -1,199 +0,0 @@
-#' Active Experiment
-#'
-#' Retrieve or set the active experiment.
-#'
-#' @name active_experiment
-#' @export
-mlflow_active_experiment <- function() {
-  .globals$active_experiment
-}
-
-#' @rdname active_experiment
-#' @param experiment_id Identifer to get an experiment.
-#' @export
-mlflow_set_active_experiment <- function(experiment_id) {
-  if (!identical(experiment_id, .globals$active_experiment)) {
-    .globals$active_experiment <- experiment_id
-    mlflow_set_active_run(NULL)
-  }
-
-  invisible(experiment_id)
-}
-
-#' Active Run
-#'
-#' Retrieves or sets the active run.
-#'
-#' @name active_run
-#' @export
-mlflow_active_run <- function() {
-  .globals$active_run
-}
-
-#' @rdname active_run
-#' @param run The run object to make active.
-#' @export
-mlflow_set_active_run <- function(run) {
-  .globals$active_run <- run
-  invisible(run)
-}
-
-mlflow_relative_paths <- function(paths) {
-  gsub(paste0("^", file.path(getwd(), "")), "", paths)
-}
-
-#' Start Run
-#'
-#' Starts a new run within an experiment, should be used within a \code{with} block.
-#'
-#' @param run_uuid If specified, get the run with the specified UUID and log metrics
-#'   and params under that run. The run's end time is unset and its status is set to
-#'   running, but the run's other attributes remain unchanged.
-#' @param experiment_id Used only when ``run_uuid`` is unspecified. ID of the experiment under
-#'   which to create the current run. If unspecified, the run is created under
-#'   a new experiment with a randomly generated name.
-#' @param source_name Name of the source file or URI of the project to be associated with the run.
-#'   Defaults to the current file if none provided.
-#' @param source_version Optional Git commit hash to associate with the run.
-#' @param entry_point_name Optional name of the entry point for to the current run.
-#' @param source_type Integer enum value describing the type of the run  ("local", "project", etc.).
-#'
-#' @examples
-#' \dontrun{
-#' with(mlflow_start_run(), {
-#'   mlflow_log("test", 10)
-#' })
-#' }
-#'
-#' @export
-mlflow_start_run <- function(run_uuid = NULL, experiment_id = NULL, source_name = NULL,
-                             source_version = NULL, entry_point_name = NULL,
-                             source_type = "LOCAL") {
-  active_run <- mlflow_active_run()
-  if (!is.null(active_run)) {
-    stop("Run with UUID ", active_run$run_info$run_uuid, " is already active.",
-         call. = FALSE)
-  }
-
-  existing_run_uuid <- run_uuid %||% {
-    env_run_id <- Sys.getenv("MLFLOW_RUN_ID")
-    if (nchar(env_run_id)) env_run_id
-  }
-
-  run_info <- if (!is.null(existing_run_uuid)) {
-    mlflow_get_run(existing_run_uuid)$info
-  } else {
-    experiment_id <- as.integer(
-      experiment_id %||% mlflow_active_experiment() %||% Sys.getenv("MLFLOW_EXPERIMENT_ID", unset = "0")
-    )
-
-    mlflow_create_run(
-      experiment_id = experiment_id,
-      source_name = source_name %||% get_source_name(),
-      source_version = source_version %||% get_source_version(),
-      entry_point_name = entry_point_name,
-      source_type = source_type
-    )
-  }
-
-  new_mlflow_active_run(run_info)
-}
-
-get_executing_file_name <- function() {
-  pattern <- "^--file="
-  v <- grep(pattern, commandArgs(), value = TRUE)
-  file_name <- gsub(pattern, "", v)
-  if (length(file_name)) file_name
-}
-
-get_source_name <- function() {
-  get_executing_file_name() %||% "<console>"
-}
-
-get_source_version <- function() {
-  file_name <- get_executing_file_name()
-  tryCatch(
-    error = function(cnd) NULL,
-    {
-      repo <- git2r::repository(file_name, discover = TRUE)
-      commit <- git2r::commits(repo, n = 1)
-      commit[[1]]@sha
-    }
-  )
-}
-
-#' End Run
-#'
-#' End the active run.
-#'
-#' @param status Ending status of the run, defaults to `FINISHED`.
-#' @export
-mlflow_end_run <- function(status = "FINISHED") {
-  if (!is.null(mlflow_active_run())) {
-    mlflow_update_run(status = status)
-    mlflow_set_active_run(NULL)
-  }
-  invisible(NULL)
-}
-
-new_mlflow_active_run <- function(run_info) {
-  run <- structure(
-    list(run_info = run_info),
-    class = c("mlflow_active_run")
-  )
-  mlflow_set_active_run(run)
-  run
-}
-
-#' @export
-with.mlflow_active_run <- function(data, expr, ...) {
-  runid <- as.character(data$run_info$run_uuid)
-
-  tryCatch(
-    error = function(cnd) {
-      message(cnd)
-      mlflow_update_run(run_uuid = runid, status = "FAILED", end_time = current_time())
-    },
-    interrupt = function(cnd) mlflow_update_run(
-      run_uuid = runid, status = "KILLED", end_time = current_time()
-    ),
-    {
-      force(expr)
-      mlflow_end_run()
-    }
-  )
-
-  invisible(NULL)
-}
-
-mlflow_ensure_run_id <- function(run_uuid) {
-  if (is.null(run_uuid)) {
-    run <- mlflow_active_run() %||% mlflow_start_run()
-    run$run_info$run_uuid
-  } else {
-    mlflow_get_or_create_active_connection()
-    tryCatch(mlflow_get_run(run_uuid)$info$run_uuid, error = function(e) {
-      stop(
-        "Run \"", run_uuid, "\" cannot be found in current tracking server at ",
-        mlflow_tracking_uri(), "."
-      )
-    })
-  }
-}
-
-#' Log Model
-#'
-#' Logs a model in the given run. Similar to `mlflow_save_model()`
-#' but stores model as an artifact within the active run.
-#'
-#' @param fn The serving function that will perform a prediction.
-#' @param artifact_path Destination path where this MLflow compatible model
-#'   will be saved.
-#' @param run_uuid The run associated with the model to be logged.
-#'
-#' @export
-mlflow_log_model <- function(fn, artifact_path, run_uuid = NULL) {
-  temp_path <- fs::path_temp(artifact_path)
-  mlflow_save_model(fn, path = temp_path)
-  mlflow_log_artifact(path = temp_path, artifact_path = artifact_path, run_uuid = run_uuid)
-}
diff --git a/mlflow/R/mlflow/R/ui.R b/mlflow/R/mlflow/R/ui.R
deleted file mode 100644
index 7938e617a096d..0000000000000
--- a/mlflow/R/mlflow/R/ui.R
+++ /dev/null
@@ -1,60 +0,0 @@
-#' @importFrom utils browseURL
-mlflow_view_url <- function(url) {
-  getOption("page_viewer", browseURL)(url)
-
-  invisible(url)
-}
-
-#' MLflow User Interface
-#'
-#' Launches MLflow user interface.
-#'
-#' @examples
-#' \dontrun{
-#' library(mlflow)
-#' mlflow_install()
-#'
-#' # launch mlflow ui locally
-#' mlflow_ui()
-#'
-#' # launch mlflow ui for existing mlflow server
-#' mlflow_set_tracking_uri("http://tracking-server:5000")
-#' mlflow_ui()
-#' }
-#'
-#' @param x If specified, can be either an `mlflow_connection` object or a string
-#'   specifying the file store, i.e. the root of the backing file store for experiment
-#'   and run data.
-#' @param ... Optional arguments passed to `mlflow_server()` when `x` is a path to a file store.
-#' @export
-mlflow_ui <- function(x, ...) {
-  UseMethod("mlflow_ui")
-}
-
-#' @export
-mlflow_ui.character <- function(x, ...) {
-  file_store <- fs::path_abs(x)
-  active_mc <- mlflow_active_connection()
-  tracking_uri <- if (!is.null(active_mc) && identical(active_mc$file_store, file_store)) {
-    active_mc$tracking_uri
-  } else {
-    mc <- mlflow_connect(file_store = file_store, ...)
-    mc$tracking_uri
-  }
-
-  mlflow_view_url(tracking_uri)
-}
-
-#' @export
-mlflow_ui.mlflow_connection <- function(x, ...) {
-  mlflow_view_url(x$tracking_uri)
-}
-
-#' @export
-mlflow_ui.NULL <- function(x, ...) {
-  tracking_uri <- mlflow_tracking_uri()
-  if (startsWith(tracking_uri, "http"))
-    mlflow_view_url(tracking_uri)
-  else
-    mlflow_ui(tracking_uri)
-}
diff --git a/mlflow/R/mlflow/R/user.R b/mlflow/R/mlflow/R/user.R
deleted file mode 100644
index 57ba158bf513e..0000000000000
--- a/mlflow/R/mlflow/R/user.R
+++ /dev/null
@@ -1,6 +0,0 @@
-mlflow_user <- function() {
-  if ("user" %in% names(Sys.info()))
-    Sys.info()[["user"]]
-  else
-    NULL
-}
diff --git a/mlflow/R/mlflow/R/wait.R b/mlflow/R/mlflow/R/wait.R
deleted file mode 100644
index 7beef7e66f776..0000000000000
--- a/mlflow/R/mlflow/R/wait.R
+++ /dev/null
@@ -1,21 +0,0 @@
-wait_for <- function(f, wait, sleep) {
-  command_start <- Sys.time()
-
-  success <- FALSE
-  while (!success && Sys.time() < command_start + wait) {
-    success <- suppressWarnings({
-      tryCatch({
-        f()
-        TRUE
-      }, error = function(err) {
-        FALSE
-      })
-    })
-
-    if (!success) Sys.sleep(sleep)
-  }
-
-  if (!success) {
-    stop("Operation failed after waiting for ", wait, " seconds")
-  }
-}
diff --git a/mlflow/R/mlflow/man-roxygen/roxlate-client-optional.R b/mlflow/R/mlflow/man-roxygen/roxlate-client-optional.R
new file mode 100644
index 0000000000000..b80faa5d562a0
--- /dev/null
+++ b/mlflow/R/mlflow/man-roxygen/roxlate-client-optional.R
@@ -0,0 +1,6 @@
+#' @param client An `mlflow_client` object. See details.
+#' @param ... Additional arguments; currently not used.
+#' @details For functions where `client` is optional, providing it will invoke the
+#'   Tracking Service API, allowing the user to specify the service where
+#'   the operation is executed. When `client` isn't specified, it defaults to
+#'   the service set by `mlflow_set_tracking_uri()`.
diff --git a/mlflow/R/mlflow/man-roxygen/roxlate-client.R b/mlflow/R/mlflow/man-roxygen/roxlate-client.R
new file mode 100644
index 0000000000000..498b52e638410
--- /dev/null
+++ b/mlflow/R/mlflow/man-roxygen/roxlate-client.R
@@ -0,0 +1 @@
+#' @param client An `mlflow_client` object.
diff --git a/mlflow/R/mlflow/man/active_experiment.Rd b/mlflow/R/mlflow/man/active_experiment.Rd
deleted file mode 100644
index d859354ad19f5..0000000000000
--- a/mlflow/R/mlflow/man/active_experiment.Rd
+++ /dev/null
@@ -1,18 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking.R
-\name{active_experiment}
-\alias{active_experiment}
-\alias{mlflow_active_experiment}
-\alias{mlflow_set_active_experiment}
-\title{Active Experiment}
-\usage{
-mlflow_active_experiment()
-
-mlflow_set_active_experiment(experiment_id)
-}
-\arguments{
-\item{experiment_id}{Identifer to get an experiment.}
-}
-\description{
-Retrieve or set the active experiment.
-}
diff --git a/mlflow/R/mlflow/man/active_run.Rd b/mlflow/R/mlflow/man/active_run.Rd
deleted file mode 100644
index 33305ff078e7e..0000000000000
--- a/mlflow/R/mlflow/man/active_run.Rd
+++ /dev/null
@@ -1,18 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking.R
-\name{active_run}
-\alias{active_run}
-\alias{mlflow_active_run}
-\alias{mlflow_set_active_run}
-\title{Active Run}
-\usage{
-mlflow_active_run()
-
-mlflow_set_active_run(run)
-}
-\arguments{
-\item{run}{The run object to make active.}
-}
-\description{
-Retrieves or sets the active run.
-}
diff --git a/mlflow/R/mlflow/man/mlflow_active_run.Rd b/mlflow/R/mlflow/man/mlflow_active_run.Rd
new file mode 100644
index 0000000000000..eb4d79bce1a90
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_active_run.Rd
@@ -0,0 +1,11 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-globals.R
+\name{mlflow_active_run}
+\alias{mlflow_active_run}
+\title{Active Run}
+\usage{
+mlflow_active_run()
+}
+\description{
+Retrieves the active run.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_client.Rd b/mlflow/R/mlflow/man/mlflow_client.Rd
new file mode 100644
index 0000000000000..e52435093ff8f
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_client.Rd
@@ -0,0 +1,14 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-client.R
+\name{mlflow_client}
+\alias{mlflow_client}
+\title{Initialize an MLflow client}
+\usage{
+mlflow_client(tracking_uri = NULL)
+}
+\arguments{
+\item{tracking_uri}{The tracking URI}
+}
+\description{
+Initialize an MLflow client
+}
diff --git a/mlflow/R/mlflow/man/mlflow_connect.Rd b/mlflow/R/mlflow/man/mlflow_connect.Rd
deleted file mode 100644
index 12c433b7ce1cb..0000000000000
--- a/mlflow/R/mlflow/man/mlflow_connect.Rd
+++ /dev/null
@@ -1,20 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/connection.R
-\name{mlflow_connect}
-\alias{mlflow_connect}
-\title{Connect to MLflow}
-\usage{
-mlflow_connect(x = NULL, activate = TRUE, ...)
-}
-\arguments{
-\item{x}{(Optional) Either a URL to the remote MLflow server or the file store,
-i.e. the root of the backing file store for experiment and run data. If not
-specified, will launch and connect to a local instance listening on a random port.}
-
-\item{activate}{Whether to set the connction as the active connection, defaults to `TRUE`.}
-
-\item{...}{Optional arguments passed to `mlflow_server()`.}
-}
-\description{
-Connect to local or remote MLflow instance.
-}
diff --git a/mlflow/R/mlflow/man/mlflow_create_experiment.Rd b/mlflow/R/mlflow/man/mlflow_create_experiment.Rd
index c94e1c0f46e13..129117d4f32ec 100644
--- a/mlflow/R/mlflow/man/mlflow_create_experiment.Rd
+++ b/mlflow/R/mlflow/man/mlflow_create_experiment.Rd
@@ -1,11 +1,10 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_create_experiment}
 \alias{mlflow_create_experiment}
 \title{Create Experiment}
 \usage{
-mlflow_create_experiment(name, artifact_location = NULL,
-  activate = TRUE)
+mlflow_create_experiment(name, artifact_location = NULL, client = NULL)
 }
 \arguments{
 \item{name}{The name of the experiment to create.}
@@ -13,22 +12,16 @@ mlflow_create_experiment(name, artifact_location = NULL,
 \item{artifact_location}{Location where all artifacts for this experiment are stored. If
 not provided, the remote server will select an appropriate default.}
 
-\item{activate}{Whether to set the created experiment as the active experiment. Defaults to `TRUE`.}
+\item{client}{An `mlflow_client` object. See details.}
+
+\item{...}{Additional arguments; currently not used.}
 }
 \description{
 Creates an MLflow experiment.
 }
-\examples{
-\dontrun{
-library(mlflow)
-mlflow_install()
-
-# create local experiment
-mlflow_create_experiment("My Experiment")
-
-# create experiment in remote MLflow server
-mlflow_set_tracking_uri("http://tracking-server:5000")
-mlflow_create_experiment("My Experiment")
-}
-
+\details{
+For functions where `client` is optional, providing it will invoke the
+  Tracking Service API, allowing the user to specify the service where
+  the operation is executed. When `client` isn't specified, it defaults to
+  the service set by `mlflow_set_tracking_uri()`.
 }
diff --git a/mlflow/R/mlflow/man/mlflow_create_run.Rd b/mlflow/R/mlflow/man/mlflow_create_run.Rd
index 60821a8fb646d..93745ebdd76ab 100644
--- a/mlflow/R/mlflow/man/mlflow_create_run.Rd
+++ b/mlflow/R/mlflow/man/mlflow_create_run.Rd
@@ -1,15 +1,16 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_create_run}
 \alias{mlflow_create_run}
 \title{Create Run}
 \usage{
-mlflow_create_run(user_id = mlflow_user(), run_name = NULL,
-  source_type = NULL, source_name = NULL, status = NULL,
-  start_time = NULL, end_time = NULL, source_version = NULL,
-  entry_point_name = NULL, tags = NULL, experiment_id = NULL)
+mlflow_create_run(experiment_id, user_id = NULL, run_name = NULL,
+  source_type = NULL, source_name = NULL, entry_point_name = NULL,
+  start_time = NULL, source_version = NULL, tags = NULL, client)
 }
 \arguments{
+\item{experiment_id}{Unique identifier for the associated experiment.}
+
 \item{user_id}{User ID or LDAP for the user executing the run.}
 
 \item{run_name}{Human readable name for run.}
@@ -18,19 +19,15 @@ mlflow_create_run(user_id = mlflow_user(), run_name = NULL,
 
 \item{source_name}{String descriptor for source. For example, name or description of the notebook, or job name.}
 
-\item{status}{Current status of the run. One of RUNNING, SCHEDULE, FINISHED, FAILED, KILLED.}
+\item{entry_point_name}{Name of the entry point for the run.}
 
 \item{start_time}{Unix timestamp of when the run started in milliseconds.}
 
-\item{end_time}{Unix timestamp of when the run ended in milliseconds.}
-
 \item{source_version}{Git version of the source code used to create run.}
 
-\item{entry_point_name}{Name of the entry point for the run.}
-
 \item{tags}{Additional metadata for run in key-value pairs.}
 
-\item{experiment_id}{Unique identifier for the associated experiment.}
+\item{client}{An `mlflow_client` object.}
 }
 \description{
 reate a new run within an experiment. A run is usually a single execution of a machine learning or data ETL pipeline.
diff --git a/mlflow/R/mlflow/man/mlflow_delete_experiment.Rd b/mlflow/R/mlflow/man/mlflow_delete_experiment.Rd
new file mode 100644
index 0000000000000..64e6000f59964
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_delete_experiment.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-client.R
+\name{mlflow_delete_experiment}
+\alias{mlflow_delete_experiment}
+\title{Delete Experiment}
+\usage{
+mlflow_delete_experiment(experiment_id, client)
+}
+\arguments{
+\item{experiment_id}{ID of the associated experiment This field is required.}
+
+\item{client}{An `mlflow_client` object.}
+}
+\description{
+Mark an experiment and associated runs, params, metrics, … etc for deletion. If the
+  experiment uses FileStore, artifacts associated with experiment are also deleted.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_disconnect.Rd b/mlflow/R/mlflow/man/mlflow_disconnect.Rd
deleted file mode 100644
index 6b853766f3a84..0000000000000
--- a/mlflow/R/mlflow/man/mlflow_disconnect.Rd
+++ /dev/null
@@ -1,11 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/connection.R
-\name{mlflow_disconnect}
-\alias{mlflow_disconnect}
-\title{Disconnect from MLflow}
-\usage{
-mlflow_disconnect()
-}
-\description{
-Disconnects from a local MLflow instance.
-}
diff --git a/mlflow/R/mlflow/man/mlflow_end_run.Rd b/mlflow/R/mlflow/man/mlflow_end_run.Rd
index 602eedc58a321..b73d1e5adba6e 100644
--- a/mlflow/R/mlflow/man/mlflow_end_run.Rd
+++ b/mlflow/R/mlflow/man/mlflow_end_run.Rd
@@ -1,14 +1,14 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking.R
+% Please edit documentation in R/tracking-fluent.R
 \name{mlflow_end_run}
 \alias{mlflow_end_run}
-\title{End Run}
+\title{End a Run}
 \usage{
-mlflow_end_run(status = "FINISHED")
+mlflow_end_run(status = c("FINISHED", "SCHEDULED", "FAILED", "KILLED"))
 }
 \arguments{
-\item{status}{Ending status of the run, defaults to `FINISHED`.}
+\item{status}{Updated status of the run. Defaults to `FINISHED`.}
 }
 \description{
-End the active run.
+End an active MLflow run (if there is one).
 }
diff --git a/mlflow/R/mlflow/man/mlflow_get_experiment.Rd b/mlflow/R/mlflow/man/mlflow_get_experiment.Rd
index f364f2e0bee4e..a94d221b05e92 100644
--- a/mlflow/R/mlflow/man/mlflow_get_experiment.Rd
+++ b/mlflow/R/mlflow/man/mlflow_get_experiment.Rd
@@ -1,13 +1,15 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_get_experiment}
 \alias{mlflow_get_experiment}
 \title{Get Experiment}
 \usage{
-mlflow_get_experiment(experiment_id)
+mlflow_get_experiment(experiment_id, client)
 }
 \arguments{
 \item{experiment_id}{Identifer to get an experiment.}
+
+\item{client}{An `mlflow_client` object.}
 }
 \description{
 Get meta data for experiment and a list of runs for this experiment.
diff --git a/mlflow/R/mlflow/man/mlflow_get_experiment_by_name.Rd b/mlflow/R/mlflow/man/mlflow_get_experiment_by_name.Rd
new file mode 100644
index 0000000000000..0bc23beccacdb
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_get_experiment_by_name.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-client.R
+\name{mlflow_get_experiment_by_name}
+\alias{mlflow_get_experiment_by_name}
+\title{Get Experiment by Name}
+\usage{
+mlflow_get_experiment_by_name(name, client)
+}
+\arguments{
+\item{name}{The experiment name.}
+
+\item{client}{An `mlflow_client` object.}
+}
+\description{
+Get meta data for experiment by name.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_get_metric.Rd b/mlflow/R/mlflow/man/mlflow_get_metric.Rd
index f5ac6f59cd3e7..679d77299bb8b 100644
--- a/mlflow/R/mlflow/man/mlflow_get_metric.Rd
+++ b/mlflow/R/mlflow/man/mlflow_get_metric.Rd
@@ -1,15 +1,17 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_get_metric}
 \alias{mlflow_get_metric}
 \title{Get Metric}
 \usage{
-mlflow_get_metric(metric_key, run_uuid = NULL)
+mlflow_get_metric(metric_key, client, run_id)
 }
 \arguments{
 \item{metric_key}{Name of the metric.}
 
-\item{run_uuid}{Unique ID for the run for which metric is recorded.}
+\item{client}{An `mlflow_client` object.}
+
+\item{run_id}{Run ID.}
 }
 \description{
 API to retrieve the logged value for a metric during a run. For a run, if this
diff --git a/mlflow/R/mlflow/man/mlflow_get_metric_history.Rd b/mlflow/R/mlflow/man/mlflow_get_metric_history.Rd
index 2988aec8928fd..b7873b2983f44 100644
--- a/mlflow/R/mlflow/man/mlflow_get_metric_history.Rd
+++ b/mlflow/R/mlflow/man/mlflow_get_metric_history.Rd
@@ -1,15 +1,17 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_get_metric_history}
 \alias{mlflow_get_metric_history}
 \title{Get Metric History}
 \usage{
-mlflow_get_metric_history(metric_key, run_uuid = NULL)
+mlflow_get_metric_history(metric_key, client, run_id)
 }
 \arguments{
 \item{metric_key}{Name of the metric.}
 
-\item{run_uuid}{Unique ID for the run for which metric is recorded.}
+\item{client}{An `mlflow_client` object.}
+
+\item{run_id}{Run ID.}
 }
 \description{
 For cases that a metric is logged more than once during a run, this API can be used
diff --git a/mlflow/R/mlflow/man/mlflow_get_param.Rd b/mlflow/R/mlflow/man/mlflow_get_param.Rd
index 980cbb570f829..7cc31625cad8b 100644
--- a/mlflow/R/mlflow/man/mlflow_get_param.Rd
+++ b/mlflow/R/mlflow/man/mlflow_get_param.Rd
@@ -1,18 +1,20 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_get_param}
 \alias{mlflow_get_param}
 \title{Get Param}
 \usage{
-mlflow_get_param(param_name, run_uuid = NULL)
+mlflow_get_param(param_name, client, run_id)
 }
 \arguments{
 \item{param_name}{Name of the param. This field is required.}
 
-\item{run_uuid}{ID of the run from which to retrieve the param value.}
+\item{client}{An `mlflow_client` object.}
+
+\item{run_id}{Run ID.}
 }
 \value{
-The param value as a named list.
+The param name and value as a data frame.
 }
 \description{
 Get a param value.
diff --git a/mlflow/R/mlflow/man/mlflow_get_run.Rd b/mlflow/R/mlflow/man/mlflow_get_run.Rd
index 9111aa2f414f5..69520f37fbc90 100644
--- a/mlflow/R/mlflow/man/mlflow_get_run.Rd
+++ b/mlflow/R/mlflow/man/mlflow_get_run.Rd
@@ -1,13 +1,15 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_get_run}
 \alias{mlflow_get_run}
 \title{Get Run}
 \usage{
-mlflow_get_run(run_uuid)
+mlflow_get_run(run_uuid, client)
 }
 \arguments{
 \item{run_uuid}{Unique ID for the run.}
+
+\item{client}{An `mlflow_client` object.}
 }
 \description{
 Get meta data, params, tags, and metrics for run. Only last logged value for each metric is returned.
diff --git a/mlflow/R/mlflow/man/mlflow_get_tracking_uri.Rd b/mlflow/R/mlflow/man/mlflow_get_tracking_uri.Rd
new file mode 100644
index 0000000000000..d74dca0b7f5b6
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_get_tracking_uri.Rd
@@ -0,0 +1,11 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-globals.R
+\name{mlflow_get_tracking_uri}
+\alias{mlflow_get_tracking_uri}
+\title{Get Remote Tracking URI}
+\usage{
+mlflow_get_tracking_uri()
+}
+\description{
+Get Remote Tracking URI
+}
diff --git a/mlflow/R/mlflow/man/mlflow_list_experiments.Rd b/mlflow/R/mlflow/man/mlflow_list_experiments.Rd
index e63b3d41d5f3e..84a19486e14d4 100644
--- a/mlflow/R/mlflow/man/mlflow_list_experiments.Rd
+++ b/mlflow/R/mlflow/man/mlflow_list_experiments.Rd
@@ -1,25 +1,17 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R
 \name{mlflow_list_experiments}
 \alias{mlflow_list_experiments}
 \title{List Experiments}
 \usage{
-mlflow_list_experiments()
+mlflow_list_experiments(view_type = c("ACTIVE_ONLY", "DELETED_ONLY",
+  "ALL"), client)
 }
-\description{
-Retrieves MLflow experiments as a data frame.
-}
-\examples{
-\dontrun{
-library(mlflow)
-mlflow_install()
-
-# list local experiments
-mlflow_list_experiments()
+\arguments{
+\item{view_type}{Qualifier for type of experiments to be returned. Defaults to `ACTIVE_ONLY`.}
 
-# list experiments in remote MLflow server
-mlflow_set_tracking_uri("http://tracking-server:5000")
-mlflow_list_experiments()
+\item{client}{An `mlflow_client` object.}
 }
-
+\description{
+Get a list of all experiments.
 }
diff --git a/mlflow/R/mlflow/man/mlflow_load_flavor.Rd b/mlflow/R/mlflow/man/mlflow_load_flavor.Rd
index 75e46ea5d8b83..74d0d4619ac0e 100644
--- a/mlflow/R/mlflow/man/mlflow_load_flavor.Rd
+++ b/mlflow/R/mlflow/man/mlflow_load_flavor.Rd
@@ -1,13 +1,13 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/flavor.R
+% Please edit documentation in R/model-flavor.R
 \name{mlflow_load_flavor}
 \alias{mlflow_load_flavor}
 \title{Load MLflow Model Flavor}
 \usage{
-mlflow_load_flavor(flavor_path)
+mlflow_load_flavor(model_path)
 }
 \arguments{
-\item{flavor_path}{The path to the MLflow model wrapped in the correct
+\item{model_path}{The path to the MLflow model wrapped in the correct
 class.}
 }
 \description{
diff --git a/mlflow/R/mlflow/man/mlflow_log_artifact.Rd b/mlflow/R/mlflow/man/mlflow_log_artifact.Rd
index fa7608decb37c..e1793f50478cc 100644
--- a/mlflow/R/mlflow/man/mlflow_log_artifact.Rd
+++ b/mlflow/R/mlflow/man/mlflow_log_artifact.Rd
@@ -1,22 +1,39 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/artifact.R
+% Please edit documentation in R/tracking-client.R, R/tracking-fluent.R
 \name{mlflow_log_artifact}
 \alias{mlflow_log_artifact}
+\alias{mlflow_log_artifact.mlflow_client}
+\alias{mlflow_log_artifact.NULL}
 \title{Log Artifact}
 \usage{
-mlflow_log_artifact(path, artifact_path = NULL, run_uuid = NULL)
+mlflow_log_artifact(path, artifact_path = NULL, client = NULL, ...)
+
+\method{mlflow_log_artifact}{mlflow_client}(path, artifact_path = NULL,
+  client = NULL, run_id = NULL, ...)
+
+\method{mlflow_log_artifact}{NULL}(path, artifact_path = NULL,
+  client = NULL, ...)
 }
 \arguments{
 \item{path}{The file or directory to log as an artifact.}
 
 \item{artifact_path}{Destination path within the run’s artifact URI.}
 
-\item{run_uuid}{The run associated with this artifact.}
+\item{client}{An `mlflow_client` object. See details.}
+
+\item{...}{Additional arguments; currently not used.}
+
+\item{run_id}{The run associated with this artifact.}
 }
 \description{
 Logs an specific file or directory as an artifact.
 }
 \details{
+For functions where `client` is optional, providing it will invoke the
+  Tracking Service API, allowing the user to specify the service where
+  the operation is executed. When `client` isn't specified, it defaults to
+  the service set by `mlflow_set_tracking_uri()`.
+
 When logging to Amazon S3, ensure that the user has a proper policy
 attach to it, for instance:
 
diff --git a/mlflow/R/mlflow/man/mlflow_log_metric.Rd b/mlflow/R/mlflow/man/mlflow_log_metric.Rd
index 16ce04beb5a1a..2108a4bdad78e 100644
--- a/mlflow/R/mlflow/man/mlflow_log_metric.Rd
+++ b/mlflow/R/mlflow/man/mlflow_log_metric.Rd
@@ -1,10 +1,18 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R, R/tracking-fluent.R
 \name{mlflow_log_metric}
 \alias{mlflow_log_metric}
+\alias{mlflow_log_metric.mlflow_client}
+\alias{mlflow_log_metric.NULL}
 \title{Log Metric}
 \usage{
-mlflow_log_metric(key, value, timestamp = NULL, run_uuid = NULL)
+mlflow_log_metric(key, value, timestamp = NULL, client = NULL, ...)
+
+\method{mlflow_log_metric}{mlflow_client}(key, value, timestamp = NULL,
+  client = NULL, run_id, ...)
+
+\method{mlflow_log_metric}{NULL}(key, value, timestamp = NULL,
+  client = NULL, ...)
 }
 \arguments{
 \item{key}{Name of the metric.}
@@ -13,10 +21,20 @@ mlflow_log_metric(key, value, timestamp = NULL, run_uuid = NULL)
 
 \item{timestamp}{Unix timestamp in milliseconds at the time metric was logged.}
 
-\item{run_uuid}{Unique ID for the run.}
+\item{client}{An `mlflow_client` object. See details.}
+
+\item{...}{Additional arguments; currently not used.}
+
+\item{run_id}{Run ID.}
 }
 \description{
 API to log a metric for a run. Metrics key-value pair that record a single float measure.
   During a single execution of a run, a particular metric can be logged several times.
   Backend will keep track of historical values along with timestamps.
 }
+\details{
+For functions where `client` is optional, providing it will invoke the
+  Tracking Service API, allowing the user to specify the service where
+  the operation is executed. When `client` isn't specified, it defaults to
+  the service set by `mlflow_set_tracking_uri()`.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_log_model.Rd b/mlflow/R/mlflow/man/mlflow_log_model.Rd
index c69c892cdb0a0..4e19de69d3e4b 100644
--- a/mlflow/R/mlflow/man/mlflow_log_model.Rd
+++ b/mlflow/R/mlflow/man/mlflow_log_model.Rd
@@ -1,18 +1,16 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking.R
+% Please edit documentation in R/model.R
 \name{mlflow_log_model}
 \alias{mlflow_log_model}
 \title{Log Model}
 \usage{
-mlflow_log_model(fn, artifact_path, run_uuid = NULL)
+mlflow_log_model(fn, artifact_path)
 }
 \arguments{
 \item{fn}{The serving function that will perform a prediction.}
 
 \item{artifact_path}{Destination path where this MLflow compatible model
 will be saved.}
-
-\item{run_uuid}{The run associated with the model to be logged.}
 }
 \description{
 Logs a model in the given run. Similar to `mlflow_save_model()`
diff --git a/mlflow/R/mlflow/man/mlflow_log_param.Rd b/mlflow/R/mlflow/man/mlflow_log_param.Rd
index 7ad536452ac6b..14aec9a45ebb4 100644
--- a/mlflow/R/mlflow/man/mlflow_log_param.Rd
+++ b/mlflow/R/mlflow/man/mlflow_log_param.Rd
@@ -1,17 +1,28 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
+% Please edit documentation in R/tracking-client.R, R/tracking-fluent.R
 \name{mlflow_log_param}
 \alias{mlflow_log_param}
+\alias{mlflow_log_param.mlflow_client}
+\alias{mlflow_log_param.NULL}
 \title{Log Parameter}
 \usage{
-mlflow_log_param(key, value, run_uuid = NULL)
+mlflow_log_param(key, value, client = NULL, ...)
+
+\method{mlflow_log_param}{mlflow_client}(key, value, client = NULL,
+  run_id, ...)
+
+\method{mlflow_log_param}{NULL}(key, value, client = NULL, ...)
 }
 \arguments{
 \item{key}{Name of the parameter.}
 
 \item{value}{String value of the parameter.}
 
-\item{run_uuid}{Unique ID for the run for which parameter is recorded.}
+\item{client}{An `mlflow_client` object. See details.}
+
+\item{...}{Additional arguments; currently not used.}
+
+\item{run_id}{Run ID.}
 }
 \description{
 API to log a parameter used for this run. Examples are params and hyperparams
@@ -19,3 +30,9 @@ API to log a parameter used for this run. Examples are params and hyperparams
   A params is a STRING key-value pair. For a run, a single parameter is allowed
   to be logged only once.
 }
+\details{
+For functions where `client` is optional, providing it will invoke the
+  Tracking Service API, allowing the user to specify the service where
+  the operation is executed. When `client` isn't specified, it defaults to
+  the service set by `mlflow_set_tracking_uri()`.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_param.Rd b/mlflow/R/mlflow/man/mlflow_param.Rd
index d03964001508a..6e818fc7462be 100644
--- a/mlflow/R/mlflow/man/mlflow_param.Rd
+++ b/mlflow/R/mlflow/man/mlflow_param.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/param.R
+% Please edit documentation in R/project-param.R
 \name{mlflow_param}
 \alias{mlflow_param}
 \title{Read Command Line Parameter}
diff --git a/mlflow/R/mlflow/man/mlflow_predict_flavor.Rd b/mlflow/R/mlflow/man/mlflow_predict_flavor.Rd
index c1bb80e8b0124..3512c4eea2bd0 100644
--- a/mlflow/R/mlflow/man/mlflow_predict_flavor.Rd
+++ b/mlflow/R/mlflow/man/mlflow_predict_flavor.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/flavor.R
+% Please edit documentation in R/model-flavor.R
 \name{mlflow_predict_flavor}
 \alias{mlflow_predict_flavor}
 \title{Predict over MLflow Model Flavor}
diff --git a/mlflow/R/mlflow/man/mlflow_restore_experiment.Rd b/mlflow/R/mlflow/man/mlflow_restore_experiment.Rd
new file mode 100644
index 0000000000000..cb1e8892bd412
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_restore_experiment.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-client.R
+\name{mlflow_restore_experiment}
+\alias{mlflow_restore_experiment}
+\title{Restore Experiment}
+\usage{
+mlflow_restore_experiment(experiment_id, client)
+}
+\arguments{
+\item{experiment_id}{ID of the associated experiment This field is required.}
+
+\item{client}{An `mlflow_client` object.}
+}
+\description{
+Restore an experiment marked for deletion. This also restores associated metadata,
+  runs, metrics, and params. If experiment uses FileStore, underlying artifacts
+  associated with experiment are also restored.
+}
+\details{
+Throws RESOURCE_DOES_NOT_EXIST if experiment was never created or was permanently deleted.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_restore_snapshot.Rd b/mlflow/R/mlflow/man/mlflow_restore_snapshot.Rd
index d2a1cb171f64b..0d4c2166fa036 100644
--- a/mlflow/R/mlflow/man/mlflow_restore_snapshot.Rd
+++ b/mlflow/R/mlflow/man/mlflow_restore_snapshot.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/snapshot.R
+% Please edit documentation in R/project-snapshot.R
 \name{mlflow_restore_snapshot}
 \alias{mlflow_restore_snapshot}
 \title{Restore Snapshot}
diff --git a/mlflow/R/mlflow/man/mlflow_rfunc_serve.Rd b/mlflow/R/mlflow/man/mlflow_rfunc_serve.Rd
index 098b1e2031df6..7fc1b3dfa04e8 100644
--- a/mlflow/R/mlflow/man/mlflow_rfunc_serve.Rd
+++ b/mlflow/R/mlflow/man/mlflow_rfunc_serve.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/serve.R
+% Please edit documentation in R/model-serve.R
 \name{mlflow_rfunc_serve}
 \alias{mlflow_rfunc_serve}
 \title{Serve an RFunc MLflow Model}
diff --git a/mlflow/R/mlflow/man/mlflow_run.Rd b/mlflow/R/mlflow/man/mlflow_run.Rd
index 4190db843bb24..846d18694ab22 100644
--- a/mlflow/R/mlflow/man/mlflow_run.Rd
+++ b/mlflow/R/mlflow/man/mlflow_run.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/run.R
+% Please edit documentation in R/project-run.R
 \name{mlflow_run}
 \alias{mlflow_run}
 \title{Run in MLflow}
diff --git a/mlflow/R/mlflow/man/mlflow_save_flavor.Rd b/mlflow/R/mlflow/man/mlflow_save_flavor.Rd
index d4facf542b6c0..d39f15c54b89e 100644
--- a/mlflow/R/mlflow/man/mlflow_save_flavor.Rd
+++ b/mlflow/R/mlflow/man/mlflow_save_flavor.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/flavor.R
+% Please edit documentation in R/model-flavor.R
 \name{mlflow_save_flavor}
 \alias{mlflow_save_flavor}
 \title{Save MLflow Model Flavor}
diff --git a/mlflow/R/mlflow/man/mlflow_server.Rd b/mlflow/R/mlflow/man/mlflow_server.Rd
index c55c69bd0d6b0..cb260a26cd5aa 100644
--- a/mlflow/R/mlflow/man/mlflow_server.Rd
+++ b/mlflow/R/mlflow/man/mlflow_server.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/connection.R
+% Please edit documentation in R/tracking-server.R
 \name{mlflow_server}
 \alias{mlflow_server}
 \title{Run the MLflow Tracking Server}
diff --git a/mlflow/R/mlflow/man/mlflow_set_experiment.Rd b/mlflow/R/mlflow/man/mlflow_set_experiment.Rd
new file mode 100644
index 0000000000000..3160ee03679ee
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_set_experiment.Rd
@@ -0,0 +1,15 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-fluent.R
+\name{mlflow_set_experiment}
+\alias{mlflow_set_experiment}
+\title{Set Experiment}
+\usage{
+mlflow_set_experiment(experiment_name)
+}
+\arguments{
+\item{experiment_name}{Name of experiment to be activated.}
+}
+\description{
+Set given experiment as active experiment. If experiment does not
+ exist, create an experiment with provided name.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_set_tag.Rd b/mlflow/R/mlflow/man/mlflow_set_tag.Rd
new file mode 100644
index 0000000000000..648048b300a74
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_set_tag.Rd
@@ -0,0 +1,36 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-client.R, R/tracking-fluent.R
+\name{mlflow_set_tag}
+\alias{mlflow_set_tag}
+\alias{mlflow_set_tag.mlflow_client}
+\alias{mlflow_set_tag.NULL}
+\title{Set Tag}
+\usage{
+mlflow_set_tag(key, value, client = NULL, ...)
+
+\method{mlflow_set_tag}{mlflow_client}(key, value, client = NULL, run_id,
+  ...)
+
+\method{mlflow_set_tag}{NULL}(key, value, client = NULL, ...)
+}
+\arguments{
+\item{key}{Name of the tag. Maximum size is 255 bytes. This field is required.}
+
+\item{value}{String value of the tag being logged. Maximum size is 500 bytes. This field is required.}
+
+\item{client}{An `mlflow_client` object. See details.}
+
+\item{...}{Additional arguments; currently not used.}
+
+\item{run_id}{Run ID.}
+}
+\description{
+Set a tag on a run. Tags are run metadata that can be updated during and
+ after a run completes.
+}
+\details{
+For functions where `client` is optional, providing it will invoke the
+  Tracking Service API, allowing the user to specify the service where
+  the operation is executed. When `client` isn't specified, it defaults to
+  the service set by `mlflow_set_tracking_uri()`.
+}
diff --git a/mlflow/R/mlflow/man/mlflow_set_terminated.Rd b/mlflow/R/mlflow/man/mlflow_set_terminated.Rd
new file mode 100644
index 0000000000000..d83f14799cada
--- /dev/null
+++ b/mlflow/R/mlflow/man/mlflow_set_terminated.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tracking-client.R
+\name{mlflow_set_terminated}
+\alias{mlflow_set_terminated}
+\title{Terminate a Run}
+\usage{
+mlflow_set_terminated(run_id, status = c("FINISHED", "SCHEDULED",
+  "FAILED", "KILLED"), end_time = NULL, client)
+}
+\arguments{
+\item{run_id}{Unique identifier for the run.}
+
+\item{status}{Updated status of the run. Defaults to `FINISHED`.}
+
+\item{end_time}{Unix timestamp of when the run ended in milliseconds.}
+
+\item{client}{An `mlflow_client` object.}
+}
+\description{
+Terminate a Run
+}
diff --git a/mlflow/R/mlflow/man/mlflow_set_tracking_uri.Rd b/mlflow/R/mlflow/man/mlflow_set_tracking_uri.Rd
index 42244057c9a13..9e2d9d29ddf08 100644
--- a/mlflow/R/mlflow/man/mlflow_set_tracking_uri.Rd
+++ b/mlflow/R/mlflow/man/mlflow_set_tracking_uri.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/remote.R
+% Please edit documentation in R/tracking-globals.R
 \name{mlflow_set_tracking_uri}
 \alias{mlflow_set_tracking_uri}
 \title{Set Remote Tracking URI}
diff --git a/mlflow/R/mlflow/man/mlflow_snapshot.Rd b/mlflow/R/mlflow/man/mlflow_snapshot.Rd
index b1d4e42c8ae26..c2bd95d0a0575 100644
--- a/mlflow/R/mlflow/man/mlflow_snapshot.Rd
+++ b/mlflow/R/mlflow/man/mlflow_snapshot.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/snapshot.R
+% Please edit documentation in R/project-snapshot.R
 \name{mlflow_snapshot}
 \alias{mlflow_snapshot}
 \title{Dependencies Snapshot}
diff --git a/mlflow/R/mlflow/man/mlflow_source.Rd b/mlflow/R/mlflow/man/mlflow_source.Rd
index 881100c2a0106..c695e21d0c6f4 100644
--- a/mlflow/R/mlflow/man/mlflow_source.Rd
+++ b/mlflow/R/mlflow/man/mlflow_source.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/source.R
+% Please edit documentation in R/project-source.R
 \name{mlflow_source}
 \alias{mlflow_source}
 \title{Source a Script with MLflow Params}
diff --git a/mlflow/R/mlflow/man/mlflow_start_run.Rd b/mlflow/R/mlflow/man/mlflow_start_run.Rd
index 32e6e20287699..dfaa34cc532d0 100644
--- a/mlflow/R/mlflow/man/mlflow_start_run.Rd
+++ b/mlflow/R/mlflow/man/mlflow_start_run.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking.R
+% Please edit documentation in R/tracking-fluent.R
 \name{mlflow_start_run}
 \alias{mlflow_start_run}
 \title{Start Run}
diff --git a/mlflow/R/mlflow/man/mlflow_tracking_uri.Rd b/mlflow/R/mlflow/man/mlflow_tracking_uri.Rd
deleted file mode 100644
index 1039ccc90bc0d..0000000000000
--- a/mlflow/R/mlflow/man/mlflow_tracking_uri.Rd
+++ /dev/null
@@ -1,11 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/remote.R
-\name{mlflow_tracking_uri}
-\alias{mlflow_tracking_uri}
-\title{Get Remote Tracking URI}
-\usage{
-mlflow_tracking_uri()
-}
-\description{
-Get Remote Tracking URI
-}
diff --git a/mlflow/R/mlflow/man/mlflow_ui.Rd b/mlflow/R/mlflow/man/mlflow_ui.Rd
index 618b5cfe55471..02ff4e0e0ddf5 100644
--- a/mlflow/R/mlflow/man/mlflow_ui.Rd
+++ b/mlflow/R/mlflow/man/mlflow_ui.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/ui.R
+% Please edit documentation in R/tracking-ui.R
 \name{mlflow_ui}
 \alias{mlflow_ui}
 \title{MLflow User Interface}
@@ -7,9 +7,7 @@
 mlflow_ui(x, ...)
 }
 \arguments{
-\item{x}{If specified, can be either an `mlflow_connection` object or a string
-specifying the file store, i.e. the root of the backing file store for experiment
-and run data.}
+\item{x}{An `mlflow_client` object.}
 
 \item{...}{Optional arguments passed to `mlflow_server()` when `x` is a path to a file store.}
 }
diff --git a/mlflow/R/mlflow/man/mlflow_update_run.Rd b/mlflow/R/mlflow/man/mlflow_update_run.Rd
deleted file mode 100644
index 6636a4b9137dc..0000000000000
--- a/mlflow/R/mlflow/man/mlflow_update_run.Rd
+++ /dev/null
@@ -1,19 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tracking-rest.R
-\name{mlflow_update_run}
-\alias{mlflow_update_run}
-\title{Update Run}
-\usage{
-mlflow_update_run(status = c("FINISHED", "SCHEDULED", "FAILED",
-  "KILLED"), end_time = NULL, run_uuid = NULL)
-}
-\arguments{
-\item{status}{Updated status of the run. Defaults to `FINISHED`.}
-
-\item{end_time}{Unix timestamp of when the run ended in milliseconds.}
-
-\item{run_uuid}{Unique identifier for the run.}
-}
-\description{
-Update Run
-}
diff --git a/mlflow/R/mlflow/tests/testthat/helpers.R b/mlflow/R/mlflow/tests/testthat/helpers.R
index e4de5f126b202..4d6865a9f0479 100644
--- a/mlflow/R/mlflow/tests/testthat/helpers.R
+++ b/mlflow/R/mlflow/tests/testthat/helpers.R
@@ -1,6 +1,13 @@
 mlflow_clear_test_dir <- function(path) {
+  mlflow_end_run()
+  mlflow_set_active_experiment_id(NULL)
   if (dir.exists(path)) {
     unlink(path, recursive = TRUE)
   }
-  mlflow_disconnect()
+  mlflow_deregister_local_servers()
+}
+
+mlflow_deregister_local_servers <- function() {
+  purrr::walk(as.list(mlflow:::.globals$url_mapping), ~ .x$handle$kill())
+  rlang::env_unbind(mlflow:::.globals, "url_mapping")
 }
diff --git a/mlflow/R/mlflow/tests/testthat/test_model.R b/mlflow/R/mlflow/tests/testthat/test-model.R
similarity index 100%
rename from mlflow/R/mlflow/tests/testthat/test_model.R
rename to mlflow/R/mlflow/tests/testthat/test-model.R
diff --git a/mlflow/R/mlflow/tests/testthat/test_params.R b/mlflow/R/mlflow/tests/testthat/test-params.R
similarity index 97%
rename from mlflow/R/mlflow/tests/testthat/test_params.R
rename to mlflow/R/mlflow/tests/testthat/test-params.R
index 54f3bd0bc38f4..8d5f21164eb3f 100644
--- a/mlflow/R/mlflow/tests/testthat/test_params.R
+++ b/mlflow/R/mlflow/tests/testthat/test-params.R
@@ -1,4 +1,4 @@
-context("Run")
+context("Params")
 
 test_that("mlflow can read typed command line parameters", {
   mlflow_clear_test_dir("mlruns")
diff --git a/mlflow/R/mlflow/tests/testthat/test_run.R b/mlflow/R/mlflow/tests/testthat/test-run.R
similarity index 100%
rename from mlflow/R/mlflow/tests/testthat/test_run.R
rename to mlflow/R/mlflow/tests/testthat/test-run.R
diff --git a/mlflow/R/mlflow/tests/testthat/test_serve.R b/mlflow/R/mlflow/tests/testthat/test-serve.R
similarity index 100%
rename from mlflow/R/mlflow/tests/testthat/test_serve.R
rename to mlflow/R/mlflow/tests/testthat/test-serve.R
diff --git a/mlflow/R/mlflow/tests/testthat/test_style.R b/mlflow/R/mlflow/tests/testthat/test-style.R
similarity index 100%
rename from mlflow/R/mlflow/tests/testthat/test_style.R
rename to mlflow/R/mlflow/tests/testthat/test-style.R
diff --git a/mlflow/R/mlflow/tests/testthat/test-rest-api.R b/mlflow/R/mlflow/tests/testthat/test-tracking.R
similarity index 60%
rename from mlflow/R/mlflow/tests/testthat/test-rest-api.R
rename to mlflow/R/mlflow/tests/testthat/test-tracking.R
index 51b865bfa3364..603d436e9b2a7 100644
--- a/mlflow/R/mlflow/tests/testthat/test-rest-api.R
+++ b/mlflow/R/mlflow/tests/testthat/test-tracking.R
@@ -1,31 +1,20 @@
-context("REST wrappers")
+context("Tracking")
 
 test_that("mlflow_create_experiment() works properly", {
-  experiment_id <- mlflow_create_experiment("exp_name", "art_loc")
-  experiment <- mlflow_get_experiment(experiment_id)
+  mlflow_clear_test_dir("mlruns")
+  client <- mlflow_client()
+  experiment_id <- mlflow_create_experiment(client = client, "exp_name", "art_loc")
+  experiment <- mlflow_get_experiment(client = client, experiment_id)
   expect_identical(experiment$experiment$name, "exp_name")
   expect_identical(experiment$experiment$artifact_location, "art_loc")
 })
 
-test_that("mlflow_create_experiment() `activate` parameter is respected", {
-  experiment_id <- mlflow_create_experiment("foo1", "art_loc")
-  expect_identical(mlflow_active_experiment(), experiment_id)
-  mlflow_create_experiment("foo2", "art_loc", activate = FALSE)
-  expect_identical(mlflow_active_experiment(), experiment_id)
-})
-
-test_that("mlflow_create_experiment() checks for existing experiment with same name", {
-  expect_message(
-    mlflow_create_experiment("foo2", "art_loc"),
-    "Experiment with name \"foo2\" already exists\\."
-  )
-})
-
 test_that("mlflow_list_experiments() works properly", {
   mlflow_clear_test_dir("mlruns")
-  mlflow_create_experiment("foo1", "art_loc1")
-  mlflow_create_experiment("foo2", "art_loc2")
-  experiments_list <- mlflow_list_experiments()
+  client <- mlflow_client()
+  mlflow_create_experiment(client = client, "foo1", "art_loc1")
+  mlflow_create_experiment(client = client, "foo2", "art_loc2")
+  experiments_list <- mlflow_list_experiments(client = client)
   expect_setequal(experiments_list$experiment_id, c("0", "1", "2"))
   expect_setequal(experiments_list$name, c("Default", "foo1", "foo2"))
   expect_setequal(experiments_list$artifact_location, c("mlruns/0", "art_loc1", "art_loc2"))
@@ -33,21 +22,26 @@ test_that("mlflow_list_experiments() works properly", {
 
 test_that("mlflow_get_experiment() works properly", {
   mlflow_clear_test_dir("mlruns")
-  experiment_id <- mlflow_create_experiment("foo1", "art_loc1")
-  experiment <- mlflow_get_experiment(experiment_id)
+  client <- mlflow_client()
+  experiment_id <- mlflow_create_experiment(client = client, "foo1", "art_loc1")
+  experiment <- mlflow_get_experiment(client = client, experiment_id)
   expect_identical(experiment$experiment$experiment_id, experiment_id)
   expect_identical(experiment$experiment$name, "foo1")
   expect_identical(experiment$experiment$artifact_location, "art_loc1")
 })
 
 test_that("mlflow_create_run()/mlflow_get_run() work properly", {
+  mlflow_clear_test_dir("mlruns")
+  client <- mlflow_client()
   create_run_response <- mlflow_create_run(
+    client = client,
+    experiment_id = "0",
     user_id = "user1",
     run_name = "run1",
     tags = list(foo = "bar", foz = "baz")
   )
 
-  run <- mlflow_get_run(create_run_response$run_uuid)
+  run <- mlflow_get_run(client = client, create_run_response$info$run_uuid)
   run_info <- run$info
 
   expect_identical(run_info$user_id, "user1")
@@ -62,30 +56,27 @@ test_that("mlflow_create_run()/mlflow_get_run() work properly", {
 })
 
 test_that("mlflow_log_param()/mlflow_get_param() work properly", {
-  mlflow_disconnect()
+  mlflow_clear_test_dir("mlruns")
   some_param <- mlflow_log_param("some_param", 42)
   expect_identical(some_param, 42)
+  client <- mlflow_client()
   expect_identical(
-    mlflow_get_param("some_param"),
+    mlflow_get_param(client = client, "some_param", run_id = mlflow_active_run()$info$run_uuid),
     data.frame(key = "some_param", value = "42", stringsAsFactors = FALSE)
   )
 })
 
-test_that("mlflow_get_param() requires `run_uuid` when there is no active run", {
-  mlflow_disconnect()
-  expect_error(
-    mlflow_get_param("some_param"),
-    "`run_uuid` must be specified when there is no active run\\."
-  )
-})
-
 test_that("mlflow_log_metric()/mlflow_get_metric() work properly", {
-  mlflow_disconnect()
+  mlflow_clear_test_dir("mlruns")
   log_time <- mlflow:::current_time()
   some_metric <- mlflow_log_metric("some_metric", 42, timestamp = log_time)
   expect_identical(some_metric, 42)
+  client <- mlflow_client()
   expect_identical(
-    mlflow_get_metric("some_metric"),
+    mlflow_get_metric(
+      client = client, run_id = mlflow_active_run()$info$run_uuid,
+      "some_metric"
+    ),
     data.frame(
       key = "some_metric", value = 42,
       timestamp = as.POSIXct(as.double(log_time) / 1000, origin = "1970-01-01"),
@@ -94,21 +85,16 @@ test_that("mlflow_log_metric()/mlflow_get_metric() work properly", {
   )
 })
 
-test_that("mlflow_get_metric() requires `run_uuid` when there is no active run", {
-  mlflow_disconnect()
-  expect_error(
-    mlflow_get_metric("some_metric"),
-    "`run_uuid` must be specified when there is no active run\\."
-  )
-})
-
 test_that("mlflow_get_metric_history() works properly", {
-  mlflow_disconnect()
+  mlflow_clear_test_dir("mlruns")
   log_time1 <- mlflow:::current_time()
   some_metric1 <- mlflow_log_metric("some_metric", 42, timestamp = log_time1)
   log_time2 <- mlflow:::current_time()
   some_metric2 <- mlflow_log_metric("some_metric", 91, timestamp = log_time2)
-  metric_history <- mlflow_get_metric_history("some_metric")
+  client <- mlflow_client()
+  metric_history <- mlflow_get_metric_history(
+    client = client, run_id = mlflow_active_run()$info$run_uuid, "some_metric"
+  )
   expected <- data.frame(
     key = c("some_metric", "some_metric"),
     value = c(some_metric1, some_metric2),
@@ -118,11 +104,15 @@ test_that("mlflow_get_metric_history() works properly", {
   expect_identical(metric_history, expected)
 })
 
-test_that("mlflow_update_run() works properly", {
-  mlflow_disconnect()
+test_that("mlflow_set_teminated() works properly", {
+  mlflow_clear_test_dir("mlruns")
   mlflow_start_run()
   killed_time <- mlflow:::current_time()
-  run_info <- mlflow_update_run(status = "KILLED", end_time = killed_time)
+  client <- mlflow_client()
+  run_info <- mlflow_set_terminated(
+    client = client, run_id = mlflow_active_run()$info$run_uuid,
+    status = "KILLED", end_time = killed_time
+  )
   expect_identical(run_info$status, "KILLED")
   expect_identical(run_info$end_time, as.POSIXct(as.double(c(killed_time)) / 1000, origin = "1970-01-01"))
 })
diff --git a/mlflow/R/mlflow/tests/testthat/test_ui.R b/mlflow/R/mlflow/tests/testthat/test-ui.R
similarity index 100%
rename from mlflow/R/mlflow/tests/testthat/test_ui.R
rename to mlflow/R/mlflow/tests/testthat/test-ui.R


Code review comments:
diff_hunk: @@ -0,0 +1,371 @@
+new_mlflow_client <- function(tracking_uri, server_url = NULL) {
+  structure(
+    list(
+      tracking_uri = tracking_uri,
+      server_url = server_url %||% tracking_uri
+    ),
+    class = "mlflow_client"
+  )
+}
+
+#' Initialize an MLflow client
+#'
+#' @param tracking_uri The tracking URI
+#'
+#' @export
+mlflow_client <- function(tracking_uri = NULL) {
+  tracking_uri <- tracking_uri %||% mlflow_get_tracking_uri()
+  server_url <- if (startsWith(tracking_uri, "http")) {
+    tracking_uri
+  } else if (!is.null(mlflow_local_server(tracking_uri)$server_url)) {
+    mlflow_local_server(tracking_uri)$server_url
+  } else {
+    local_server <- mlflow_server(file_store = tracking_uri, port = mlflow_connect_port())
+    mlflow_register_local_server(tracking_uri = tracking_uri, local_server = local_server)
+    local_server$server_url
+  }
+
+  new_mlflow_client(tracking_uri, server_url = server_url)
+}
+
+#' Create Experiment
+#'
+#' Creates an MLflow experiment.
+#'
+#' @param name The name of the experiment to create.
+#' @param artifact_location Location where all artifacts for this experiment are stored. If
+#'   not provided, the remote server will select an appropriate default.
+#' @template roxlate-client-optional
+#'
+#' @export
+mlflow_create_experiment <- function(name, artifact_location = NULL, client = NULL) {
path: mlflow/R/mlflow/R/tracking-client.R
text: @user1:
Wouldn't  it be better to put client first so we can do client %>% mlflow_create_experiment?

diff_hunk: @@ -0,0 +1,88 @@
+mlflow_relative_paths <- function(paths) {
+  gsub(paste0("^", file.path(getwd(), "")), "", paths)
+}
+
+get_executing_file_name <- function() {
+  pattern <- "^--file="
+  v <- grep(pattern, commandArgs(), value = TRUE)
+  file_name <- gsub(pattern, "", v)
+  if (length(file_name)) file_name
+}
+
+get_source_name <- function() {
+  get_executing_file_name() %||% "<console>"
+}
+
+get_source_version <- function() {
+  file_name <- get_executing_file_name()
+  tryCatch(
+    error = function(cnd) NULL,
+    {
+      repo <- git2r::repository(file_name, discover = TRUE)
+      commit <- git2r::commits(repo, n = 1)
+      commit[[1]]@sha
+    }
+  )
+}
+
+mlflow_get_or_start_run <- function() {
+  mlflow_active_run() %||% mlflow_start_run()
+}
+
+#' @export
+with.mlflow_active_run <- function(data, expr, ...) {
path: mlflow/R/mlflow/R/tracking-utils.R
text: @user1:
nit: I am not sure if this one does not belong to fluent.R.

diff_hunk: @@ -0,0 +1,371 @@
+new_mlflow_client <- function(tracking_uri, server_url = NULL) {
+  structure(
+    list(
+      tracking_uri = tracking_uri,
+      server_url = server_url %||% tracking_uri
+    ),
+    class = "mlflow_client"
+  )
+}
+
+#' Initialize an MLflow client
+#'
+#' @param tracking_uri The tracking URI
+#'
+#' @export
+mlflow_client <- function(tracking_uri = NULL) {
+  tracking_uri <- tracking_uri %||% mlflow_get_tracking_uri()
+  server_url <- if (startsWith(tracking_uri, "http")) {
+    tracking_uri
+  } else if (!is.null(mlflow_local_server(tracking_uri)$server_url)) {
+    mlflow_local_server(tracking_uri)$server_url
+  } else {
+    local_server <- mlflow_server(file_store = tracking_uri, port = mlflow_connect_port())
+    mlflow_register_local_server(tracking_uri = tracking_uri, local_server = local_server)
+    local_server$server_url
+  }
+
+  new_mlflow_client(tracking_uri, server_url = server_url)
+}
+
+#' Create Experiment
+#'
+#' Creates an MLflow experiment.
+#'
+#' @param name The name of the experiment to create.
+#' @param artifact_location Location where all artifacts for this experiment are stored. If
+#'   not provided, the remote server will select an appropriate default.
+#' @template roxlate-client-optional
+#'
+#' @export
+mlflow_create_experiment <- function(name, artifact_location = NULL, client = NULL) {
+  UseMethod("mlflow_create_experiment", client)
+}
+
+#' @export
+mlflow_create_experiment.mlflow_client <- function(name, artifact_location = NULL, client = NULL) {
+  name <- forge::cast_string(name)
+  response <- mlflow_client_create_experiment(client, name, artifact_location)
+  invisible(response$experiment_id)
+}
+
+#' Create Run
+#'
+#' reate a new run within an experiment. A run is usually a single execution of a machine learning or data ETL pipeline.
+#'
+#' MLflow uses runs to track Param, Metric, and RunTag, associated with a single execution.
+#'
+#' @param experiment_id Unique identifier for the associated experiment.
+#' @param user_id User ID or LDAP for the user executing the run.
+#' @param run_name Human readable name for run.
+#' @param source_type Originating source for this run. One of Notebook, Job, Project, Local or Unknown.
+#' @param source_name String descriptor for source. For example, name or description of the notebook, or job name.
+#' @param start_time Unix timestamp of when the run started in milliseconds.
+#' @param source_version Git version of the source code used to create run.
+#' @param entry_point_name Name of the entry point for the run.
+#' @param tags Additional metadata for run in key-value pairs.
+#' @template roxlate-client
+#' @export
+mlflow_create_run <- function(
+  experiment_id, user_id = NULL, run_name = NULL, source_type = NULL,
+  source_name = NULL, entry_point_name = NULL, start_time = NULL, source_version = NULL,
+  tags = NULL, client
+) {
+  tags <- if (!is.null(tags)) tags %>%
+    purrr::imap(~ list(key = .y, value = .x)) %>%
+    unname()
+
+  run <- mlflow_client_create_run(
+    client, experiment_id, user_id, run_name, source_type,
+    source_name, entry_point_name, start_time, source_version, tags
+  )
+  new_mlflow_entities_run(run)
+}
+
+#' Delete Experiment
+#'
+#' Mark an experiment and associated runs, params, metrics, … etc for deletion. If the
+#'   experiment uses FileStore, artifacts associated with experiment are also deleted.
+#'
+#' @param experiment_id ID of the associated experiment This field is required.
+#' @template roxlate-client
+#' @export
+mlflow_delete_experiment <- function(experiment_id, client) {
+  mlflow_client_delete_experiment(client, experiment_id)
+}
+
+#' Restore Experiment
+#'
+#' Restore an experiment marked for deletion. This also restores associated metadata,
+#'   runs, metrics, and params. If experiment uses FileStore, underlying artifacts
+#'   associated with experiment are also restored.
+#'
+#' Throws RESOURCE_DOES_NOT_EXIST if experiment was never created or was permanently deleted.
+#'
+#' @param experiment_id ID of the associated experiment This field is required.
+#' @template roxlate-client
+#' @export
+mlflow_restore_experiment <- function(experiment_id, client) {
+  mlflow_client_restore_experiment(client, experiment_id)
+}
+
+#' Get Run
+#'
+#' Get meta data, params, tags, and metrics for run. Only last logged value for each metric is returned.
+#'
+#' @param run_uuid Unique ID for the run.
+#' @template roxlate-client
+#'
+#' @export
+mlflow_get_run <- function(run_uuid, client) {
+  response <- mlflow_rest("runs", "get", client = client, query = list(run_uuid = run_uuid))
+  new_mlflow_entities_run(response)
+}
+
+#' Log Metric
+#'
+#' API to log a metric for a run. Metrics key-value pair that record a single float measure.
+#'   During a single execution of a run, a particular metric can be logged several times.
+#'   Backend will keep track of historical values along with timestamps.
+#'
+#' @param key Name of the metric.
+#' @param value Float value for the metric being logged.
+#' @param timestamp Unix timestamp in milliseconds at the time metric was logged.
+#' @template roxlate-client-optional
+#' @export
+mlflow_log_metric <- function(key, value, timestamp = NULL, client = NULL, ...) {
+  UseMethod("mlflow_log_metric", client)
+}
+
+#' @rdname mlflow_log_metric
+#' @param run_id Run ID.
+#' @export
+mlflow_log_metric.mlflow_client <- function(
+  key, value, timestamp = NULL, client = NULL, run_id, ...
+) {
+  if (!rlang::inherits_any(value, c("character", "numeric", "integer"))) {
+    stop("Metric ", key, " must be a character or numeric but ", class(value), " found.")
path: mlflow/R/mlflow/R/tracking-client.R
text: @user1:
Metric should be numeric.

diff_hunk: @@ -0,0 +1,371 @@
+new_mlflow_client <- function(tracking_uri, server_url = NULL) {
+  structure(
+    list(
+      tracking_uri = tracking_uri,
+      server_url = server_url %||% tracking_uri
+    ),
+    class = "mlflow_client"
+  )
+}
+
+#' Initialize an MLflow client
+#'
+#' @param tracking_uri The tracking URI
+#'
+#' @export
+mlflow_client <- function(tracking_uri = NULL) {
+  tracking_uri <- tracking_uri %||% mlflow_get_tracking_uri()
+  server_url <- if (startsWith(tracking_uri, "http")) {
+    tracking_uri
+  } else if (!is.null(mlflow_local_server(tracking_uri)$server_url)) {
+    mlflow_local_server(tracking_uri)$server_url
+  } else {
+    local_server <- mlflow_server(file_store = tracking_uri, port = mlflow_connect_port())
+    mlflow_register_local_server(tracking_uri = tracking_uri, local_server = local_server)
+    local_server$server_url
+  }
+
+  new_mlflow_client(tracking_uri, server_url = server_url)
+}
+
+#' Create Experiment
+#'
+#' Creates an MLflow experiment.
+#'
+#' @param name The name of the experiment to create.
+#' @param artifact_location Location where all artifacts for this experiment are stored. If
+#'   not provided, the remote server will select an appropriate default.
+#' @template roxlate-client-optional
+#'
+#' @export
+mlflow_create_experiment <- function(name, artifact_location = NULL, client = NULL) {
+  UseMethod("mlflow_create_experiment", client)
+}
+
+#' @export
+mlflow_create_experiment.mlflow_client <- function(name, artifact_location = NULL, client = NULL) {
+  name <- forge::cast_string(name)
+  response <- mlflow_client_create_experiment(client, name, artifact_location)
+  invisible(response$experiment_id)
+}
+
+#' Create Run
+#'
+#' reate a new run within an experiment. A run is usually a single execution of a machine learning or data ETL pipeline.
+#'
+#' MLflow uses runs to track Param, Metric, and RunTag, associated with a single execution.
+#'
+#' @param experiment_id Unique identifier for the associated experiment.
+#' @param user_id User ID or LDAP for the user executing the run.
+#' @param run_name Human readable name for run.
+#' @param source_type Originating source for this run. One of Notebook, Job, Project, Local or Unknown.
+#' @param source_name String descriptor for source. For example, name or description of the notebook, or job name.
+#' @param start_time Unix timestamp of when the run started in milliseconds.
+#' @param source_version Git version of the source code used to create run.
+#' @param entry_point_name Name of the entry point for the run.
+#' @param tags Additional metadata for run in key-value pairs.
+#' @template roxlate-client
+#' @export
+mlflow_create_run <- function(
path: mlflow/R/mlflow/R/tracking-client.R
text: @user1:
Can you clarify why is some of the client functionality in methods and some as a function? Is there a difference between them?

@user1:
Oh actually I think I understand after reading the fluent section. 

Hmm I am not sure if method overloading on client==NULL (if I understand correctly) is a good idea in this case, because one version has a run_id as a mandatory argument while the other ignores it and uses the active run. Maybe I don't understand correctly, but what about the following code:
`
`
`
mlflow_start_run() # active run == run1
`
`
mlflow_log_metric(key="metric1", value=1, run_id="run2", client = mlflow_client()) # this should be logged under run2 I believe?`
`
mlflow_log_metric(key="metric1", value=1, run_id="run2") # is this logged under run1?
`
`
`
If I understand it correctly, both of the lines above would work but they would log the metric under different run?  I think it might be better to explicitly call mlflow_client_log_metric() if that is the case.