AIR CLI Integration: air list Functionality & UI (Interfacing with Training Service) - #5684
Merged
Conversation
Add the experimental `air` command group as the Go port surface for the Python `air` CLI. Every subcommand (run, status, list, logs, cancel, register-image) is registered as a stub that returns a not-implemented error; the real implementations land in later milestones. The package lives under experimental/air/cmd (imported as aircmd), matching the layout of the other experimental features (aitools, genie, postgres); cmd/experimental/ keeps only the dispatcher. TEST_PACKAGES in Taskfile.yml gains ./experimental/air/... so the unit tests keep running after the move. Includes unit tests for the command-tree wiring and the not-implemented stubs, plus an acceptance test exercising the stubs end-to-end. Co-authored-by: Isaac
Rename the run-details subcommand from `status` to `get`, matching the Python air CLI's current `air get run` naming (it replaced `get status`). Renames the file, constructor, command name, and updates the stub/help/unimplemented tests and goldens accordingly. Co-authored-by: Isaac
Rename the RUN_ID arg placeholder to JOB_RUN_ID across get/logs/cancel to disambiguate it from other run identifiers. Hide the `logs --review` flag to match the Python CLI (help=argparse.SUPPRESS), and add the `-i` shorthand for `register-image --interactive-authenticate`. Co-authored-by: Isaac
Implement the read-only run-details command (renamed from `status` to `get`).
It fetches a job run via the Jobs API and renders the run's status, start time,
duration, retries, experiment, accelerators, dashboard URL, MLflow deep-link,
and a foreach/sweep summary. Output is the air-style {v, ts, data} JSON envelope
under -o json, or a text view.
Renames the command-level identifiers (status -> get) while keeping the run's
"status" field/label. Adds format/mlflow/sweep/output helpers with unit tests
and an acceptance test, and drops `get` from the not-implemented stub coverage.
Co-authored-by: Isaac
Co-authored-by: Isaac
Co-authored-by: Isaac
The training-config block is command result data, but it was emitted via cmdio.LogString, which targets stderr. Write it to cmd.OutOrStdout() instead so it lands on stdout, matching the Python `air get`. Download/read failures stay on stderr as warnings. Co-authored-by: Isaac
`air get` derived Submitted and Duration from run-level start/end and truncated milliseconds to seconds. Port Python's _reported_attempt_timing so a retried run reports its latest attempt, and round to the nearest second to match Python's round(). Drops the run-level RunDuration shortcut, which diverged on retries. Co-authored-by: Isaac
mlflowURL resolved runs/get-output against Tasks[0], linking a retried run to its stale first attempt. Use the last task (latest attempt) to match Python (jobs_api_client.py:68). Co-authored-by: Isaac
…N with Python
In -o json mode, error paths now emit the structured error envelope
({v, ts, error:{code, kind, message, retryable}}) and exit non-zero, matching
the Python air CLI's print_json_error instead of letting the framework print a
bare "Error: ..." string. Covers invalid RUN_ID, run-not-found, backend
failures, and client/auth failures (wrapped PreRunE).
Also align the success envelope with the Python CLI:
- dashboard_url: construct {host}/jobs/runs/{id}?o={workspace_id} (via
CurrentWorkspaceID) instead of using the API's run_page_url
- started_at: datetime.isoformat() form ("+00:00" with microseconds), not
RFC3339 "Z"
- duration_seconds: rounded half-to-even to match Python's round()
- use run-level start/end times for started_at and duration_seconds, dropping
the last-attempt preference, which had no Python equivalent
Co-authored-by: Isaac
Revert the run-level timing change from the previous commit: started_at and
duration_seconds read from the last task's window again (reportedTiming),
matching the released Python `air` output, which reports the latest attempt.
The isoformat timestamp ("+00:00") and half-to-even rounding are kept.
Co-authored-by: Isaac
The runs/get-output call passed run_id via the query-param arg and a nil request body, which this endpoint rejects with "expected a map", so the MLflow link was never produced for completed runs. Pass run_id through the request arg instead (the SDK serializes it to the query string for GET), which sends a valid body and returns the gen_ai_compute_output run info. Failed runs without MLflow output still yield no link: get-output 404s for them, so mlflowURL returns nil as before. Co-authored-by: Isaac
…output Nest the run-status command under a `get` parent group so the command is `air get run JOB_RUN_ID`, mirroring the Python CLI (the JOB_RUN_ID arg name matches the sibling air commands and avoids confusion with the MLflow run id). Align the text output with Python's `air get run`: lead with the dashboard link (hyperlinked, falling back to the bare URL off a terminal) followed by a gap, then the training config, then the status table. The table uses Python's field order, "N/A" for empty cells, a "2006-01-02 15:04 UTC" Submitted timestamp, and terminal hyperlinks on the Run ID, Experiment, and MLflow Run cells (the MLflow Run cell shows the run's name from the MLflow REST API). The JSON envelope is unchanged. Also reformat the training-config YAML shown in text mode so multi-line fields (e.g. command) render as block literals instead of escaped one-liners. Co-authored-by: Isaac
Port `air list runs` from the Python air CLI: page through runs/list, keep AIR (gen_ai_compute_task) workloads, filter by user and the --filter keys (experiment glob, accelerator_type, num_accelerators), and render either an aligned text table or the air JSON envelope. MLflow links are fetched in parallel for the text table only, reusing the get command's format helpers via a BaseRun->Run adapter. Co-authored-by: Isaac
…tive UI Move `air list` off the Jobs API onto the AiWorkflowService ListTrainingWorkflows RPC (hand-rolled via client.Do, since the endpoint is PUBLIC_UNDOCUMENTED), so the AIR filtering, creator scoping, and MLflow-ID resolution live server-side. Replace the static table with an inline, navigable table (Bubble Tea + Lip Gloss + termenv): scroll with up/down, status colored by state, and a short MLflow hyperlink that Enter opens in the browser. Piped output, an explicit --limit, and empty results print the table once; `-o json` keeps the air envelope. --limit now defaults to all. Flatten the command to `air list` (no `runs` subcommand). Add cmdio.IsPagerSupported (stdin+stdout+stderr TTY) and promote termenv to a direct dependency. Co-authored-by: Isaac
Add page-at-a-time navigation to the interactive runs table: left/right move a screenful, home/end (g/G) jump to the ends. Line movement stays on up/down. Co-authored-by: Isaac
The interactive table now shows at most 20 runs per page (still bounded by terminal height), and left/right page by that amount. Co-authored-by: Isaac
Contributor
Approval status: pending
|
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 22, 2026 18:53 — with
GitHub Actions
Inactive
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 22, 2026 18:53 — with
GitHub Actions
Inactive
Collaborator
Integration test reportCommit: c7fdd86
20 interesting tests: 13 SKIP, 7 KNOWN
Top 28 slowest tests (at least 2 minutes):
|
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 22, 2026 20:06 — with
GitHub Actions
Inactive
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 22, 2026 20:06 — with
GitHub Actions
Inactive
- Default --limit to 20 (was: all). - Enter the interactive table only on a color TTY, so NO_COLOR falls back to the static table. - Fetch full page batches when client-side --filter keys are set, since most rows may be dropped before reaching the limit. Co-authored-by: Isaac
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 23, 2026 17:14 — with
GitHub Actions
Inactive
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 23, 2026 17:14 — with
GitHub Actions
Inactive
…amp repl The acceptance [TIMESTAMP] replacement now also consumes the trailing "+00:00" offset, so the committed started_at golden was stale after the rebase. Co-authored-by: Isaac
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 23, 2026 21:57 — with
GitHub Actions
Inactive
riddhibhagwat-db
temporarily deployed
to
test-trigger-is
June 23, 2026 21:57 — with
GitHub Actions
Inactive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Add
air listas a browsable view of the caller's recent AIR training runs.AiWorkflowService.ListTrainingWorkflowsRPC (GET /api/2.0/ai-training/workflows), called directly viaclient.Dosince the endpoint isPUBLIC_UNDOCUMENTEDand not modeled by the SDK. The server does the AIR filtering, creator scoping, MLflow-ID resolution, and pagination, so no Jobs-API logic lives in the CLI.air listrenders an inline, navigable table (Bubble Tea + Lip Gloss + termenv):↑/↓move a row,←/→page (20 rows/page),Enteropens the run's MLflow page,qquits. Status is colored by state and the MLflow column is a short clickable hyperlink.--limit, and empty results print the table once;-o jsonemits the air{v,ts,data}envelope unchanged.--limit(default: all),--active,--all-users, and client-side--filterkeys (experiment,accelerator_type,num_accelerators). Gateway timeouts (e.g. HTTP 504 on--all-users) return an actionable message.cmdio.IsPagerSupported; promotestermenvto a direct dependencyWhy
The
ai-trainingservice now owns the AIR-specific run logic server-side, soair listshould call its RPC rather than reimplementing run discovery against the Jobs API. The interactive table gives a browsable run list on par with the PythonairCLI anddatabricks jobs list-runs.Tests
TrainingWorkflow→row mapping,--filtermatching, status/accelerator/timestamp helpers, and the TUI model (navigation, paging, 20-row page cap, window scroll, quit, static render).acceptance/experimental/air/list(text + JSON) plushelpupdates;unimplementedno longer coversair listManual verification output:
