From d48c026205bea07e144f3afb5518eead994fb184 Mon Sep 17 00:00:00 2001 From: 9-71 <1853979230@qq.com> Date: Tue, 27 Oct 2026 13:21:29 +0800 Subject: [PATCH] docs(studio): consolidate documentation and reproduction guide --- README.md | 17 +- README_CN.md | 15 +- docs/f1_user_manual.md | 654 +----------------------------------- docs/studio/README.md | 55 +++ docs/studio/api.md | 119 +++++++ docs/studio/architecture.md | 52 +++ docs/studio/core.md | 101 +++--- docs/studio/job_schema.json | 292 ++++++++++++++++ docs/studio/runtime.md | 134 ++++---- docs/studio/security.md | 56 +++ docs/studio/service.md | 214 +++++------- docs/studio/testing.md | 171 ++++++++++ docs/studio/user_guide.md | 78 +++++ f1/README.md | 233 +------------ f1/RUNTIME_LIFECYCLE.md | 152 +-------- f1/handlers/README.md | 21 +- f1/handlers/USAGE.md | 26 +- 17 files changed, 1068 insertions(+), 1322 deletions(-) create mode 100644 docs/studio/README.md create mode 100644 docs/studio/api.md create mode 100644 docs/studio/architecture.md create mode 100644 docs/studio/job_schema.json create mode 100644 docs/studio/security.md create mode 100644 docs/studio/testing.md create mode 100644 docs/studio/user_guide.md diff --git a/README.md b/README.md index 647ddb9a..5aa21b13 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ For a deep dive into the design philosophy of MoE modules, detailed routing mech - [Training](#training) - [Inference](#inference) - [Export](#export) - - [Gradio Demo](#gradio-demo) + - [Studio Task Platform](#studio-task-platform) - [Community & Contributing](#-community--contributing) - [License](#-license) - [Acknowledgements](#-acknowledgements) @@ -705,18 +705,19 @@ model.export(format="engine", half=True) # Export to TensorRT # formats: onnx, openvino, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs ``` -### Gradio Demo +### Studio Task Platform -Launch a local web interface to test the model interactively. This application provides a user-friendly Gradio dashboard for model inference, supporting automatic model scanning, task switching (Detection, Segmentation, Classification), and real-time visualization. +Launch Studio to submit and manage model tasks in your browser. The platform supports training, validation, prediction, export and environment diagnostics. View job status and logs, cancel tasks, and preview or download results. + +Run from the repository root: ```bash -python start_studio.py -# Open http://127.0.0.1:7860 in your browser +python -m pip install -e ".[studio]" +python main_engine.py +# Open http://127.0.0.1:8000 in your browser ``` -This recommended launcher starts both the FastAPI Studio Job API and the Gradio WebUI. For manual two-process startup, -run `python main_engine.py` first and then `python app.py` in a second terminal. `python app.py` remains a Gradio-only -entrypoint and requires the API configured by `F1_STUDIO_API_URL` (default: `http://127.0.0.1:8000`). +See the [Studio documentation](docs/studio/README.md) and [user guide](docs/studio/user_guide.md) for more startup options and usage instructions. ## 🤝 Community & Contributing diff --git a/README_CN.md b/README_CN.md index 8c499651..75e6ab79 100644 --- a/README_CN.md +++ b/README_CN.md @@ -111,7 +111,7 @@ - [训练](#训练) - [推理](#推理) - [导出](#导出) - - [Gradio 演示](#gradio-演示) + - [Studio 任务平台](#studio-任务平台) - [社区与贡献](#-社区与贡献) - [许可证](#-许可证) - [致谢](#-致谢) @@ -692,15 +692,20 @@ model.export(format="engine", half=True) # 导出为 TensorRT # 格式: onnx, openvino, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs ``` -### Gradio 演示 +### Studio 任务平台 -启动本地 Web 界面以交互式测试模型。此应用程序提供了一个用户友好的 Gradio 仪表板,用于模型推理,支持自动模型扫描、任务切换(检测、分割、分类)和实时可视化。 +启动 Studio 任务平台,在浏览器中提交和管理模型任务。支持训练、验证、预测、导出和环境诊断,可查看任务状态和运行日志、取消任务,并预览和下载结果文件。 + +在仓库根目录运行: ```bash -python app.py -# 在浏览器中打开 http://127.0.0.1:7860 +python -m pip install -e ".[studio]" +python main_engine.py +# 在浏览器中打开 http://127.0.0.1:8000 ``` +更多启动方式和操作说明见 [Studio 文档](docs/studio/README.md)和[用户指南](docs/studio/user_guide.md)。 + ## 🤝 社区与贡献 我们欢迎贡献!有关如何参与的详细信息,请查看我们的 [贡献指南](CONTRIBUTING.md)。 diff --git a/docs/f1_user_manual.md b/docs/f1_user_manual.md index 0061ee24..bf1dfb6d 100644 --- a/docs/f1_user_manual.md +++ b/docs/f1_user_manual.md @@ -1,652 +1,6 @@ -# F1 Task Platform — User Manual +# User guide moved -> **Audience**: end-users and evaluation reviewers. -> **Scope**: submitting and monitoring detection jobs through the Gradio Web UI and the standalone REST engine (FastAPI) — not framework development. +See the [Studio user guide](studio/user_guide.md) for setup and daily use. -**Version**: 1.2 · **Last synchronized**: 2026-09-15 - ---- - -## 1. Introduction - -The **F1 Task Platform** is the unified, browser-based job console of YOLO-Master. It wraps the -training / validation / inference / export engines behind a single submission form and an incremental -monitoring dashboard. You never touch training or inference internals — the platform acts as an -orchestration layer that accepts a job, enforces security rules, forwards the work to the underlying -Ultralytics engine, and collects the results back for you. - -The platform provides four core capabilities: - -| Capability | What it does | -|---|---| -| **Unified submission** | One form dispatches every task type — `predict`, `train`, `val`, `export`, and `diagnose`. | -| **Task dispatcher** | The manager routes each admitted job through a worker, dispatcher, and registered handler. | -| **Incremental logging** | The consoles poll sanitized structured logs with an HTTP cursor while the job runs. | -| **Artifact management** | Generated files (weights, metrics, plots, exported models) are listed, previewed, and exposed for download. | - -### 1.1 Supported task types - -| Task type | Purpose | -|---|---| -| `predict` | Run object detection on images or videos (single file, directory, or batch). | -| `train` | Fine-tune a model on a dataset defined by a `data.yaml` file. | -| `val` | Validate a checkpoint and compute metrics (mAP50, mAP50‑95, precision, recall). | -| `export` | Convert a checkpoint to a deployment format (ONNX, TorchScript, etc.). | -| `diagnose` | Collect a system environment report (Python, PyTorch, CUDA, GPU). | - -### 1.2 Job lifecycle at a glance - -Every job follows the public lifecycle below. `JobsManager` owns admission, queueing, -cancellation, timeout, and terminal publication. Inside the worker, the dispatcher enforces -`PENDING → RUNNING → COMPLETED/FAILED`; `CANCELLED` is published by the manager after it has -stopped the owned process tree. Terminal status/result/error snapshots cannot be restarted or reset. - -``` -PENDING ──► RUNNING ──► COMPLETED (success) - │ │ - ├────────────┴──► FAILED (failure or timeout) - └───────────────► CANCELLED (user cancellation) -``` - ---- - -## 2. Quick Start & UI Launch - -### 2.1 Prerequisites - -The platform runs in the same environment as YOLO-Master. The recorded verification used -Windows/Python 3.12 and by the Ubuntu/Python 3.10 F1 CI job; these are verification environments, -not an additional package compatibility guarantee beyond the project metadata. - -| Component | Requirement | -|---|---| -| Python | 3.10 – 3.12 (verified on 3.12.10) | -| PyTorch | 2.5.1+ (CUDA 12.1 build recommended) | -| Ultralytics | 8.4.x | -| CUDA (optional) | 12.1 with a compatible driver — used for GPU acceleration | -| Python packages | `gradio`, `fastapi`, `uvicorn[standard]`, `opencv-python`, `numpy`, `pandas`, `pydantic` | - -> **Tip** — Verify your environment before launching: -> -> ```bash -> python --version -> python -c "import torch; print(torch.cuda.is_available())" # True = GPU available -> python -c "from ultralytics import YOLO; print(YOLO.__version__)" -> ``` - -Install the project in editable mode if you have not already: - -```bash -pip install -e . -``` - -### 2.2 Launching the Web UI - -From the **project root directory**, start the server: - -```bash -python start_studio.py -``` - -On startup the app: - -1. Starts and health-checks the FastAPI Studio Job API. -2. Creates a `ckpts/` checkpoint directory if it does not exist. -3. Scans `ckpts/` (recursively) for `.pt` weights and categorizes them by task. -4. Serves the Gradio interface and opens it in your default browser. - -For manual startup, run `python main_engine.py` first, then run `python app.py` in another terminal. The latter starts -Gradio only and connects to `F1_STUDIO_API_URL` (default: `http://127.0.0.1:8000`). - -> **Note** — `main_engine.py` is the **standalone FastAPI engine**, a production entry point -> that runs independently of Gradio. See §7 for its full HTTP API, interactive docs, and -> environment configuration. - -> **Tip** — The interface is served on the default Gradio address -> `http://127.0.0.1:7860` and opens automatically (`inbrowser=True`). If the -> browser does not open, navigate to that URL manually. - -### 2.3 The interface layout - -The UI is organized into two top-level tabs: - -- **🖼️ Inference Studio** — the interactive, synchronous inference playground. -- **📋 Jobs** — the asynchronous task console described in this manual. - -A top-level **Language** selector (English / 中文) switches the entire interface in one place. - -```text -+-------------------------------------------------------------------------------+ -| YOLO-Master Web Console [ 🌐 Language: English / 中文 ] | -+-------------------------------------------------------------------------------+ -| [ 🖼️ Inference Studio ] | [ 📋 Jobs (Active) ] | -+-----------------------------------+-------------------------------------------+ -| LEFT PANEL: Job Submission Form | RIGHT PANEL: Lifecycle & Diagnostics | -| | [ 📊 Status ] [ 📜 Logs ] [ 📁 Artifacts ]| -| - Task Type: [predict/train/...] | ---------------------------------------- | -| - Model Path: ckpts/yolov8n.pt | Job ID: 20260908-120000-xxxx | -| - Data Source: assets/bus.jpg | Status: RUNNING | -| - Output Dir: runs/predict | ---------------------------------------- | -| - [⚙️ Hyperparameters] | Console Stream: | -| - [🔒 Security Constraints] | [INFO] Model loaded successfully... | -| | [INFO] Processing batch 1/1... | -| [ 🔥 Submit Job ] | [ 🚫 Cancel Job ] | -+-----------------------------------+-------------------------------------------+ -``` - ---- - -## 3. Job Submission Workflow - -All asynchronous work is submitted through the **Jobs** tab. The submission panel is on the left; -the monitoring panels are on the right. - -### 3.1 Step-by-step submission - -1. Open the **📋 Jobs** tab. -2. Select a **Task Type** (`predict`, `train`, `val`, `export`, or `diagnose`). - Selecting a task auto-fills the form with sensible defaults for that task. -3. Fill in **Model Path**, **Data Source**, and **Output Directory** (see the parameter tables below). -4. (Optional) Open **⚙️ Hyperparameters** and adjust **Confidence Threshold** and **Device**. -5. (Optional) Open **🔒 Security Constraints** to inspect the compatibility path list. The - FastAPI service does not trust or extend access from this client-controlled value. -6. Click **🔥 Submit Job**. The generated **Job ID** appears in the *Status Monitor*. - -### 3.2 Form field reference - -| Field | Meaning | Default | -|---|---|---| -| **Task Type** | Which handler will run the job. | `predict` | -| **Model Path** | Path to model weights (`.pt`) or, for `train`, an architecture YAML. | `./ckpts/yolov8n.pt` | -| **Data Source** | Input data: an image/video path, a directory of media, or a dataset YAML for `train`/`val`. | `ultralytics/assets/bus.jpg` | -| **Output Directory** | Base directory where per-job results are written. | `runs/predict` | -| **Confidence Threshold** | Detection confidence cutoff, in `(0.0, 1.0]`. | `0.25` | -| **Device** | Compute device: `0` (GPU), `cpu`, or `mps`. | `0` | -| **Allowed Paths** | Compatibility field serialized by the client. The server discards it and applies its own trusted roots. | `., ultralytics/assets, runs, ckpts` | - -> **Security boundary** — editing **Allowed Paths** cannot grant filesystem access. The server -> independently validates model, data, and output locations against `F1_MODEL_ROOTS`, -> `F1_DATA_ROOTS`, and `F1_OUTPUT_ROOT`. - -The **🔄 Reset** buttons next to *Model Path* and *Data Source* restore the active task’s default -value without changing the selected task type. - -### 3.3 Per-task presets and requirements - -The form repopulates itself when you change the task type: - -| Task type | Model Path preset | Data Source preset | Output Dir preset | Additional notes | -|---|---|---|---|---| -| `predict` | `./ckpts/yolov8n.pt` | `ultralytics/assets/bus.jpg` | `runs/predict` | Accepts image/video/directory/list. | -| `train` | `./ckpts/yolov8n.pt` | `coco8.yaml` | `runs/train` | `data_source` **must** be a `.yaml`/`.yml`. Defaults: 1 epoch, `imgsz=640`. | -| `val` | `./ckpts/yolov8n.pt` | `coco8.yaml` | `runs/val` | `data_source` **must** be a `.yaml`/`.yml`. Default `imgsz=640`. | -| `export` | `./ckpts/yolov8n.pt` | *(empty)* | `runs/export` | Exports to ONNX by default. | -| `diagnose` | *(empty)* | *(empty)* | `runs/diagnose` | No inputs required; reads system state only. | - -> **Warning** — `train` and `val` reject any non-YAML data source at validation time. Submitting -> an image path (e.g. `bus.jpg`) for these tasks fails immediately with -> `PARAM_VALIDATION_FAILED` rather than hanging mid-run. - -### 3.4 Data Source formats for `predict` - -The `predict` handler normalizes the *Data Source* into a deterministic file list: - -- A **single file** path → one input. -- A **directory** → all supported media files inside it, sorted (recursive-free). -- A **list** of paths → expanded entry-by-entry, order preserved. -- An explicitly authorized `http`, `https`, `rtmp`, `rtsp`, or `tcp` URL → one - network input. The hostname must appear in the server's `F1_NETWORK_INPUT_HOSTS`; - network inputs are rejected by default. - -Supported media extensions: `.jpg .jpeg .png .bmp .webp .tif .tiff .mp4 .avi .mov .mkv .ts`. -Inputs are split into batches (`batch_size` defaults to 8), and each batch is written to its own -`batch_NNN` sub-folder under the job directory. - ---- - -## 4. Job Monitoring & Lifecycle Management - -### 4.1 Navigating the Jobs tab - -The right-hand panel has four monitoring sub-tabs: - -| Sub-tab | Purpose | -|---|---| -| **📊 Status Monitor** | Current job ID, structured status, error diagnostics, and the **Cancel** button. | -| **📜 Live Logs** | Streaming console output for the selected job. | -| **📁 Artifacts** | Generated-file list with image preview and download access. | -| **🕒 Recent Jobs** | Newest-first history of every submitted job. | - -### 4.2 Job states - -The *Status Monitor* shows a JSON snapshot containing the canonical job state: - -Public job statuses: `pending | running | completed | failed | cancelled`. - -| State | Meaning | Terminal? | -|---|---|---| -| `PENDING` | Job accepted but not yet started. | No | -| `RUNNING` | Handler is executing on the engine. | No | -| `COMPLETED` | Execution finished successfully; artifacts captured. | Yes | -| `FAILED` | Execution failed — see the error code for the cause. | Yes | -| `CANCELLED` | Execution stopped after a user cancellation request; see `USER_CANCELLED`. | Yes | -| `NOT_FOUND` | The selected Job ID does not exist. | Yes | - -While a job is `PENDING` or `RUNNING`, the console refreshes **every second** and stops -automatically once the job reaches a terminal state. A slower **30-second** background sync keeps -the *Recent Jobs* table and final artifacts fresh at all other times. - -### 4.3 Job cancellation - -1. Select the job (its Job ID must be shown in the *Status Monitor*). -2. Click **🚫 Cancel Job**. - -Cancellation is manager-owned. For a waiting job, `JobsManager` can publish cancellation without -starting a process. For a running job, it records the request, asks the managed worker to stop, -escalates through the platform process-tree cleanup path when needed, joins and closes the worker, -and only then publishes `CANCELLED / USER_CANCELLED`. Dispatcher/handler checkpoints provide an -additional cooperative path between engine calls, but do not replace manager-owned termination. - -> **Warning** — constraints on cancellation: -> - You can only cancel a job that is `PENDING` or `RUNNING`. A job already in `COMPLETED` or -> `FAILED` or `CANCELLED` is terminal and cannot be cancelled. -> - Cancellation marks the job as `CANCELLED` with error code `USER_CANCELLED`; it is *not* resumable. -> Re-submit the job to run it again. - -### 4.4 Timeouts - -Every job carries a **default execution limit of 300 seconds** (`timeout_seconds = 300`). - -- The deadline starts when a queue slot launches the managed worker. Time spent waiting in - `PENDING` does not consume the timeout. -- `JobsManager` supervises the managed process against this deadline, stops and joins the owned - process tree, and then publishes `FAILED / TIMEOUT`. -- A timeout never produces a partial `COMPLETED` result. - -> **Tip** — Long-running `train` jobs with many epochs can exceed the 300-second limit. Keep early -> experiments short (the default is 1 epoch), and watch the elapsed-time column in the *Status -> Monitor*. - ---- - -## 5. Live Diagnostics & Log Inspection - -### 5.1 Viewing incremental output - -Open the **📜 Live Logs** sub-tab while a job is active. The console polls the HTTP log endpoint -and displays the available structured entries, including: - -- Submission and state-machine transitions (`transitioned to: RUNNING/COMPLETED/FAILED/CANCELLED`). -- Handler progress (e.g. the number of artifacts captured). -- A sanitized environment audit line. -- Sanitized tracebacks captured by the dispatcher for failed execution. - -The log panel keeps up to the most recent 100 lines and refreshes automatically. - -The sanitizer covers controlled job log entries, structured errors, and persisted fields. Output -written directly to stdout/stderr by Ultralytics or another third-party component is not guaranteed -to be captured by this API log stream or processed by the sanitizer. A terminal job's state, -result, and error are immutable; historical or already-in-flight log entries may remain readable -without changing that terminal snapshot. - -### 5.2 Error codes and troubleshooting - -The *Status Monitor* shows the `error_code` and `error_message`. Common causes: - -| Error code | What it means | Recommended fix | -|---|---|---| -| `SEC_ERR_001` | Security policy violation: a path fell outside the whitelist, shell execution was requested, or whitelisting was disabled. | Ensure every path in *Model Path*, *Data Source*, and *Output Directory* lives under a server-configured trusted root (see §7.10 and Appendix — Security guarantees). | -| `PARAM_VALIDATION_FAILED` | A parameter was missing, out of range, or of the wrong type. | Check required fields (e.g. `model_path`, `data_source`) and value bounds (e.g. confidence in `(0.0, 1.0]`). | -| `TASK_TYPE_UNKNOWN` | The submitted task type is not registered. | Use one of `predict`, `train`, `val`, `export`, `diagnose`. | -| `USER_CANCELLED` | The job was cancelled by the user. | Re-submit the job if you need the result. | -| `TIMEOUT` | Execution exceeded the 300-second deadline. | Reduce the workload (fewer epochs / smaller inputs) or re-run. | -| `EXEC_ERR_500` | An unhandled exception occurred in the handler. | Inspect the live logs for the captured traceback. | -| `HANDLER_EXEC_FAILED` | The handler reported a controlled failure (e.g. no supported media files, model load error). | Read the specific message in the *Error Diagnostics* box. | - -### 5.3 Common scenarios - -| Symptom | Likely cause | Guidance | -|---|---|---| -| **Out-of-memory (OOM) on GPU** | Model + batch too large for VRAM. | Reduce `batch_size`, use `device=cpu`, or free the GPU by restarting the app. | -| **"no supported media files found"** | `predict` pointed at a directory with no supported files. | Point *Data Source* at a valid image/video file or directory. | -| **"Not a YAML file" / validation reject** | `train`/`val` received a non-YAML data source. | Supply a dataset `.yaml` (e.g. `coco8.yaml`). | -| **Job stuck in `PENDING`/`RUNNING`** | A previous process died mid-execution. | Orphaned active jobs are healed to `FAILED` on the next app restart. | -| **"Output directory does not exist"** | You tried to open the output folder before any artifact was written. | Wait for `COMPLETED`, then reopen. | - ---- - -## 6. Artifact Retrieval & Verification - -Handlers write outputs under `//` and collect existing files into the job's -artifact manifest. The manager normalizes those files to safe relative artifact IDs before the API -exposes them. Failed or cancelled jobs may leave partial files on disk, but only manifest entries -are downloadable through the API. - -### 6.1 Locating artifacts - -Open the **📁 Artifacts** sub-tab. The *Generated Artifacts* table lists each file with: - -- **Filename** — the file’s base name. -- **Path** — in the same-host Gradio client, a resolved local source path when it can be safely - reconstructed; otherwise an API download URL. - -Alternatively, click **📂 Open Folder** to open the job’s specific output directory in your -operating system’s file manager (Windows Explorer / macOS Finder / Linux file manager). - -### 6.2 Previewing image artifacts - -The *Artifact Preview* pane renders the first image file produced by the job. When a job produces -multiple images, **◀ Prev** / **Next ▶** buttons and a selector dropdown become available to cycle -through them. Clicking an image row in the artifacts table also loads it into the preview. - -> **Tip** — Selecting a non-image row (e.g. a `.pt` or `.csv` file) does not change the preview; a -> short notice explains that the file cannot be rendered. - -### 6.3 What each task produces - -| Task type | Representative artifacts | -|---|---| -| `predict` | Annotated images/labels saved per batch under `.../job_id/batch_NNN/`. | -| `train` | `best.pt`, `last.pt`, `results.csv`, `results.png`, confusion matrix and curve plots, sample batches (`train_batch*.jpg`, `val_batch*_labels.jpg`). | -| `val` | `results.csv`, `confusion_matrix.png`, `F1_curve.png`, `PR_curve.png`, `P_curve.png`, `R_curve.png`, sample validation batches. | -| `export` | The exported model file (e.g. `yolov8n.onnx`) plus the job-local `.pt` copy. | -| `diagnose` | `system_diagnostics.json` and `system_diagnostics.txt`. | - -> **Note** — Production handlers capture artifacts with a sorted **full-tree scan** of the job -> directory. The API filters the result to existing files below that job root and serves only exact -> manifest matches; it does not expose a general filesystem browser. - -### 6.4 Verifying exported models - -For `export` jobs, the exported model (e.g. `yolov8n.onnx`) is written *inside* the job’s own -directory, next to a job-local copy of the source weights. This isolation guarantees that concurrent -exports never overwrite each other and that the original model directory is never modified. Locate -the `.onnx` file in the artifacts table and verify it exists on disk at the listed path. - ---- - -## 7. Standalone REST Engine (FastAPI) - -The **standalone REST engine** (`main_engine.py`) exposes the same F1 task dispatcher as a plain HTTP -API with native OpenAPI documentation. It has no Gradio dependency: it serves jobs, logs, cancellation -and artifact delivery directly, so a reviewer can verify the platform end-to-end with only a web -browser and command-line tools such as `curl`. - -### 7.1 Starting the engine - -Any of the following starts the engine: - -| Command | What it does | -|---|---| -| `python start_studio.py` | One-command launcher: starts the FastAPI engine (auto-started if `/health` is not ready), then launches the Gradio WebUI. | -| `python main_engine.py` | Runs only the standalone FastAPI engine (Gradio-free). | -| `uvicorn main_engine:app --host 127.0.0.1 --port 8000` | Direct ASGI launch for embedded/development setups. | - -Verify liveness at any time: - -```bash -curl http://127.0.0.1:8000/health -# → {"status":"ok","service":"YOLO-Master F1 Task Engine"} -``` - -`/health` is a static liveness probe and does not depend on job state. - -### 7.2 Interactive API docs - -The engine exposes its own OpenAPI/Swagger surface, no extra tooling required: - -| URL | Purpose | -|---|---| -| `http://127.0.0.1:8000/docs` | Swagger UI — interactive, form-driven endpoint explorer. | -| `http://127.0.0.1:8000/openapi.json` | Raw OpenAPI schema consumed by clients/generators. | -| `http://127.0.0.1:8000/redoc` | Redoc reference view of the same schema. | -| `docs/api/job_schema.json` | Repository file: the kernel schema registry for `JobRequest` and related types. | - -### 7.3 Endpoint reference - -| Method & path | Summary | -|---|---| -| `POST /api/v1/jobs` | Submit a new job (`201 Created`). Accepts a `JobRequest` body; rejects malformed bodies with `422`, duplicates with `409`, and a full pending queue with `429`. | -| `GET /api/v1/jobs` | List recent jobs, newest first, with `limit`/`offset` pagination. | -| `GET /api/v1/jobs/{job_id}` | Current status and metadata of one job (`404` when unknown). | -| `POST /api/v1/jobs/{job_id}/cancel` | Request cooperative cancellation (`202` new, `200` idempotent replay, `404` unknown, `409` not cancellable). | -| `GET /api/v1/jobs/{job_id}/logs` | Sanitized execution logs with `offset`/`limit` windowing. | -| `GET /api/v1/jobs/{job_id}/artifacts` | Artifact manifest and image artifact IDs (`/static/artifacts/...` references). | -| `GET /static/artifacts/{job_id}/{artifact_id}` | Download one artifact file, manifest-gated (fail-closed). | -| `GET /health` | Liveness probe. | - -The job collection routes also accept a trailing slash (`/api/v1/jobs/`) for clients that disagree -with the canonical form. - -### 7.4 Job submission example - -Submit a CPU `predict` job with repository-relative paths (no machine-specific absolute paths): - -```bash -curl -X POST http://127.0.0.1:8000/api/v1/jobs \ - -H "Content-Type: application/json" \ - -d '{ - "job_id": "predict_demo_001", - "task_type": "predict", - "params": { - "model_path": "ckpts/yolov8n.pt", - "data_source": "ultralytics/assets/bus.jpg", - "conf": 0.25, - "device": "cpu" - }, - "output": { - "output_dir": "runs/predict" - } - }' -``` - -A successful submission returns `201 Created` with the server-normalized `JobRequest`. - -> **Important** — client-supplied security fields do **not** determine the final server whitelist. -> On every submission the engine forces `allow_shell=False` and `path_whitelisted=True`, discards the -> caller's `security_constraints.allowed_paths` / `allowed_path_patterns`, and replaces them with the -> server-configured trusted roots (`F1_MODEL_ROOTS` + `F1_DATA_ROOTS`). `model_path`, `data_source` -> and `output_dir` are then resolved independently and must each land inside their trusted root -> (`F1_OUTPUT_ROOT` for output). See §7.10. - -### 7.5 Status query - -```bash -curl http://127.0.0.1:8000/api/v1/jobs/predict_demo_001 -``` - -The response includes the canonical lifecycle status plus metadata: - -```json -{ - "job_id": "predict_demo_001", - "task_type": "predict", - "status": "completed", - "created_at": "2026-09-12T03:00:45.000000+00:00", - "started_at": "2026-09-12T03:00:46.000000+00:00", - "completed_at": "2026-09-12T03:00:52.000000+00:00", - "duration": 6.0, - "error_code": null, - "error_message": null, - "artifact_count": 2, - "metadata": { "created_by": "anonymous", "priority": "normal", "tags": [] } -} -``` - -`status` is one of `pending | running | completed | failed | cancelled`. - -### 7.6 Live Logs - -```bash -curl "http://127.0.0.1:8000/api/v1/jobs/predict_demo_001/logs?offset=0&limit=100" -``` - -- `offset` — number of lines to skip from the start (default `0`, `>= 0`). -- `limit` — maximum lines in this window (default `null` = the full remainder, capped at `10000`). -- `next_offset` — the cursor for the next page, or `null` once the tail is reached. - -Every structured line returned by this endpoint is sanitized: credentials (Bearer tokens, `sk-` -keys, `KEY=value` secrets) are redacted as `***REDACTED***` before the line reaches the response or -persisted state. This does not claim capture or sanitization of arbitrary child-process -stdout/stderr outside the structured log path. - -### 7.7 Cancellation - -```bash -curl -X POST http://127.0.0.1:8000/api/v1/jobs/predict_demo_001/cancel -``` - -Cancellation is asynchronous and manager-owned: the engine sets the cancellation flag, stops and -joins the owned process tree, then publishes `CANCELLED` with error code `USER_CANCELLED`. -Dispatcher/handler checkpoints supplement this process-level stop between engine calls. - -- `202 Accepted` — a fresh cancellation request was acknowledged. -- `200 OK` — an idempotent replay against a job already in a terminal state, returning its existing state. -- `409 Conflict` — the job is not cancellable. -- `404 Not Found` — the job is unknown. - -Terminal jobs (`completed` / `failed` / `cancelled`) cannot be re-cancelled; replaying the request is -safe and does not produce a second lifecycle transition. - -### 7.8 Artifacts / download - -```bash -curl http://127.0.0.1:8000/api/v1/jobs/predict_demo_001/artifacts -``` - -The response lists the handler-produced, manager-normalized artifact manifest (`artifacts`, with per-file -`download_url`) and safe image artifact IDs (`image_artifacts`). Server filesystem paths are never -returned. Download one file via its `download_url`: - -```bash -curl -O "http://127.0.0.1:8000/static/artifacts/predict_demo_001/results.csv" -``` - -Artifact delivery is **manifest-gated and fail-closed**: - -- Only files recorded by the dispatcher after a successful execution are served; unlisted files and - directory-traversal attempts return `404`. -- Each job's artifacts are isolated under its own `job_id` sub-directory, and the route performs an - exact manifest lookup rather than joining client input onto a filesystem path, so cross-job access - is blocked. - -### 7.9 Console entry points - -The platform provides three client entry points with different hosting relationships: - -| Entry point | Path | Notes | -|---|---|---| -| Zero-build console | `/` | Always mounted by FastAPI (`frontend/index.html` + `app.js`, plain ES6). | -| React console | `/console` | Served only when `web/dist` is present on disk. | -| Gradio WebUI | `http://127.0.0.1:7860` | Runs in the `start_studio.py` launcher process, separate from the API child service; it calls FastAPI through `StudioJobsApiClient`. | - -The repository contains React/TypeScript/Vite source under `web/`, but does not commit a guaranteed -`web/dist` deployment artifact. The current F1 CI workflow does not run Node lint, TypeScript -checking, or a Vite production build. - -### 7.10 Environment configuration - -The following `F1_*` variables are read by the engine/manager source. Variables whose separator is -`os.pathsep` accept multiple entries (`;` on Windows, `:` on Unix); comma-separated variables accept -a comma-delimited list. - -| Variable | Meaning | Default | -|---|---|---| -| `F1_JOBS_STATE_PATH` | API/backend JobsManager persistence file; Gradio observes it through the Studio Job API. | `runs/jobs_state.json` | -| `F1_ENGINE_HOST` / `F1_ENGINE_PORT` | Bind address for `python main_engine.py`. | `127.0.0.1` / `8000` | -| `F1_CORS_ORIGINS` | Comma-separated CORS allowlist; when non-empty it fully replaces the dev defaults. | dev defaults (§7.11) | -| `F1_MODEL_ROOTS` | Server-owned trusted model roots (`os.pathsep`-separated). | project root (cwd) | -| `F1_DATA_ROOTS` | Server-owned trusted data roots (`os.pathsep`-separated). | project root (cwd) | -| `F1_OUTPUT_ROOT` | Server-owned trusted output root (single). | `/runs` | -| `F1_NETWORK_INPUT_HOSTS` | Comma-separated hostnames allowed as network `data_source`. | *(empty — network inputs rejected)* | -| `F1_MAX_PENDING_JOBS` | Pending-job capacity before `429`. | `100` | -| `F1_CPU_CONCURRENCY` | CPU execution slots. | `2` | -| `F1_GPU_CONCURRENCY` | Accelerator execution slots. | `1` | -| `F1_STOP_GRACE_SECONDS` | Worker stop grace (0–30). | `2` | -| `F1_STUDIO_API_URL` | Studio API origin used by the Gradio client. | `http://127.0.0.1:8000` | - -### 7.11 CORS behavior - -By default the engine adds `CORSMiddleware` with `allow_methods=["*"]` and `allow_headers=["*"]`, and -an origin allowlist covering local development: `http://localhost:8000`, `http://127.0.0.1:8000`, -the `null` origin (for `file://` pages), and the local Vite/CRA/port `5173` / `3000` / `8080` -spellings. Setting `F1_CORS_ORIGINS` to a non-empty comma-separated list **replaces** these defaults -rather than appending to them. - -### 7.12 Relationship to the Gradio Jobs tab - -The FastAPI REST engine is the sole lifecycle owner. It owns the process-wide -`f1.jobs_manager.JobsManager`, workers, lifecycle state, and the `F1_JOBS_STATE_PATH` persistence file. -The Gradio **📋 Jobs** tab uses `StudioJobsApiClient` to submit, poll, cancel, and download through the -task API; it neither opens that state file nor creates another manager. The `JobsManager` name exported -from `f1/ui/jobs_tab.py` is only a lazy compatibility shim for legacy imports. - -### 7.13 Relationship to Agent Skills - -The F1 production runtime ends at `f1/handlers/` and does not dispatch through -`agent/runtime/cli/async_jobs.py` or an Agent worker. `agent/` is a separate skill/CLI integration -layer documented by `agent/SKILL.md`. Agent validation does not replace F1 API, lifecycle, handler, -or security tests. Shared task names are defined through the explicit contracts under `core/`. - ---- - -## 8. Recorded verification and limits - -The recorded review range is baseline -`acce839c7e895d6b179de7f7093fa879e237cc7b` to reviewed commit -`0e7a8f83b53c97abc8eb3caedc532f5779fbb086`. - -Windows/Python 3.12 verification in the repository `.venv` produced: - -```text -412 passed, 11 skipped, 3 warnings -78% combined f1/core coverage -F1 smoke: 5/5 passed -``` - -The 11 skips require Windows symlink privileges. The Ubuntu/Python 3.10 source-branch CI at the -reviewed commit produced `422 passed, 1 skipped, 3 warnings`, 78% coverage, smoke 5/5, and passing -Ruff lint/format: -. This is source-branch push evidence, -not a Tencent repository required PR check. - -Current operational limits: - -- one API process / one lifecycle owner; task execution uses spawned child processes -- local best-effort JSON persistence; no distributed durable queue, priority, or automatic retry -- static CPU/GPU slots; no memory-aware or per-GPU placement -- HTTP cursor polling; no WebSocket/SSE -- no authentication, RBAC, tenant isolation, or per-job OS sandbox -- Windows/Linux process containment verified; other POSIX platforms have weaker guarantees -- React deployment build and Node checks are not covered by the current F1 CI -- some OBB/classification metric displays remain incomplete - ---- - -## Appendix — Quick reference - -### Server-owned trusted roots - -The submission form may serialize `allowed_paths` for compatibility, but client-supplied -`allowed_paths` and `allowed_path_patterns` never grant filesystem access. The FastAPI backend -discards both fields and replaces them with its trusted `F1_MODEL_ROOTS` and `F1_DATA_ROOTS`; -`output_dir` is checked independently against `F1_OUTPUT_ROOT` (see §7.10). - -### Security guarantees - -- **Shell execution** is permanently disabled (`allow_shell = False`). -- **Path whitelisting** is always enforced (`path_whitelisted = True`) using server-owned roots; - the handler layer fails closed when its trusted whitelist is empty. -- **Directory traversal** (`../`) is neutralized by resolving paths to absolute form before the - containment check. -- **Structured log sanitization** redacts credentials (API keys, `KEY=value` secrets) before - controlled job entries reach the API console or persisted state. Arbitrary child stdout/stderr - is outside this guarantee. -- **Job isolation** uses a unique `job_id` sub-directory per job to prevent output collisions. - -### State persistence - -The FastAPI service makes a best-effort local persistence of job history to -`runs/jobs_state.json`, using temporary-file replacement. Jobs left in `PENDING`/`RUNNING` when the -service restarts are marked `FAILED / SERVICE_RESTARTED`; execution is not resumed. This is not a -transactional database or durable distributed queue. +The [previous F1 manual](https://github.com/Tencent/YOLO-Master/blob/b10cdf11c9f7aa6cb949595391572e0eed1bf0f1/docs/f1_user_manual.md) +preserves the earlier platform instructions and historical verification records. diff --git a/docs/studio/README.md b/docs/studio/README.md new file mode 100644 index 00000000..c6c18794 --- /dev/null +++ b/docs/studio/README.md @@ -0,0 +1,55 @@ +# YOLO-Master Studio + +Studio is an asynchronous task platform for model training and inference +workflows. It manages training, validation, prediction, export and diagnostic +tasks through a shared HTTP API. React, the built-in console and Gradio Jobs +are clients of this platform. + +Each submitted task is a **job** that runs in the background. You can follow its +status, read logs and download its output files. + +## Start from a source checkout + +Use Python 3.10 or later. From the repository root, install and start Studio: + +```bash +python -m pip install -e ".[studio]" +python main_engine.py +``` + +Keep the terminal open and visit [http://127.0.0.1:8000/](http://127.0.0.1:8000/). This built-in console +runs directly from the checkout; it needs neither Node.js nor Gradio. The same +service provides interactive API documentation at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs). + +The service binds to the local machine. See [security](security.md) for deployment +settings and access rules. + +## Submit and inspect a job + +Try an environment diagnostic first; it needs no model or dataset: + +1. Check **Engine** is `http://127.0.0.1:8000` and select **Apply**. +2. Choose **Task Type → diagnose**. Leave model and data fields empty. +3. Open **Advanced options**, set **Output Directory** to `runs/diagnose`, + and leave **Extra Params** empty. +4. Submit the job and select it in the job list. Wait for `completed`, then + read its logs and download the JSON and text reports from the artifact list. + +An **artifact** is a downloadable output file. Diagnostic reports contain local +environment information. If the job fails, read its error and logs before resubmitting. + +Stop the service with Ctrl+C in its terminal. Closing the browser keeps it running. + +## Next steps + +- [User guide](user_guide.md): choose a client, prepare model/data paths, + track jobs, cancel work and download results. +- [Clients and launcher](frontend.md): React development, Gradio startup and + process ownership. +- [API](api.md): request examples, errors, log cursors, downloads and the + [generated input schema](job_schema.json). +- [Service configuration](service.md): trusted roots, local origins and settings. +- [Architecture](architecture.md), [Core](core.md) and [Runtime](runtime.md): + execution layers, handlers, persistence and checkpoint recovery. +- [Security](security.md): supported deployment and file-access rules. +- [Testing and reproduction](testing.md): automated checks and manual procedures. diff --git a/docs/studio/api.md b/docs/studio/api.md new file mode 100644 index 00000000..d241e719 --- /dev/null +++ b/docs/studio/api.md @@ -0,0 +1,119 @@ +# Studio HTTP API + +[Studio documentation](README.md) · [Service configuration](service.md) + +Run `python main_engine.py` from the repository root. The default origin is +`http://127.0.0.1:8000`. `/docs` and `/openapi.json` describe the running HTTP +contract. Collection routes accept both `/api/v1/jobs` and `/api/v1/jobs/`. + +## Submit a local diagnostic + +With the API running in another terminal, this standard-library Python example +submits a diagnostic without model weights. Run it from the source checkout; +the example uses the default server output root `runs`: + +```python +import json +from urllib.request import Request, urlopen + +payload = { + "job_id": "docs-diagnose-001", + "task_type": "diagnose", + "output": {"output_dir": "runs"}, +} +request = Request( + "http://127.0.0.1:8000/api/v1/jobs", + data=json.dumps(payload).encode("utf-8"), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urlopen(request, timeout=10) as response: + print(response.status, json.load(response)["job_id"]) +``` + +Expect HTTP 201 and the submitted ID. Use a new ID for each run; duplicates +receive 409. Query `GET /api/v1/jobs/docs-diagnose-001` until terminal, then read +its `/logs` and `/artifacts`. Diagnostic reports describe the local environment. +For other tasks, `params` carries handler-specific values such as `model_path`, +`data_source`, `device`, `epochs` +and `imgsz`; paths must satisfy the server's independent trusted-root policy. +The handler validates task parameters during execution; query the job's final +status for the outcome. + +## Status, errors and cancellation + +Jobs have five states: `pending`, `running`, `completed`, `failed` and +`cancelled`. The last three are final states. Admission and cancellation +acknowledgements precede execution/cleanup; poll the job for its final outcome. + +- `POST /api/v1/jobs`: 201 accepted; 409 duplicate ID; 429 `QUEUE_FULL`; + 422 invalid schema or input path; 503 `SERVICE_UNAVAILABLE`, + `SERVICE_SHUTDOWN` or `PERSISTENCE_FAILED`. +- `GET /api/v1/jobs?limit=10&offset=0`: atomic recent-job page and total. + `limit` is 1–100, `offset` is nonnegative. +- `GET /api/v1/jobs/{job_id}`: status, error code/message, metadata, + artifact count and numeric duration. Duration excludes queue time; an absent + start is null and an actual zero duration is `0.0`. +- `POST /api/v1/jobs/{job_id}/cancel`: 202 `cancel_requested` acknowledges + cancellation; 200 replays an already terminal job; 409 means not cancellable; + 404 means unknown job. Continue polling after 202 until cleanup is confirmed. +- Unknown jobs and missing artifacts return 404. Unsupported local-boundary + requests receive 403 `LOCAL_ONLY`; disallowed CORS preflights receive 400. + +Structured transport errors use `detail.code` and `detail.message`. Existing +duplicate, path, unknown-job and missing-artifact details remain strings; +schema/query validation returns a sanitized error list. Handle these shapes +separately and use status/code for program decisions. +An admission persistence failure rolls back the unaccepted job and launches no +worker. A later write failure has a different [durability boundary](runtime.md#persistence-and-recovery): +execution truth can survive in memory without reaching disk. `/health` keeps +the compatibility liveness response and exposes no persistence-warning field. + +## Log cursors and artifacts + +`GET /api/v1/jobs/{job_id}/logs?offset=0&limit=200` pages sanitized flattened +lines. An explicit `limit` is 1–10000. Omitting it returns all currently +remaining lines from `offset`; there is no implicit 10000-line truncation. +Follow `next_offset` until null. Null means the current tail only; continue at +the prior `offset + len(logs)` for later polls, including terminal tail drains. +Changing job/API selection must invalidate older in-flight responses. + +`GET /api/v1/jobs/{job_id}/artifacts` returns exact relative `artifact_id` +values, names, image flags and `download_url` values. Download from +`GET /static/artifacts/{job_id}/{artifact_id:path}`, preserving nested IDs and +URL-encoding their components. Downloads recheck manifest membership, +containment and the identity of the opened file. Use the returned download URL. +A confirmed `weights/shutdown.pt` may also be +authorized after cleanup on a `failed / SERVICE_SHUTDOWN` job. Only listed +artifacts are downloadable. + +## Regenerate the domain schema + +[job_schema.json](job_schema.json) is generated from `core.schema.JobRequest` +in Pydantic validation mode. It describes domain fields; handler-specific +`params` and HTTP response models are documented separately. The service applies +its own lifecycle/security policy to validated inputs. The older +`docs/api/job_schema.json` describes the legacy F1 registry. + +From the repository root, run this Python block (or save it as a temporary script): + +```python +import json +from pathlib import Path + +from core.schema import JobRequest + +schema = JobRequest.model_json_schema(mode="validation") +# Drop only the volatile import-time timestamp annotation; validation is unchanged. +schema["properties"]["metadata"]["default"].pop("created_at", None) +Path("docs/studio/job_schema.json").write_text( + json.dumps(schema, indent=2, ensure_ascii=False, sort_keys=True) + "\n", + encoding="utf-8", + newline="\n", +) +``` + +Record the source revision, Python and Pydantic versions when regenerating. +Generate again in a fresh process and compare the parsed JSON and bytes; only +the timestamp annotation is intentionally omitted. Use the running OpenAPI for +complete endpoint/response contracts. diff --git a/docs/studio/architecture.md b/docs/studio/architecture.md new file mode 100644 index 00000000..d5514e5b --- /dev/null +++ b/docs/studio/architecture.md @@ -0,0 +1,52 @@ +# Studio architecture + +[Studio documentation](README.md) + +Studio separates browser interaction, HTTP handling and job execution. A job is +described by the shared `core.schema.JobRequest` model and runs in a worker +process managed by the API service. + +```text +React / built-in console / Gradio Jobs + | HTTP +main_engine.py + api/v1/jobs.py + | +studio.jobs_manager.JobsManager + | +CPU/GPU queues -> studio.worker_runtime.ManagedWorker + | +studio.dispatcher -> studio.handlers -> Ultralytics engines +``` + +## Layer responsibilities + +- [Core](core.md) validates job inputs, removes credentials from public text and + dispatches each task to its handler. Handlers adapt synchronous calls to the + Ultralytics engines. Direct callers supply trusted policy and validate inputs. +- [Runtime](runtime.md) admits and schedules jobs, owns public status and + timestamps, persists history and authorizes output files in an artifact + manifest. `AdmissionPolicy`, `JobStore`, `JobLogs` and `ArtifactResolver` + support these operations. `ManagedWorker` contains the OS process tree and + reports its outcome; the manager confirms cleanup before publishing a final + status or releasing a slot. +- [Service](service.md) creates one manager during application startup and waits + for its cleanup during shutdown (FastAPI's lifespan). HTTP requests read + snapshots and cancellation decisions from that manager. +- [Clients](frontend.md) submit and monitor jobs through HTTP. Gradio's + **Inference Studio** retains a separate synchronous inference path beside Jobs. + +## Compatibility + +Production workers use `studio.dispatcher` and the `studio.handlers` registry. +The retained `f1` backend and direct-call adapters are separate compatibility +implementations. `f1/ui` provides lazy compatibility imports for `studio/ui`. + +The service accepts legacy `F1_*` configuration aliases and retains the health +identity `YOLO-Master F1 Task Engine`. Version-1 snapshots and safe historical +artifact entries remain readable; see [service compatibility](service.md#history-and-lifecycle). +Each state file has one service owner. Restart reconciles interrupted jobs +without running them again. + +`agent/` has its own runtime, dispatcher and Skills. It does not participate in +Studio job execution. Deployment and access limits are documented in +[security](security.md). diff --git a/docs/studio/core.md b/docs/studio/core.md index 8a8b67db..c2e4f652 100644 --- a/docs/studio/core.md +++ b/docs/studio/core.md @@ -1,75 +1,54 @@ # Studio Core Task Engine -This layer supplies the JobRequest schema, credential sanitization, -path validation, dispatcher FSM, registry and synchronous train / val / predict / -export / diagnose handlers. Install from the checkout with `pip install -e ".[studio]"`. -The handlers use Ultralytics engines and the repository pytest/doctest settings. +Core translates a validated job into a synchronous Ultralytics engine call. +It supplies the `JobRequest` input model, path validation, credential sanitization, +a dispatcher that tracks execution state, and handlers for `train`, `val`, +`predict`, `export` and `diagnose`. Install with `python -m pip install -e ".[studio]"`. -Import `studio.dispatcher.JobDispatcherStateMachine` or `studio.handlers` before concurrent -execution; importing handlers registers all five classes. Register extensions before -dispatch threads start. Direct handlers have two steps: `validate_params(params, -constraints)`, then `execute(job_id, params, output_dir)`. The existing result is a -dict containing success, artifacts, metadata and error. The dispatcher returns the -updated JobRequest; its existing FSM and failure codes are retained. +## Dispatch and handlers + +Import `studio.dispatcher.JobDispatcherStateMachine` or `studio.handlers` before +concurrent execution. Importing handlers registers all five classes; extensions +belong in `studio/handlers/__init__.py` and must be registered before dispatch +threads start. The separate `f1.handlers` registry serves legacy direct calls. + +Direct handler calls have two steps: `validate_params(params, constraints)`, then +`execute(job_id, params, output_dir)`. Execution returns a dictionary containing +`success`, `artifacts`, `metadata` and `error`. The dispatcher returns an updated +`JobRequest`. Adding a public task requires a corresponding schema/API contract +change as well as handler registration. ## Trusted caller and safety boundary -Studio Core is an in-process library for trusted callers. The caller owns roots/regex -rules and must validate output_dir and the final job directory before execution. -Handler validation checks model/data paths against those trusted rules; direct -execute calls assume validated input. Do not accept untrusted roots or regex patterns. -allow_shell=false and path_whitelisted=true are required by the dispatcher. -Containment resolves symlinks, rejects broken symlinks and sibling-prefix escapes, -and rejects malformed-only regex rules. Empty roots/patterns reject input paths. +Core is an in-process library for trusted callers. The caller supplies path roots +and regex rules and validates `output_dir` and the final job directory. Handlers +validate model/data paths; direct `execute()` calls assume validated inputs. +The dispatcher requires `allow_shell=false` and `path_whitelisted=true`. +Containment resolves symlinks and rejects broken links, sibling-prefix escapes +and malformed-only regex rules. Empty roots/patterns reject input paths. -Returned artifact paths are discovery candidates. They do not authorize downloads; -a consuming runtime or service must normalize an authorized manifest and recheck -containment on every read. -append_log and dispatcher error/traceback handling sanitize credentials. Direct -handler results remain internal engine facts and require sanitization before a -public response or persistence boundary. +Returned artifact paths are candidates for a runtime's authorized file list. +The consuming runtime checks containment before authorizing or reading them. +`append_log` and dispatcher error/traceback handling sanitize credentials; +direct handler results require sanitization before public responses or storage. +See [security](security.md) for the production service's access rules. -The legacy direct dispatcher can report cancellation/timeout while a detached daemon -thread is still computing. It does not provide OS process-tree cleanup. The existing -managed=True path executes synchronously and expects external supervision. Core does -not implement a public lifecycle owner, queue, workers, recovery, API or shutdown. +The legacy direct dispatcher may report cancellation/timeout while a detached +daemon thread still computes. Use [Runtime](runtime.md) for managed process-tree +cleanup, queues and persistence. Core's `managed=True` path executes synchronously +under external supervision. ## Upstream compatibility -TrainHandler forwards explicitly supplied imgsz after the same integer normalization -used by val/export. Omitting imgsz preserves the upstream default. The optimizer -metadata summarizes trainer.optimizer_group_audit, recorded by upstream after final -optimizer construction; it does not perform a second audit. Missing audit snapshots -report audited=false. LoRA/router name detection alone is not coverage proof. +`TrainHandler` forwards explicitly supplied `imgsz` after the same integer +normalization used by val/export. Omitting it preserves the upstream default. +Optimizer metadata summarizes `trainer.optimizer_group_audit`, recorded after +upstream optimizer construction. A missing snapshot reports `audited=false`. ## Verification -Offline unit/fake-engine tests prohibit implicit model/data downloads: - -```powershell -python -m pytest tests/studio -q -python -m pytest --doctest-modules core studio -q -ruff check core studio tests/studio -ruff format --check core studio tests/studio -codespell core studio tests/studio -``` - -Real engine tests require both explicit flags and existing local detection weights: - -```powershell -$env:STUDIO_TEST_MODEL="D:\path\to\local-model.pt" -$env:YOLO_AUTOINSTALL="false" -python -m pytest tests/studio/test_core_integration.py --slow --studio-integration -q -``` - -These tests use generated images/data, CPU, one training epoch at imgsz=64, and a -TorchScript export. They never download weights/data. Ordinary upstream --slow does -not opt into them. They verify Core calls, not service lifecycle or checkpoint resume. -Directories containing symlink tests require symlink privileges; skipped cases are -unverified and must be rerun on a capable Windows environment and on Linux. - -## Runtime integration responsibilities - -A consuming runtime must own admission/output roots, public lifecycle decisions, -process supervision, sanitized snapshots, persistence recovery and manifest authorization. -Core does not provide checkpoint-aware shutdown, an HTTP service or a user interface. +Use the [offline lane](testing.md#offline-core-runtime-and-service-lane) for unit +checks and [local-model integration](testing.md#explicit-local-model-integration) +for real engine calls. Core integration uses generated images/data, one CPU +training epoch at `imgsz=64` and TorchScript export. Service lifecycle and +checkpoint resume are covered by the separate production integration tests. diff --git a/docs/studio/job_schema.json b/docs/studio/job_schema.json new file mode 100644 index 00000000..c58e7f96 --- /dev/null +++ b/docs/studio/job_schema.json @@ -0,0 +1,292 @@ +{ + "$defs": { + "ErrorInfo": { + "description": "Structured error attached to a ``FAILED`` job transition.", + "properties": { + "code": { + "title": "Code", + "type": "string" + }, + "message": { + "title": "Message", + "type": "string" + }, + "timestamp": { + "title": "Timestamp", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "title": "ErrorInfo", + "type": "object" + }, + "JobStatus": { + "description": "Lifecycle state of a job, enforced by the dispatcher state machine.", + "enum": [ + "pending", + "running", + "completed", + "failed", + "cancelled" + ], + "title": "JobStatus", + "type": "string" + }, + "Metadata": { + "description": "Job bookkeeping metadata attached to every ``JobRequest``.", + "properties": { + "completed_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Completed At" + }, + "created_at": { + "title": "Created At", + "type": "string" + }, + "created_by": { + "default": "anonymous", + "title": "Created By", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "priority": { + "default": "normal", + "title": "Priority", + "type": "string" + }, + "started_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Started At" + }, + "tags": { + "items": { + "type": "string" + }, + "title": "Tags", + "type": "array" + } + }, + "title": "Metadata", + "type": "object" + }, + "OutputConfig": { + "description": "Output destination and artifact-capture policy for a job.", + "properties": { + "artifacts": { + "items": { + "type": "string" + }, + "title": "Artifacts", + "type": "array" + }, + "output_dir": { + "default": "runs/predict/job_20260824_f1_001", + "title": "Output Dir", + "type": "string" + }, + "save_images": { + "default": true, + "title": "Save Images", + "type": "boolean" + }, + "save_labels": { + "default": false, + "title": "Save Labels", + "type": "boolean" + }, + "save_logs": { + "default": true, + "title": "Save Logs", + "type": "boolean" + } + }, + "title": "OutputConfig", + "type": "object" + }, + "RuntimeTracking": { + "description": "Runtime supervision knobs: streaming, deadline and cancellation.", + "properties": { + "cancel_requested": { + "default": false, + "title": "Cancel Requested", + "type": "boolean" + }, + "cancellable": { + "default": true, + "title": "Cancellable", + "type": "boolean" + }, + "stream_logs": { + "default": true, + "title": "Stream Logs", + "type": "boolean" + }, + "timeout_seconds": { + "default": 300, + "title": "Timeout Seconds", + "type": "integer" + } + }, + "title": "RuntimeTracking", + "type": "object" + }, + "SecurityConstraints": { + "description": "Fail-closed security policy enforced before and during job execution.\n\nRegex-enhanced whitelisting (P1): ``allowed_path_patterns`` holds additional\nregex patterns accepted by ``BaseTaskHandler._is_path_safe``. Patterns are\nmatched against the normalized resolved POSIX path and must consume the\nentire path string; malformed patterns fail closed. For backward\ncompatibility, entries in ``allowed_paths`` starting with ``^`` are also\ninterpreted as regex patterns by the handler layer.", + "properties": { + "allow_shell": { + "default": false, + "title": "Allow Shell", + "type": "boolean" + }, + "allowed_path_patterns": { + "items": { + "type": "string" + }, + "title": "Allowed Path Patterns", + "type": "array" + }, + "allowed_paths": { + "items": { + "type": "string" + }, + "title": "Allowed Paths", + "type": "array" + }, + "path_whitelisted": { + "default": true, + "title": "Path Whitelisted", + "type": "boolean" + } + }, + "title": "SecurityConstraints", + "type": "object" + }, + "TaskType": { + "description": "Task identifier accepted by the job dispatcher and handler registry.", + "enum": [ + "predict", + "train", + "export", + "diagnose", + "val" + ], + "title": "TaskType", + "type": "string" + } + }, + "description": "Strongly typed job submission contract shared by UI, skills and dispatcher.\n\nAttributes:\n job_id: Unique job identifier (``{task_type}_{timestamp}_{uuid}`` from the UI).\n task_type: Registered task type resolved by the handler registry.\n status: Current lifecycle state; transitions are FSM-enforced.\n metadata: Bookkeeping metadata (author, priority, tags, timestamps).\n params: Task-specific parameters validated by the resolved handler.\n output: Output directory and artifact capture configuration.\n security_constraints: Fail-closed security policy for the job.\n runtime_tracking: Deadline, streaming and cooperative-cancellation flags.\n error: Structured failure detail; ``None`` while the job is not FAILED.\n logs: Sanitized in-memory execution log lines; only written through\n :meth:`append_log`, which redacts credentials before storage so\n UI-facing and persisted logs never carry plaintext secrets.", + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/$defs/ErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "job_id": { + "maxLength": 128, + "minLength": 1, + "pattern": "^[A-Za-z0-9_-]+$", + "title": "Job Id", + "type": "string" + }, + "logs": { + "items": { + "type": "string" + }, + "title": "Logs", + "type": "array" + }, + "metadata": { + "$ref": "#/$defs/Metadata", + "default": { + "completed_at": null, + "created_by": "anonymous", + "description": null, + "priority": "normal", + "started_at": null, + "tags": [] + } + }, + "output": { + "$ref": "#/$defs/OutputConfig", + "default": { + "artifacts": [], + "output_dir": "runs/predict/job_20260824_f1_001", + "save_images": true, + "save_labels": false, + "save_logs": true + } + }, + "params": { + "additionalProperties": true, + "title": "Params", + "type": "object" + }, + "runtime_tracking": { + "$ref": "#/$defs/RuntimeTracking", + "default": { + "cancel_requested": false, + "cancellable": true, + "stream_logs": true, + "timeout_seconds": 300 + } + }, + "security_constraints": { + "$ref": "#/$defs/SecurityConstraints", + "default": { + "allow_shell": false, + "allowed_path_patterns": [], + "allowed_paths": [], + "path_whitelisted": true + } + }, + "status": { + "$ref": "#/$defs/JobStatus", + "default": "pending" + }, + "task_type": { + "$ref": "#/$defs/TaskType" + } + }, + "required": [ + "job_id", + "task_type" + ], + "title": "JobRequest", + "type": "object" +} diff --git a/docs/studio/runtime.md b/docs/studio/runtime.md index 44d2d90b..c0cd660d 100644 --- a/docs/studio/runtime.md +++ b/docs/studio/runtime.md @@ -1,26 +1,32 @@ # Studio Runtime -The runtime runs on a trusted local machine. It adds bounded asynchronous -execution to the synchronous Core engine without importing HTTP, UI or Agent -code. There is no authentication or public deployment support here. +Runtime schedules jobs and runs the synchronous Core engine in managed worker +processes on a trusted local machine. It owns job state, process cleanup and +persistence. See [architecture](architecture.md) for the service integration and +[security](security.md) for deployment limits. ```python from core.schema import JobRequest from studio.jobs_manager import JobsManager -manager = JobsManager( - storage_path="runs/jobs_state.json", - model_roots=["models"], - data_roots=["datasets"], - output_root="runs", -) -try: - submitted = manager.submit_job_request( - JobRequest(job_id="local-diagnose", task_type="diagnose", output={"output_dir": "runs"}) +def main(): + manager = JobsManager( + storage_path="runs/jobs_state.json", + model_roots=["models"], + data_roots=["datasets"], + output_root="runs", ) - snapshot = manager.get_job(submitted.job_id) -finally: - manager.shutdown() + try: + submitted = manager.submit_job_request( + JobRequest(job_id="local-diagnose", task_type="diagnose", output={"output_dir": "runs"}) + ) + snapshot = manager.get_job(submitted.job_id) + finally: + manager.shutdown() + + +if __name__ == "__main__": + main() ``` Applications using spawn must construct and call the manager inside an @@ -36,7 +42,7 @@ parse `cancel_job()`'s compatibility message. `JobsManager` alone decides admission registration, status, timestamps, terminal publication, restart reconciliation, final manifest and logical slot release. `AdmissionPolicy` validates inputs, `JobStore` restores valid records, `JobLogs` -orders and buffers IPC logs, `ArtifactResolver` resolves manifest entries, and +orders and buffers inter-process communication (IPC) logs, `ArtifactResolver` resolves manifest entries, and `ManagedWorker` reports containment and exit facts. Helpers make no lifecycle decisions. @@ -67,8 +73,9 @@ parent request as user-cancelled. Arbitration preserves an already-published terminal record, then accepted user cancellation, then service closing, then an observed timeout, worker failure or result. Stop windows continue to drain IPC logs. Single-process training additionally uses the checkpoint protocol below. -Cancellation and timeout retain bounded process cleanup without a checkpoint -promise; DDP checkpoint cooperation is unsupported. +Cancellation and timeout use bounded process cleanup. Checkpoint cooperation +applies to single-process training during service shutdown; +DistributedDataParallel (DDP) uses deadline-based cleanup. ## Training shutdown and recovery @@ -85,8 +92,9 @@ temporary file, flushes/fsyncs and atomically replaces `weights/shutdown.pt`. This preserves a recovery copy independently of stripped `last.pt`/`best.pt`. It reports a checkpoint fact (relative ID, epoch, size) and waits for the parent to validate exact containment and size and acknowledge it. Parent validation -does not deserialize worker-supplied pickle files. Loading and actual resume are -separate acceptance checks; a fact/ack alone does not prove either. +does not deserialize worker-supplied pickle files. The acknowledgment records +the checkpoint's location and size. To check recovery, load it and run the +resume procedure below. Throughout cooperation, the owner drains ordered, sanitized IPC. It buffers log updates in memory instead of doing a JSON atomic replacement per line; checkpoint @@ -100,8 +108,8 @@ During closing this wait is capped by the first shutdown's cooperation deadline plus its reserved stop window; elapsed retries reduce the remaining wait to zero. The budget's 16 seconds for OS cleanup and handle closure are not added to this cancellation wait. Cancellation never renews the shutdown deadline. -Cleanup failure retains the owner and slot as `WORKER_STOP_FAILED`; it cannot -publish a terminal result or claim shutdown succeeded. +Cleanup failure retains the owner and slot as `WORKER_STOP_FAILED` while cleanup +retries. `shutdown()` raises if cleanup remains unfinished at its deadline. After cleanup, the manager revalidates the checkpoint candidate and commits only that exact ID to the shutdown manifest. It publishes `failed / SERVICE_SHUTDOWN`, @@ -120,27 +128,24 @@ allow at least `manager.shutdown_budget_seconds` (default 78 seconds) and handle raised cleanup failures rather than kill the manager early. This budget includes the grace and bounded OS cleanup; it does not bound persistent cleanup retries. -This branch has no Service lifespan or Studio launcher. Their real budget and -shutdown integration remain an explicit acceptance gate for the later layers; -this Runtime interface does not prove their behavior. No FastAPI/UI/Agent code is -part of this change. +The [Service lifespan](service.md#history-and-lifecycle) and +[combined launcher](frontend.md#trusted-local-configuration) consume this shutdown +budget. Single-process CPU and single-device training share the callback protocol. -Current evidence must name the device tested; CPU training forces dataloader -workers to zero upstream, so it cannot prove real dataloader child shutdown. -Multiple devices or `WORLD_SIZE > 1` do not install the outer callback and -receive no checkpoint guarantee; the owned tree is forcibly cleaned if it is -still running at the deadline. Rank/collective and real GPU/dataloader evidence -are separate gates. There is no arbitrary-batch resume or checkpoint promise for -other task types. +CPU training forces dataloader workers to zero upstream; verification of +dataloader child shutdown therefore needs a device/configuration that uses them. +Multiple devices or `WORLD_SIZE > 1` skip the outer checkpoint callback; +the owned tree is forcibly cleaned if it is still running at the deadline. +Checkpoint cooperation runs at epoch boundaries in single-process training. To validate recovery of a trusted local checkpoint, inspect its nonnegative `epoch`, online model, optimizer, scaler and EMA fields, load it using `YOLO(path)`, then train with `resume=True` and a total epoch count greater than -`saved_epoch + 1` (at least `saved_epoch + 2`). Verify the next epoch actually executes. Resume follows upstream -epoch-boundary semantics; it does not preserve an arbitrary in-flight batch, -exact RNG/dataloader position, or claim bit-identical continuation. Restarting -the manager preserves the already-published shutdown terminal; it never +`saved_epoch + 1` (at least `saved_epoch + 2`). Verify the next epoch actually executes. +Resume follows upstream epoch-boundary semantics; in-flight batches and exact +RNG/dataloader positions are not retained. Restarting the manager preserves the +already-published shutdown terminal; it never automatically resumes training. ## Persistence and recovery @@ -162,11 +167,11 @@ reconciliation before startup succeeds. It never relaunches those jobs or signal historical PIDs. With a configured store, submission must persist before starting supervisors or entering a queue. A failure raises `StatePersistenceError` (`PERSISTENCE_FAILED`) and removes the unaccepted record and submission log; -the same identifier can be retried. In-memory mode (`storage_path=None`) does not -promise disk persistence. After acceptance, runtime write failures are exposed +the same identifier can be retried. In-memory mode (`storage_path=None`) stores +state only in memory. After acceptance, runtime write failures are exposed through `persistence_error`; owned process cleanup continues, and later mutations retry persistence. Such a -failure means the latest in-memory state is not guaranteed durable. Accepted +failure leaves the disk at its last successfully saved snapshot. Accepted cancellation and actual execution results remain valid in memory even when their snapshot cannot be written; a later successful mutation clears the error. @@ -178,47 +183,30 @@ to be rejected by the resolver without granting file access. ## Paths, logs and artifacts -Model, data and output roots come exclusively from constructor configuration or -`STUDIO_MODEL_ROOTS`, `STUDIO_DATA_ROOTS`, `STUDIO_OUTPUT_ROOT`; root lists use the -platform path separator. Credential-shaped job identifiers and network URLs with -userinfo are rejected before registration. Network inputs additionally require a host in -`STUDIO_NETWORK_INPUT_HOSTS` (comma-separated). Models must be local paths; remote model protocols are rejected. Local model and -data paths are canonicalized before execution so downstream loaders cannot -reinterpret URL-like spellings. Client roots, regex rules, -preloaded artifacts and lifecycle fields cannot expand server authority; shell -execution remains disabled. Traversal, sibling-prefix escapes, broken symlinks -and symlink escapes fail closed. - -Artifact IDs are exact job-relative paths, including nested IDs. Files absent -from the committed manifest are unauthorized even when they exist in the output -directory. Every resolution rechecks containment and current symlinks. Returned -absolute paths are internal filesystem references, not download authorization; -a future transport must use the exact manifest ID and check it again at read time. +Model, data and output roots are server configuration; see +[service settings](service.md#trusted-configuration-and-existing-ui-entry-points). +Network hosts use comma-separated `STUDIO_NETWORK_INPUT_HOSTS`. Admission +canonicalizes local model/data paths before execution and rejects credential-shaped +job IDs and URL userinfo. The [security guide](security.md#paths-and-manifest-authorization) +describes path containment and artifact authorization. + +Runtime artifact IDs are exact job-relative paths, including nested names. +The [HTTP service](api.md#log-cursors-and-artifacts) uses those manifest IDs and +checks containment and opened-file identity before streaming. Logs are sanitized, ordered by child sequence and deduplicated. Terminal and stop tails remain in an independent buffer, so late logs never mutate a published terminal job snapshot. `get_job_log_page(job_id, offset, limit)` returns copied -entries; `next_offset=None` means the current tail, not a promise that no later -logs will arrive. A caller continuing after a tail read resumes from +entries; `next_offset=None` marks the current tail. Later logs remain available +through subsequent polls. A caller continuing after a tail read resumes from `offset + len(logs)`. ## Verification -```bash -YOLO_AUTOINSTALL=false python -m pytest tests/studio -q -python -m pytest --doctest-modules core studio -q -ruff check core studio tests/studio -ruff format --check core studio tests/studio -codespell core studio tests/studio docs/studio/runtime.md -``` - -The existing Studio Core workflow explicitly selects `tests/studio` on Ubuntu -and Windows and therefore also executes these runtime regressions. Broad -upstream test collection continues to exclude Studio unless explicitly selected. -Process tests use importable server-owned executors, real child/grandchild -processes and separate deterministic race tests; parent dispatcher patches are -not assumed to propagate into spawn children. OS, Python version, skips and -unverified gates must accompany reported results. +Use the commands and prerequisites in [testing](testing.md). Process tests use +importable server-owned executors and real child/grandchild processes, with +separate deterministic race tests. Parent dispatcher patches do not propagate +automatically into spawned children. Implementation references: [Python multiprocessing](https://docs.python.org/3/library/multiprocessing.html) and [psutil process APIs](https://psutil.readthedocs.io/). diff --git a/docs/studio/security.md b/docs/studio/security.md new file mode 100644 index 00000000..5557021c --- /dev/null +++ b/docs/studio/security.md @@ -0,0 +1,56 @@ +# Studio security boundary + +[Studio documentation](README.md) + +## Trusted local deployment + +Studio supports one service process on a trusted local machine, bound to +`127.0.0.1`, `localhost` or `::1`. The service checks loopback peers, literal +local Host headers and configured local Origins. Proxy headers cannot grant +trust. Wildcard/remote configured Origins and remote bind addresses are rejected. +There is no login, role-based access control, user isolation or per-job OS sandbox. +Public hosting and untrusted LAN/proxy deployments are unsupported. + +Cross-origin resource sharing (CORS) permits configured local development origins +and legacy `null`/file-console +compatibility. An opaque `null` origin cannot authenticate its source. +Configuration belongs to the trusted operator; see +[service settings](service.md#trusted-configuration-and-existing-ui-entry-points). + +## Paths and manifest authorization + +The server owns model/data/output roots, network-host permissions and policy. +Client `allowed_paths`, regex patterns, lifecycle fields and artifact candidates +cannot extend access. Shell execution stays disabled. Model paths must be local; +network data inputs require a server-authorized host and reject URL credentials. + +Containment resolves existing symlinks and permits them only when the resolved +target stays inside the trusted root. Broken links, out-of-root targets, +traversal, sibling-prefix escapes and invalid regex policy are rejected. Both +the output base and final job directory must remain inside the permitted root. + +After process cleanup, the manager validates handler output paths and records +the permitted downloads in an artifact manifest. API lists and downloads +use exact job-relative IDs, including nested paths; every read checks current +containment and opened-file identity. Existing files outside that manifest are +not downloadable. Safe legacy absolute manifest entries are adapted at read +time without rewriting historical meaning. + +## Logs, errors and persisted state + +Structured job logs, error/traceback surfaces, snapshots and HTTP validation +errors are sanitized. Invalid service configuration uses fixed errors that omit +configuration values and parser exception chains. Keep identifiers free of secrets. + +Windows workers detach from the service console and route Python stdout/stderr +and prebound console logging through sanitized inter-process communication (IPC). +The Windows capture path handles complete Python text lines; native descriptor +writes, interrupted partial lines and arbitrary subprocess output fall outside +this path. Windows Job Objects and Linux process-group/guardian/subreaper handling +govern lifecycle cleanup. Jobs run with the host user's permissions. + +State is stored in local JSON snapshots. Keep state files and model/checkpoint +inputs under trusted operator control, and load only trusted pickle checkpoints. +Parent checkpoint acknowledgment checks containment and size without deserializing +the file. See [runtime recovery](runtime.md#training-shutdown-and-recovery) +for load/resume checks and [testing](testing.md) for verification procedures. diff --git a/docs/studio/service.md b/docs/studio/service.md index 51636719..0fc277ca 100644 --- a/docs/studio/service.md +++ b/docs/studio/service.md @@ -1,151 +1,89 @@ -# Existing FastAPI service with Studio Runtime (PR3 integration) +# Studio FastAPI service -PR3 connects the mainline `main_engine.py` and `api/v1/jobs.py` to -`studio.JobsManager`. It reuses the existing REST service and clients. -FastAPI lifespan creates one owner, reconciles history before admission, and -waits for Runtime cleanup before releasing ownership. Concurrent lifespan -startup is rejected. Requests never construct an owner. The old `f1/` package, -its HTTP client, and the existing frontend and Launcher remain available. +`main_engine.py` and `api/v1/jobs.py` expose Studio Runtime over HTTP. Application +startup creates one `studio.JobsManager` and restores history; shutdown waits +for its cleanup. See [architecture](architecture.md) for layer responsibilities +and [API](api.md) for endpoints, response fields and error codes. -Install the optional service dependencies with Python 3.10+: +## Start the service + +With Python 3.10+ in the source checkout: ```bash -pip install -e ".[studio]" +python -m pip install -e ".[studio]" python main_engine.py ``` -The supported script entry binds to `127.0.0.1:8000`, runs one process, disables -proxy-header trust and access-query logging, and returns a nonzero exit on -handled startup or shutdown failure. Invalid service configuration produces a -fixed error without echoing the value. Raw `uvicorn main_engine:app` uses the -same application lifespan, but does not provide this script's failure-exit and -signal-replay guarantees. Multiple server processes, reload, public/LAN service, -authentication and multi-user isolation remain outside the supported scope. - -## Compatibility - -The existing routes, request and response fields, OpenAPI operation IDs, -collection slash spellings and execution-duration semantics are preserved: - -- `POST /api/v1/jobs[/]`: 201 admission, 409 duplicate, 429 capacity, 422 invalid - payload or path. Duplicate and invalid-path `detail` remain strings; queue - errors retain `detail.code` and `detail.message`. -- `GET /api/v1/jobs[/]?limit=10&offset=0`: the existing page and total, now taken - together under the owner lock. -- `GET /api/v1/jobs/{job_id}`: coherent status, error, timestamps, metadata, - artifact count and numeric duration. Queue time is excluded; missing start - stays null and zero duration stays `0.0`. -- `POST /api/v1/jobs/{job_id}/cancel`: 202 acknowledgement, 200 terminal replay, - 409 non-cancellable and 404 unknown. Runtime's typed decision determines the - response; running cancellation remains active until actual cleanup. -- `GET /api/v1/jobs/{job_id}/logs`: sanitized flattened-line offset/limit pages. - A null cursor denotes the current tail; consumers must drain terminal pages. -- `GET /api/v1/jobs/{job_id}/artifacts` and - `GET /static/artifacts/{job_id}/{artifact_id:path}`: existing manifest fields - and nested download IDs. Unknown-job and missing-artifact details remain - strings. Downloads recheck containment and opened-file identity before streaming. -- `GET /health`: exactly `status=ok` and - `service="YOLO-Master F1 Task Engine"`, preserving the existing liveness response. - -New 503 structured errors represent unavailable/closing service and admission -that could not be persisted. Such an admission is rolled back and starts no -worker. Validation error lists omit raw input/context to avoid credential echo; -unexpected failures, log lines and persistence snapshots are sanitized. -Cancellation messages are presentation text; clients must use status/code. - -Existing `create_app()` and `app.dependency_overrides[get_jobs_manager]` remain -usable. An override must return the same Studio-compatible manager throughout -the lifespan. `manager_factory` is an additional trusted test/embedding seam. -Calling `get_jobs_manager` to lazily construct a manager outside ASGI lifespan -is no longer supported. HTTP clients need no Runtime imports or state-file access. +The script binds to `127.0.0.1:8000` and runs one process. It disables proxy-header +trust and access-query logging and returns a nonzero exit on handled startup or +shutdown failure. Invalid configuration produces a fixed error without echoing +its value. Raw `uvicorn main_engine:app` uses the same lifespan but lacks the +script's failure-exit and signal-replay handling. Use one process without reload; +see [deployment limits](security.md#trusted-local-deployment). -## Trusted configuration and existing UI entry points +## Embedding -`F1_*` remains an alias for matching `STUDIO_*` settings: `ENGINE_HOST`, -`ENGINE_PORT`, `CORS_ORIGINS`, `JOBS_STATE_PATH`, `MODEL_ROOTS`, `DATA_ROOTS`, -`OUTPUT_ROOT`, `NETWORK_INPUT_HOSTS`, `CPU_CONCURRENCY`, `GPU_CONCURRENCY`, -`MAX_PENDING_JOBS`, `STOP_GRACE_SECONDS`, `SHUTDOWN_GRACE_SECONDS` and -`HTTP_DRAIN_SECONDS`. If both names contain different trimmed values, startup -fails with a fixed error rather than silently changing trusted roots or budgets. -Empty legacy CORS/root/state-path values retain their mainline default behavior; -explicit empty `STUDIO_CORS_ORIGINS` disables the allowlist. Root lists use the -platform path separator. State defaults to `runs/jobs_state.json`. -The existing client/Launcher `F1_STUDIO_API_URL` setting is unchanged. - -The mainline CORS defaults remain local origins on ports 8000, 5173, 3000 and -8080 for localhost/127.0.0.1, plus `null` for the existing trusted file console. -Retaining `null` is an explicit compatibility choice; it cannot identify the -source of an opaque origin and is not an authentication boundary. Request peers -must be loopback and Host must be a literal local host. Unsupported remote bind -hosts, remote configured origins, wildcard origins and credential-bearing -origins are rejected. Disallowed preflight origins retain the existing CORS 400; -disallowed actual requests receive 403. Proxy headers cannot grant local trust. - -When present, the existing `frontend/` is mounted at `/`, its favicon is served, -and a built `web/dist` is mounted at `/console`. API routes take precedence. -Range/If-Range, full/stat headers and multipart ranges use FileResponse semantics -through the already-open authorized handle (Starlette 0.49.3+). These assets are optional: a pure installed service starts without UI resources -or Gradio. A wheel includes main_engine/api/studio and the existing F1 -compatibility packages; source UI assets are not bundled. No frontend or -Launcher behavior is redesigned in this change. +`create_app()` and `app.dependency_overrides[get_jobs_manager]` support trusted +embedding and tests. An override must return the same Studio-compatible manager +throughout the lifespan; `manager_factory` supplies a custom factory. Concurrent +lifespan startup is rejected, and `get_jobs_manager` does not construct an owner +outside lifespan. HTTP clients communicate through REST without state-file access. -## History and lifecycle +## Trusted configuration and existing UI entry points -Version-1 F1 snapshots use the existing shared core schema. Terminal records, -timestamps, errors and stored manifests are retained. Active history is -reconciled to `failed/SERVICE_RESTARTED`, never re-executed or used to signal an -old PID. Safe absolute historical manifest entries obtain contained relative -download IDs at read time without rewriting the stored entry. Unlisted files, -outside paths, traversal and symlink escapes cannot grant download authorization. - -Studio validates primary and backup snapshots, atomically replaces state and -fails startup if neither existing copy is valid. Back up and validate real -history before operational migration; two services must never share the state -store. After admission, a later persistence failure preserves execution truth -in memory and sets the owner's `persistence_error`. The unchanged public health -response does not expose this internal warning; an additive client-facing -durability indicator needs separate API agreement. Persistent write failure -cannot promise that restart will see the latest in-memory terminal state. - -Shutdown drains HTTP requests first (default 5 seconds), then awaits Runtime's -checkpoint cooperation and cleanup. Runtime's caller budget is -`shutdown_grace_seconds + stop_grace_seconds + 16` (default 78 seconds). -Checkpoint/ACK/result alone cannot publish terminal or release a slot. -Cleanup drains IPC without a disk snapshot for each log; the final ordered tail -is persisted at checkpoint confirmation or terminal publication. This prevents -console log bursts from postponing cancellation or process cleanup. Active jobs stopped by successful -service shutdown become `failed/SERVICE_SHUTDOWN`, distinct from cancellation and -completion. Cleanup failure retains owner/slot and fails lifespan. Permanent -cleanup failure may continue through atexit retries or multiprocessing joins; -there is no unconditional bounded-exit promise. - -Windows workers detach from the service console before task execution and route -Python stdout/stderr and prebound console logging through sanitized IPC. Job -Object containment remains active. Native descriptor writes and interrupted -partial lines are outside this text-stream guarantee. POSIX guardian/subreaper -behavior is unchanged. The combined launcher requests cooperative Service -shutdown and waits for the configured HTTP drain, Runtime cooperation/cleanup, -and service exit budgets. See [clients and launcher](frontend.md) for process -ownership, failure behavior, and manual/platform acceptance scope. - -## Verification +Service settings use the `STUDIO_` prefix. Matching `F1_` aliases are accepted for +these suffixes: `ENGINE_HOST`, `ENGINE_PORT`, `CORS_ORIGINS`, `JOBS_STATE_PATH`, +`MODEL_ROOTS`, `DATA_ROOTS`, `OUTPUT_ROOT`, `NETWORK_INPUT_HOSTS`, +`CPU_CONCURRENCY`, `GPU_CONCURRENCY`, `MAX_PENDING_JOBS`, `STOP_GRACE_SECONDS`, +`SHUTDOWN_GRACE_SECONDS` and `HTTP_DRAIN_SECONDS`. + +Different trimmed values for both names fail startup. Empty legacy CORS/root/ +state-path settings retain their defaults; an explicit empty +`STUDIO_CORS_ORIGINS` disables the allowlist. Root lists use the platform path +separator (`;` on Windows, `:` on POSIX). State defaults to `runs/jobs_state.json`. +Model and data roots default to the working directory; the output root is its +`runs/` subdirectory. Start from the repository root for the quick-start defaults. +Clients use `STUDIO_API_URL`, with `F1_STUDIO_API_URL` as an alias; see +[frontend settings](frontend.md#trusted-local-configuration). + +Cross-origin resource sharing (CORS) controls browser access from other origins. +Default origins are localhost/127.0.0.1 on ports 8000, 5173, 3000 and 8080, +plus legacy `null` for the file console. Peers must be loopback and Host must +be literal local. Remote/wildcard/credential-bearing origins and remote bind +addresses are rejected. Disallowed preflights return 400; actual requests return +403. The `null` origin cannot identify its source; CORS is not authentication. +See [security](security.md#trusted-local-deployment). + +The source `frontend/` console is mounted at `/` when present. A built `web/dist/` +is mounted at `/console`; API routes take precedence. Downloads stream from an +already-open authorized file using Starlette 0.49.3+ FileResponse range/header +semantics. An installed wheel includes `main_engine`, `api`, `studio` and F1 +compatibility packages, but no source UI assets. The API starts without Gradio +or those assets. See [clients and launcher](frontend.md) for UI startup. -```bash -python -m pytest tests/studio tests/api/test_api_v1.py -q -# Explicit local-model, production entry and existing HTTP-client acceptance -STUDIO_TEST_MODEL=/absolute/local/yolov8n.pt python -m pytest \ - tests/studio/test_mainline_integration.py --slow --studio-integration -q -``` +## History and lifecycle -The production integration uses real loopback HTTP, the default production -factory, five real task handlers, existing HTTP client, real running cancellation, -external service signal, checkpoint load/resume and restart. Its launcher case -runs the actual combined Service and Gradio entry and checks both HTTP endpoints -before exercising tasks and signal-driven cleanup. It uses CPU and a tiny generated -dataset; effective loader workers are zero and CPU AMP is disabled even if the -configured AMP option is true. When run on Windows, this establishes the -automated CPU launcher path. Manual client acceptance and GPU/loader/AMP-scaler, -DDP, Linux/macOS validation require separate, scoped evidence. Process harness tests additionally -cover cooperation, fallback and retained ownership after injected cleanup failure. -Their finalizer restores cleanup; they do not prove permanent-failure exit bounds. -Identity absence after cleanup/exit does not measure the exact kernel reap instant. +Version-1 F1 snapshots retain terminal records, timestamps, errors and manifests. +Safe absolute artifact entries receive contained relative download IDs at read +time. Restored active jobs become `failed / SERVICE_RESTARTED`; startup neither +relaunches them nor signals historical PIDs. Validate and back up real history +before migration, and give each state store a single service owner. + +An admission write failure rolls back the job and starts no worker. Later write +failures keep execution state in memory and set `persistence_error` on the owner; +the latest state may then be absent after restart. `/health` reports liveness +(`status=ok`, `service="YOLO-Master F1 Task Engine"`) without that internal warning. +See [Runtime persistence](runtime.md#persistence-and-recovery) for recovery rules. + +Shutdown drains HTTP requests (default 5 seconds), then awaits Runtime's checkpoint +cooperation and cleanup. Runtime's caller budget is +`shutdown_grace_seconds + stop_grace_seconds + 16` (default 78 seconds). Active +jobs stopped by service shutdown become `failed / SERVICE_SHUTDOWN`. Cleanup +failure retains ownership and occupied slots and fails lifespan; persistent +cleanup failure can prevent a bounded process exit. The [launcher](frontend.md) +waits for the service's configured budgets. + +Checkpoint publication and ordered log-tail persistence are described in +[Runtime](runtime.md#training-shutdown-and-recovery); output-capture coverage is in +[security](security.md#logs-errors-and-persisted-state). For automated and manual +verification, use [testing and reproduction](testing.md). diff --git a/docs/studio/testing.md b/docs/studio/testing.md new file mode 100644 index 00000000..e70a4c0d --- /dev/null +++ b/docs/studio/testing.md @@ -0,0 +1,171 @@ +# Testing and reproduction + +[Studio documentation](README.md) + +Run commands from the source root unless a section says `web/`. Use Python +3.10+ and a virtual environment. Examples below use PowerShell; in POSIX shells +use `export NAME=value` instead of `$env:NAME="value"`. Record the tested +revision/patch and whether the checkout and environment were fresh. + +## Offline Core, Runtime and Service lane + +```powershell +python -m pip install -e ".[studio]" pytest httpx ruff codespell +$env:YOLO_AUTOINSTALL="false" +$env:YOLO_OFFLINE="true" +python -m pytest tests/studio --ignore=tests/studio/ui -q +python -m pytest --doctest-modules core studio api main_engine.py -q +ruff check core studio api main_engine.py tests/studio +ruff format --check core studio api main_engine.py tests/studio +codespell core studio api main_engine.py tests/studio +``` + +[Studio Core CI](https://github.com/Tencent/YOLO-Master/blob/main/.github/workflows/studio-core.yml) +runs the Python selection on Ubuntu and Windows with Python 3.10. It includes +Core, Runtime and Service checks and excludes Gradio UI tests. Studio tests need +explicit selection; ordinary upstream collection excludes them. +`tests/studio/conftest.py` prohibits implicit model/data downloads and removes +real-model tests unless explicitly enabled below. + +## Frontend lane + +```powershell +python -m pip install -e ".[studio]" gradio pandas pytest httpx ruff +$env:YOLO_AUTOINSTALL="false" +$env:YOLO_OFFLINE="true" +$env:NO_PROXY="127.0.0.1,localhost,::1" +python -m pytest tests/studio tests/f1/ui -q +``` + +In `web/`, with Node.js 24: + +```bash +npm ci +npm test +npm run lint +npm run build +``` + +[Studio Frontend](https://github.com/Tencent/YOLO-Master/blob/main/.github/workflows/studio-frontend.yml) runs the Python +clients/launcher on Ubuntu and Windows and the browser checks on Ubuntu. Build +includes TypeScript checking. Keep `package-lock.json`; do not add generated +`dist/`, `node_modules/`, logs or model files to a contribution. + +## Compatibility lane + +[Studio Compatibility CI](https://github.com/Tencent/YOLO-Master/blob/main/.github/workflows/f1_ci.yml) +covers the retained F1 package, API compatibility, smoke and coverage. It fetches +a model for legacy export/smoke checks and has its own triggers. Use that workflow +for the compatibility test selection; browser/Node checks belong to the Frontend lane. + +## Explicit local-model integration + +Use an existing trusted detection `.pt` file; no download is part of this +procedure. Keep the source checkout and local model/data under trusted control. +Install the frontend dependencies above for the combined launcher case. + +```powershell +$env:STUDIO_TEST_MODEL="D:\path\to\existing-local-model.pt" +$env:YOLO_AUTOINSTALL="false" +$env:YOLO_OFFLINE="true" +$env:NO_PROXY="127.0.0.1,localhost,::1" +python -m pytest tests/studio/test_core_integration.py --slow --studio-integration -q +python -m pytest tests/studio/test_mainline_integration.py --slow --studio-integration -q +``` + +Both flags are required. Core integration uses tiny generated data, CPU +prediction/training and TorchScript export. Production integration exercises +loopback HTTP, five handlers, the legacy HTTP client, cancellation, service and +launcher shutdown, checkpoint load/resume and restart reconciliation. It uses +CPU with effective dataloader workers zero and AMP disabled. Record a separate +device/configuration when checking GPU, AMP-scaler or dataloader-child recovery. + +## Documentation checks + +Regenerate [job_schema.json](job_schema.json) using the [API instructions](api.md#regenerate-the-domain-schema) +and compare again in a fresh process. Check every changed Markdown link and +anchor, parse/compile the examples, and run the diagnostic against a local API. +Run `codespell` on the changed Markdown/JSON files and `git diff --check`. + +The repository's `mkdocs.yml` serves `docs/en/`, not `docs/studio/`. For the +full-site toolchain in a fresh environment, install the documentation entries +from the `dev` extra together with MkDocs and the configured Material theme: + +```bash +python -m pip install mkdocs mkdocs-material "zensical>=0.0.15" "mkdocs-ultralytics-plugin>=0.2.5" "minijinja>=2.0.0" +python -m mkdocs build --strict +zensical build --strict -f mkdocs.yml +``` + +MkDocs is the repository's `AGENTS.md` check. Zensical is also used by +`docs/build_docs.py`, which prepares generated/external pages and macros first; +the direct command above builds only existing checkout pages. Record versions +and compare any failure with the unchanged base using the same command/environment. +Record baseline and candidate failures in the PR report. + +To build the Studio documentation, run the following Python block from the +repository root. It copies Studio pages to a temporary tree and runs MkDocs +strict with default warning handling. Record the Studio documentation build +and full-site build separately. + +```python +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile + +root = Path.cwd() +work = Path(tempfile.mkdtemp(prefix="studio-docs-")) +for item in (root / "docs/studio").rglob("*"): + if item.is_file() and item.suffix in {".md", ".json"}: + target = work / "docs" / item.relative_to(root) + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(item, target) +config = work / "mkdocs.yml" +config.write_text( + 'site_name: Studio documentation\ndocs_dir: docs\nsite_dir: site\n' + 'markdown_extensions: [fenced_code, tables]\n', + encoding="utf-8", +) +subprocess.run( + [sys.executable, "-m", "mkdocs", "build", "--strict", "--config-file", str(config)], + check=True, +) +print(work) +``` + +## Manual clean-checkout checklist + +Use a fresh checkout/environment and follow the [quick start](README.md) and +[user guide](user_guide.md). Record the revision/patch, OS, dependency +versions and effective device/AMP/loader settings. Keep commands, outcomes, +skips and artifact hashes in the PR report; mark unperformed checks explicitly. + +- [ ] Install and start using only the documented prerequisites. Check `/health`, + `/docs`, `/openapi.json` and the built-in console from the source checkout. +- [ ] Complete a diagnostic in each client; follow logs, download both reports, + and inspect their contents. Check a duplicate API ID returns 409 and an unknown + ID returns 404. +- [ ] With local assets inside configured roots, submit model tasks, cancel + running work and wait for final status. Check an outside path is rejected. + Download nested/non-image artifacts and compare a large file's hash. +- [ ] Switch jobs/APIs while refreshing; check old responses do not replace the + new selection. Disconnect and reconnect the API. Check Inference Studio, + browser-close behavior and launcher-owned versus external API shutdown as + described in [clients and launcher](frontend.md). +- [ ] When changing recovery behavior, check confirmed checkpoint download, + `failed/SERVICE_SHUTDOWN`, cleanup, trusted load and actual next-epoch resume. + Restart and check `failed/SERVICE_RESTARTED` without automatic relaunch. + +## Test scope + +Report automated checks, manual UI review and platform/model tests separately, +including skipped and untested cases. Put run-specific results in the PR report. + +Use a matching device and configuration for GPU, AMP and dataloader-child tests. +Cooperative checkpoint shutdown supports single-process training. Process tests +check for remaining owned processes after exit; failure-injection cases restore +cleanup before checking shutdown. Persistent cleanup failures retain the worker +and slot for retry. See [Runtime](runtime.md) and [launcher](frontend.md) for +shutdown budgets and cleanup behavior. diff --git a/docs/studio/user_guide.md b/docs/studio/user_guide.md new file mode 100644 index 00000000..2596bfd8 --- /dev/null +++ b/docs/studio/user_guide.md @@ -0,0 +1,78 @@ +# Studio user guide + +Start with the [quick start](README.md) to install Studio and run a +diagnostic. This guide covers daily use after the API service is running. + +## Choose a client + +All three clients share the same API and job history: + +- **Built-in console:** visit after starting + `python main_engine.py`. No frontend build is needed. +- **React:** with Node.js 24, run `npm ci` and `npm run dev` from `web/` in a + second terminal. Open the local address printed by Vite, normally + . Set **Engine** to `http://127.0.0.1:8000` and **Apply**. +- **Gradio:** run `python -m pip install gradio pandas` in the active environment. + Stop the standalone API with Ctrl+C if you want the launcher to own both services, + then run `python start_studio.py`. Open its printed URL and select **Jobs**. + The separate **Inference Studio** tab runs synchronous inference. + +For separate Gradio/API processes, custom API addresses or a production React +bundle, see [clients and launcher](frontend.md). + +## Prepare a task + +Choose a task type according to the work you want to run: + +- `diagnose`: collect environment reports; no model or dataset needed. +- `predict`: provide a model and an image, video, media directory or input list. +- `train`: provide a model and a dataset YAML file, then set training options. +- `val`: provide a model and a dataset YAML file to measure validation metrics. +- `export`: provide a model and select an export format. + +Paths refer to files on the API machine. Use existing model weights inside the +server's model roots, input data inside its data roots, and an output directory +inside its output root (`runs` by default). These **roots** are directories the +operator permits Studio to access. Model/data roots default to the repository +root when started there. Ask the operator to configure them when +needed; client path fields cannot change those permissions. See +[service settings](service.md#trusted-configuration-and-existing-ui-entry-points). + +For a first model task, choose `cpu` if CUDA is unavailable. For a diagnostic, +leave model/data fields and extra parameters empty and use `runs/diagnose` as +the output directory. Fields and defaults vary by client; the +[API guide](api.md) shows the corresponding request format. + +## Follow progress and cancel + +After submission, select the job in the list and follow its status and logs: + +- `pending`: waiting for an execution slot. +- `running`: executing, or finishing cleanup after a stop request. +- `completed`: execution and cleanup finished successfully. +- `failed`: execution or service shutdown ended the job; inspect its error code. +- `cancelled`: cancellation finished and the worker was cleaned up. + +**Cancel** requests a stop. Keep checking until the job reaches a final status. +A missing-job message means the selected API cannot find that ID; check the +Engine address and job ID. + +## Download results + +Select a file in the job's artifact list to preview or download it. Nested names +such as `weights/shutdown.pt` identify files within that job. In Gradio, +**Open Downloads** opens the client's downloaded cache, which may differ from +the API's output directory. Files not listed as artifacts cannot be downloaded +through the API. + +## Stop and recover + +Keep the API terminal open while jobs run. Closing a client does not stop its +jobs; Ctrl+C requests service shutdown. The combined launcher stops services it +started and leaves an externally started API under its owner's control. + +After an API restart, interrupted jobs become `failed / SERVICE_RESTARTED`. +Training does not resume automatically. A training job stopped by service +shutdown may expose `weights/shutdown.pt` while remaining +`failed / SERVICE_SHUTDOWN`. Use the [checkpoint recovery procedure](runtime.md#training-shutdown-and-recovery) +to load a trusted checkpoint and explicitly resume from the next epoch. diff --git a/f1/README.md b/f1/README.md index a574a6c7..faa51749 100644 --- a/f1/README.md +++ b/f1/README.md @@ -1,189 +1,20 @@ -# YOLO-Master F1 Task Platform +# Legacy F1 compatibility -This document describes the current F1 implementation and runtime boundaries. +For Studio setup and daily use, start at the [quick start](../docs/studio/README.md) +and [user guide](../docs/studio/user_guide.md). -F1 is an orchestration layer around the existing Ultralytics YOLO Python API. It -does not replace the training, validation, prediction, or export engines. +The `f1` backend and direct-call adapters support legacy integrations. Production +HTTP jobs use the `studio` runtime, dispatcher and handlers; `f1/ui` provides lazy +imports for `studio/ui`. See [architecture](../docs/studio/architecture.md), +[service configuration](../docs/studio/service.md) and [legacy handler extensions](handlers/USAGE.md). -## 1. Runtime architecture +The [earlier F1 platform guide](https://github.com/Tencent/YOLO-Master/blob/b10cdf11c9f7aa6cb949595391572e0eed1bf0f1/f1/README.md) +remains available for historical reference. -```text -Gradio UI -- StudioJobsApiClient --+ -Zero-build console (/) ------------+--> FastAPI task API -React/Vite console (/console) -----+ | -Other REST clients ----------------+ v - JobsManager - | | - CPU queue GPU queue - | | - fixed supervisor slots - | - v - ManagedWorker (spawn process) - | - v - JobDispatcherStateMachine - | - v - TaskHandlerRegistry - | - v - train / val / predict / export / diagnose - | - v - Ultralytics YOLO Python API -``` - -The singleton `JobsManager` owned by the FastAPI service is the sole production -lifecycle owner. It owns admission, queues, workers, cancellation, timeout, -terminal publication, history, logs, artifacts, and the state file. Gradio uses -`StudioJobsApiClient`; it does not create a second production manager or open the -state file. The `JobsManager` export in `f1/ui/jobs_tab.py` is a lazy compatibility -shim for legacy imports. - -Run the API with one ASGI worker. F1 does not coordinate multiple API processes -or multiple hosts. Task execution itself is not single-process: each running job -uses a spawned managed process. - -## 2. Contract and task routing - -`core/schema.py` is the canonical contract. Public task types are: - -- `predict` -> `f1/handlers/predict.py` -- `train` -> `f1/handlers/train.py` -- `val` -> `f1/handlers/val.py` -- `export` -> `f1/handlers/export.py` -- `diagnose` -> `f1/handlers/diagnose.py` - -Importing `f1.handlers` registers all five handlers. The dispatcher resolves the -concrete class through `TaskHandlerRegistry`, validates parameters and security -constraints, injects cooperative cancellation tracking, invokes the handler, and -normalizes the returned result. - -The public statuses are: - -```text -pending | running | completed | failed | cancelled -``` - -The worker-side dispatcher state machine permits: - -```text -PENDING -> RUNNING -> COMPLETED - | | - +----------+----> FAILED -``` - -`CANCELLED` is published by `JobsManager`, not by the dispatcher state machine. -For a running job the manager first stops and joins the owned process tree, then -publishes `CANCELLED / USER_CANCELLED`. Terminal status, result, and error -snapshots cannot be overwritten by a late worker result. - -## 3. Lifecycle and capacity - -- CPU and accelerator jobs use separate queues. -- Defaults are two CPU slots, one accelerator slot, and 100 pending jobs. -- Explicit `device=cpu` uses a CPU slot. Empty/auto values, CUDA device strings, - multi-GPU strings, and MPS use the accelerator queue. -- A waiting job remains `pending` and owns no process or dedicated thread. -- A full pending queue returns HTTP `429`. -- Timeout starts when a slot launches a worker; queue waiting time is excluded. -- A fresh cancellation request returns HTTP `202`. The caller polls until the - job reaches a terminal state. -- Graceful shutdown stops pending/running work before the service exits. -- On startup, persisted `pending` or `running` jobs become - `FAILED / SERVICE_RESTARTED`; execution is not resumed. - -Windows uses a kill-on-close Job Object. Linux uses a guardian, process groups, -and subreaper behavior to contain and reap descendants. These mechanisms provide -lifecycle cleanup, not an OS security sandbox. Other POSIX platforms do not have -the same verified subreaper guarantee. See `f1/RUNTIME_LIFECYCLE.md`. - -## 4. State, logs, and artifacts - -The API manager persists history to `F1_JOBS_STATE_PATH`, defaulting to -`runs/jobs_state.json`. Writes use a temporary file and replace, but persistence -is local and best-effort; it is not a transactional database or durable queue. - -Logs are exposed with HTTP offset/limit cursors. `JobRequest.append_log()` and -manager-owned structured log/error paths sanitize credentials before storage and -API delivery. This guarantee applies to controlled job logs, errors, and persisted -fields; arbitrary third-party output written directly to process stdout/stderr is -not captured or guaranteed to pass through the sanitizer. - -Terminal status/result/error snapshots are immutable. Historical log entries -remain readable, and log events already in flight may still be retained without -changing the terminal snapshot. - -Handlers collect existing files below their job-specific output directory into a -sorted artifact manifest. The API returns safe relative artifact IDs and serves a -file only when that exact ID exists in the stored manifest. It does not expose an -arbitrary directory browser or accept client-preloaded artifact entries. - -## 5. Security boundary +## Historical verification record -At API admission, `JobsManager` resets client-controlled lifecycle state and -enforces the server policy: - -- `allow_shell = false` -- `path_whitelisted = true` -- client `allowed_paths` and `allowed_path_patterns` are discarded -- model, data, and output paths are validated against independent server roots -- network data sources require an explicitly allowlisted hostname -- client-provided artifact entries are cleared -- `job_id` accepts only safe alphanumeric, underscore, and hyphen identifiers - -Path validation rejects traversal, sibling-prefix escapes, existing symlink -components, and broken symlinks. A missing ordinary output leaf below the trusted -output root is allowed so the handler can create it. - -The API has no authentication, RBAC, tenant isolation, or per-job filesystem -sandbox. It is intended for a trusted host/network and controlled service account. - -## 6. Frontends and startup - -Use the combined launcher: - -```bash -python start_studio.py -``` - -It health-checks or starts `main_engine.py` as a child process when needed, then -runs Gradio in the launcher process. Gradio and the API remain separate services. -The default endpoints are: - -- Gradio: `http://127.0.0.1:7860` -- FastAPI: `http://127.0.0.1:8000` -- Health: `http://127.0.0.1:8000/health` -- OpenAPI: `http://127.0.0.1:8000/docs` -- Zero-build console: `http://127.0.0.1:8000/` - -For API-only operation: - -```bash -python main_engine.py -``` - -`frontend/` is always mounted at `/`. The React/TypeScript/Vite source is under -`web/`; `/console` is mounted only when a local `web/dist` build exists. Gradio is -not mounted by FastAPI and communicates through REST. - -## 7. Configuration - -- `F1_JOBS_STATE_PATH`: local manager state file; default `runs/jobs_state.json` -- `F1_ENGINE_HOST` / `F1_ENGINE_PORT`: API bind address; defaults `127.0.0.1:8000` -- `F1_STUDIO_API_URL`: API origin used by Gradio; default `http://127.0.0.1:8000` -- `F1_MODEL_ROOTS`: trusted model roots, separated by `os.pathsep` -- `F1_DATA_ROOTS`: trusted data roots, separated by `os.pathsep` -- `F1_OUTPUT_ROOT`: one trusted output root; default `/runs` -- `F1_NETWORK_INPUT_HOSTS`: comma-separated network input host allowlist -- `F1_MAX_PENDING_JOBS`: pending capacity; default `100` -- `F1_CPU_CONCURRENCY`: CPU slots; default `2` -- `F1_GPU_CONCURRENCY`: accelerator slots; default `1` -- `F1_STOP_GRACE_SECONDS`: worker stop grace, 0-30 seconds; default `2` -- `F1_CORS_ORIGINS`: comma-separated CORS allowlist replacing dev defaults - -## 8. Verification record +This original record describes the revision named below. For current test +selections, see [testing](../docs/studio/testing.md). The following results were recorded on commit `0e7a8f83b53c97abc8eb3caedc532f5779fbb086`, reviewed against the official @@ -220,43 +51,3 @@ does not currently run Node lint, TypeScript checking, or a Vite production buil Local checks at the same commit additionally passed `npm run lint` and TypeScript `--noEmit` checks for the React app and Node configuration. No claim is made here that a production React bundle is exercised by CI. - -## 9. Agent/F1 boundary - -The F1 production execution path is `f1/jobs_manager.py` -> -`f1/worker_runtime.py` -> `f1/dispatcher.py` -> `f1/handlers/`. It does not route -jobs through `agent/runtime/cli/async_jobs.py` or an Agent worker. - -`agent/` remains a separate skill/CLI integration layer. Agent documentation and -validation belong to `agent/SKILL.md` and -`agent/scripts/validate_yolo_master_skill.py`; those checks do not replace F1 API, -lifecycle, handler, or security tests. - -## 10. Known limitations - -- One API service process and one local lifecycle owner only -- Local best-effort JSON state; no database-backed durable queue -- No distributed scheduling, priority execution, preemption, or automatic retry -- Static CPU/GPU slot counts; no memory-aware or per-GPU placement -- HTTP cursor polling only; no WebSocket/SSE stream -- No authentication, RBAC, tenant isolation, or per-job OS sandbox -- React build artifacts are deployment-provided and are not covered by F1 CI -- Windows/Linux process containment is verified; other POSIX behavior is weaker -- Some OBB/classification metric displays remain incomplete -- Export tests currently emit legacy ONNX exporter deprecation warnings -- Dependency versions are not fully upper-bounded or locked - -## References - -- User manual: `docs/f1_user_manual.md` -- Handler framework: `f1/handlers/README.md` -- Handler extension guide: `f1/handlers/USAGE.md` -- Runtime lifecycle: `f1/RUNTIME_LIFECYCLE.md` -- Agent skill boundary: `agent/SKILL.md` -- Ultralytics documentation: - ---- - -**Document version**: 1.5.0 - -**Last synchronized**: 2026-09-15 diff --git a/f1/RUNTIME_LIFECYCLE.md b/f1/RUNTIME_LIFECYCLE.md index 3a791187..77e0451b 100644 --- a/f1/RUNTIME_LIFECYCLE.md +++ b/f1/RUNTIME_LIFECYCLE.md @@ -1,151 +1,17 @@ -# Studio runtime lifecycle +# Runtime documentation moved -This document describes the current F1 runtime and lifecycle boundaries. +See [Studio Runtime](../docs/studio/runtime.md) for production queues, cleanup, +persistence and checkpoint recovery. [Service](../docs/studio/service.md) and +[launcher](../docs/studio/frontend.md) describe startup/shutdown integration. -F1 owns task orchestration only. It calls the existing Ultralytics Python API and -does not route production jobs through the Agent runtime. - -## Execution - -All five task types follow the same backend-owned execution path: - -`Gradio UI → StudioJobsApiClient → FastAPI task API → JobsManager → CPU/GPU waiting queue → fixed supervisor slot → managed process → F1 dispatcher → handler → YOLO Python API` - -Other API clients join at the FastAPI task API. The Gradio path never constructs -its own `JobsManager`. - -The zero-build console at `/`, the optional built React console at `/console`, -and Gradio all call the same REST API. FastAPI always serves the zero-build -console. It serves `/console` only when `web/dist` exists. `start_studio.py` runs -Gradio in its launcher process and starts the API as a child process when needed; -Gradio is not mounted inside the engine. - -On Windows the managed process owns computation and is assigned to a kill-on-close -Job Object before the parent permits execution. Child and grandchild processes -remain in that object. On Linux the managed root is a subreaper guardian; its -computation child runs the same dispatcher and handler. The guardian retains -orphaned dataloader/DDP descendants, including torchrun ranks that create a new -session. No fork of the server's GPU context is used: workers use `spawn`. - -The API-owned `JobsManager` calls the dispatcher with `managed=True`, executing -the handler on the computation process's main thread. The old direct-dispatcher -thread mode remains for compatibility outside Studio; it does not provide forced -termination. - -The dispatcher state machine owns `PENDING -> RUNNING -> COMPLETED/FAILED` inside -the worker. `JobsManager` owns admission and the public lifecycle, including -`CANCELLED`, timeout/shutdown failures, terminal arbitration, and persistence. -The manager only accepts a worker result while the public job is non-terminal. - -## Capacity and waiting - -- `F1_CPU_CONCURRENCY`: positive integer, default **2**. -- `F1_GPU_CONCURRENCY`: positive integer, default **1** for all accelerator jobs. -- `F1_STOP_GRACE_SECONDS`: 0–30 seconds, default **2**. -- Constructor settings override these server environment settings. -- Explicit `cpu` uses CPU slots; absent device follows existing handler default - `cpu`. Auto/empty/None, CUDA device IDs/lists and MPS use GPU slots. -- Waiting jobs remain publicly `pending` and do not get a process or a dedicated - thread. Each slot is retained through execution and cleanup. -- Limits apply to the sole API-owned JobsManager in one service process. Run the - Studio API with one ASGI worker. Multiple service processes must not share a - state file; cross-process scheduling/storage coordination is not provided. - -The waiting/history records remain in memory and JSON persistence; this is not a -distributed or durable execution queue. There is no priority preemption, resource -prediction, per-GPU scheduling or automatic recovery of unfinished computation. -Persistence uses local temporary-file replacement and is best-effort; an I/O -failure is not promoted to a transactional storage guarantee. - -## Cancellation, timeout and cleanup - -`cancel` acknowledges a stop request with HTTP 202. A running job remains active -while cleanup is in progress. Cancellation of a waiting job requires no process -launch and finishes immediately. - -Timeout starts when a slot launches the worker and includes interpreter startup -and validation; waiting time does not consume the execution timeout. Cancellation -and timeout use the same stop routine: - -1. Send a stop request through a private one-way pipe. -2. On POSIX also send SIGTERM to the process group and known descendants. -3. Wait the configured grace period. -4. Force termination if needed: Windows `TerminateJobObject`; Linux freeze/kill - descendants, then SIGKILL the owned process group. -5. Join the root, verify no owned computation remains, close IPC/OS handles. -6. Release the slot and persist the final result under the manager lock. - -Successful handlers also pass through tree cleanup before `completed` becomes -visible. Results arriving during an accepted cancellation cannot overwrite it. -An OS cleanup failure retains the worker handle and slot, exposes -`WORKER_STOP_FAILED` on the still-running job and retries cleanup. - -The stop channel deliberately avoids multiprocessing Event/Queue locks: abruptly -killing a process can leave such locks permanently held. There is no thread-kill -hack, `shell=True`, shell interpolation or client-selected worker executable. - -## History and errors - -The public status set is `pending | running | completed | failed | cancelled`. - -- Cancel: `cancelled` + `USER_CANCELLED`, after tree exit. -- Deadline: `failed` + `TIMEOUT`, after tree exit. -- Worker crash/missing result: `failed` + `WORKER_LOST`. -- Startup/unhandled execution exception: `failed` + `EXECUTION_FAILED`. -- Graceful service shutdown: `failed` + `SERVICE_SHUTDOWN`. -- Startup finds historical pending/running: `failed` + `SERVICE_RESTARTED`, with - an error timestamp and sanitized log entry, immediately persisted. - -FastAPI lifespan loads/reconciles history before serving and shuts the manager -down on exit. Windows kill-on-close containment and the Linux guardian's parent -watch handle abnormal parent termination. Restart never claims that old work was -resumed, and persisted PIDs are not used to signal potentially unrelated processes. - -## Logs, artifacts and security - -The manager publishes ordered structured log events and exposes them through the -HTTP offset/limit cursor API. `JobRequest.append_log()`, manager errors, and -persisted structured fields pass through the credential sanitizer. Arbitrary -third-party output written directly to process stdout/stderr is outside that -structured logging guarantee. - -Terminal status, result, and error snapshots are immutable. Historical logs stay -readable, and log events already in flight can be retained without changing the -terminal snapshot. - -Handlers collect files below `output_dir/job_id`; the manager normalizes the -result to safe relative artifact IDs. Download requires an exact manifest match. -Client-supplied artifacts are cleared at admission. - -At the same boundary the manager forces shell execution off and path whitelisting -on, discards client roots and regex patterns, and independently checks model, -data, and output paths against server-owned roots. Network data sources require -an allowlisted host. Process containment supports stop and cleanup; it is not a -per-job security sandbox, and the API has no authentication or RBAC. - -## Lightweight verification - -`tests/f1/test_worker_lifecycle.py` uses sleep workers, real children/grandchildren, -separate POSIX sessions and fake YOLO classes behind the four YOLO-backed handlers -(`train`, `val`, `predict`, and `export`). The non-YOLO `diagnose` handler is covered -by the handler inventory and focused handler suites. -It checks cancellation, timeout, normal completion, worker/startup failures, -parent exit, shutdown, restart/API errors, limits, and CPU/GPU slot reuse after -each terminal outcome. GPU tests select a GPU resource class but execute only CPU -sleep fixtures. No torch, real training, model/data download or large export is -needed for this suite. - -Example command (use a fresh temporary directory per run): - -```text -python -m pytest tests/f1/test_worker_lifecycle.py tests/api/test_api_v1.py tests/api/test_security_boundaries.py -o addopts= -p no:cacheprovider --confcutdir=tests/f1 --basetemp=runs/runtime-validation -q -``` - -The implementation follows Python's [spawn/process lifecycle documentation](https://docs.python.org/3/library/multiprocessing.html) -and Microsoft's [Job Object containment documentation](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects). +The [previous lifecycle guide](https://github.com/Tencent/YOLO-Master/blob/b10cdf11c9f7aa6cb949595391572e0eed1bf0f1/f1/RUNTIME_LIFECYCLE.md) +covers the retained F1 backend. Current verification methods are in +[testing](../docs/studio/testing.md). ## Verification record: 2026-09-15 +This record describes the historical F1 revision named below. + - The official baseline `acce839c7e895d6b179de7f7093fa879e237cc7b` is an ancestor of the reviewed commit `0e7a8f83b53c97abc8eb3caedc532f5779fbb086`. - Windows/Python 3.12 in the repository `.venv`: `412 passed, 11 skipped, diff --git a/f1/handlers/README.md b/f1/handlers/README.md index e02c2179..3bb8488d 100644 --- a/f1/handlers/README.md +++ b/f1/handlers/README.md @@ -1,31 +1,30 @@ -# F1 Task Handlers +# Legacy F1 task handlers The F1 handler package adapts the canonical job contract to the existing Ultralytics YOLO Python API. Handlers validate task parameters, call the engine, and return a structured result and artifact manifest. Queueing, process ownership, -timeouts, terminal publication, and persistence belong to `JobsManager`, not to -the handler layer. +timeouts, terminal publication, and persistence belong to `JobsManager`. -This document describes the current handler implementation. +Use this reference for the legacy `f1.handlers` package. For production +handlers and registration, see [Studio Core](../../docs/studio/core.md). ## Runtime position ```text -FastAPI -> JobsManager -> ManagedWorker -> dispatcher -> registry -> handler +Legacy direct caller -> f1 dispatcher -> f1 registry -> f1 handler | v Ultralytics YOLO API ``` -Production jobs must enter through the FastAPI API and its singleton -`JobsManager`. Direct dispatcher/handler use is supported for tests and embedding, -but it does not provide the managed process-tree termination guarantees. +Direct dispatcher/handler calls support tests and embedding. Use Studio Runtime +when process-tree cleanup is required; see [architecture](../../docs/studio/architecture.md). ## Package structure ```text f1/handlers/ -|-- __init__.py # imports all production handlers and triggers registration +|-- __init__.py # imports legacy handlers and triggers registration |-- base.py # BaseTaskHandler and validation/cancellation helpers |-- registry.py # TaskHandlerRegistry |-- predict.py # predict handler @@ -137,8 +136,8 @@ python -m pytest \ tests/f1/test_dispatcher.py -v ``` -The recorded F1 suite and API suite are documented in `f1/README.md`. The CI -workflow runs the broader `tests/f1/ tests/api/` set plus smoke and Ruff checks. +For the current compatibility and production selections, see +[testing](../../docs/studio/testing.md). See [USAGE.md](USAGE.md) for extension guidance and the Agent/F1 boundary. diff --git a/f1/handlers/USAGE.md b/f1/handlers/USAGE.md index ce089aae..b1fc614c 100644 --- a/f1/handlers/USAGE.md +++ b/f1/handlers/USAGE.md @@ -1,12 +1,13 @@ -# F1 Handler Extension Guide +# Legacy F1 direct-use handler extension guide -This guide targets developers extending the current handler implementation. For -user-facing submission and REST examples, see `docs/f1_user_manual.md`. +Use these examples to extend the legacy F1 direct-call implementation. +For production handler registration, see [Studio Core](../../docs/studio/core.md). +For job submission, use the [Studio user guide](../../docs/studio/user_guide.md). ## 1. Understand the ownership boundary ```text -client -> FastAPI -> JobsManager -> ManagedWorker -> dispatcher -> handler +Legacy direct caller -> f1 dispatcher -> f1 registry -> f1 handler ``` Handlers own task-specific validation and engine adaptation. They do not own: @@ -70,9 +71,8 @@ class ExampleHandler(BaseTaskHandler): } ``` -This example demonstrates the method shape only. Registering `example` does not -make it a public API task until the canonical schema and all public surfaces are -updated as described in section 6. +This example demonstrates the method shape. To expose `example` as a public API +task, update the canonical schema and public surfaces as described in section 6. ## 3. Validation and error classification @@ -152,8 +152,7 @@ A new public task is a cross-layer contract change. Update all applicable areas: Agent-facing capability. 5. Update REST/Gradio/zero-build/React form validation and presets as needed. 6. Add handler inventory, dispatcher, API contract, lifecycle, and UI tests. -7. Update this documentation without claiming support before every public surface - accepts the new task. +7. Document the new task and the public surfaces that accept it. The dispatcher normally needs no task-specific branch because it uses the registry, but the schema and clients are closed over the current five task types. @@ -210,11 +209,11 @@ ruff format --check f1/ tests/f1/ smoke/ core/ api/ main_engine.py tests/api/ ap ## 9. Agent/F1 boundary -F1 handlers are not Agent Skill handlers. Production F1 routing ends in +F1 handlers are not Agent Skill handlers. Legacy direct-use F1 routing ends in `f1/handlers/`; Agent CLI dispatch and multimodal skill behavior live under `agent/`. Share canonical task names through `core/` only where the code already -defines an explicit shared contract. Do not route F1 jobs through an Agent worker -or treat Agent validation as evidence for F1 lifecycle behavior. +defines an explicit shared contract. F1 lifecycle checks use the F1 runtime and +its test suite. ## Current limitations @@ -229,3 +228,6 @@ or treat Agent validation as evidence for F1 lifecycle behavior. **Document version**: 1.2.0 **Last synchronized**: 2026-09-15 + +For current production and compatibility verification selections, see +[testing and reproduction](../../docs/studio/testing.md).