{"id":49994077,"url":"https://github.com/webbertakken/studio-worker","last_synced_at":"2026-05-19T06:42:43.379Z","repository":{"id":358320234,"uuid":"1240900913","full_name":"webbertakken/studio-worker","owner":"webbertakken","description":"Pull-based multi-modal generation worker (image / LLM / audio / video) for the minis.gg studio.","archived":false,"fork":false,"pushed_at":"2026-05-16T19:09:30.000Z","size":160,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-16T20:41:12.783Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webbertakken.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":"webbertakken","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2026-05-16T17:57:51.000Z","updated_at":"2026-05-16T19:09:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/webbertakken/studio-worker","commit_stats":null,"previous_names":["webbertakken/studio-worker"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/webbertakken/studio-worker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webbertakken%2Fstudio-worker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webbertakken%2Fstudio-worker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webbertakken%2Fstudio-worker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webbertakken%2Fstudio-worker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webbertakken","download_url":"https://codeload.github.com/webbertakken/studio-worker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webbertakken%2Fstudio-worker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33204735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"online","status_checked_at":"2026-05-19T02:00:06.763Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-19T06:42:42.620Z","updated_at":"2026-05-19T06:42:43.374Z","avatar_url":"https://github.com/webbertakken.png","language":"Rust","funding_links":["https://github.com/sponsors/webbertakken"],"categories":[],"sub_categories":[],"readme":"# studio-worker\n\n[![Checks](https://github.com/webbertakken/studio-worker/actions/workflows/checks.yml/badge.svg)](https://github.com/webbertakken/studio-worker/actions/workflows/checks.yml)\n[![Build](https://github.com/webbertakken/studio-worker/actions/workflows/build.yml/badge.svg)](https://github.com/webbertakken/studio-worker/actions/workflows/build.yml)\n[![Coverage](https://github.com/webbertakken/studio-worker/actions/workflows/coverage.yml/badge.svg)](https://github.com/webbertakken/studio-worker/actions/workflows/coverage.yml)\n\nA single self-contained Rust binary that pulls **image**, **LLM**,\n**audio (STT/TTS)**, and **video** jobs from the minis.gg studio API,\nruns them locally, and posts the results back.\n\nReplaces the previous push-based studio-proxy + cloudflared topology\nwith a pull-based pipeline: install the worker on any PC, register\nonce, and it will claim queued jobs whose VRAM estimate fits its\nthreshold.  The worker also **auto-updates itself** between jobs.\n\n## Tasks supported\n\n| Kind        | Wire `kind`   | Synthetic engine (default)                   | Real engine (planned)     |\n| ----------- | ------------- | -------------------------------------------- | ------------------------- |\n| Image       | `image`       | real WEBP / PNG via the `image` crate        | `image-candle` / `gradio` |\n| LLM         | `llm`         | OpenAI-shape JSON (`chat.completion`)        | `llama` (llama.cpp)       |\n| Audio STT   | `audio_stt`   | Whisper-shape JSON                           | `whisper` (whisper.cpp)   |\n| Audio TTS   | `audio_tts`   | real WAV (sine wave keyed by hash(text))     | `tts-piper`               |\n| Video       | `video`       | real WebP image (single-frame stand-in)      | `video-ffmpeg`            |\n\nThe synthetic engine is the default and exercises the full pipeline\nend-to-end with no GPU, no model downloads, and ~0 ms per task — exactly\nwhat the unattended CI suite uses.  Real high-performance backends\n(llama.cpp, whisper.cpp, candle, Piper, ffmpeg) are wired in via\nfeature flags and are deferred to a follow-up iteration (the trait,\ncontract, and dispatch are already in place).\n\n## Quick install\n\n### Linux / macOS\n\n```bash\ncurl --proto '=https' --tlsv1.2 -LsSf \\\n  https://github.com/webbertakken/studio-worker/releases/latest/download/studio-worker-installer.sh | sh\n```\n\n### Windows (PowerShell)\n\n```powershell\nirm https://github.com/webbertakken/studio-worker/releases/latest/download/studio-worker-installer.ps1 | iex\n```\n\n### From cargo\n\n```bash\ncargo install studio-worker\n```\n\nEach release ships pre-built binaries for:\n\n- `x86_64-pc-windows-msvc`\n- `x86_64-unknown-linux-gnu`\n- `aarch64-unknown-linux-gnu`\n- `aarch64-apple-darwin`\n- `x86_64-apple-darwin`\n\n## First run\n\n```bash\n# 1. Register with the studio API.\nstudio-worker register \\\n  --bootstrap-token \u003cTOKEN\u003e \\\n  --api-base-url https://studio.example.com\n\n# 2. Install the auto-start service (systemd --user on Linux, launchd\n#    on macOS, scheduled task on Windows).\nstudio-worker install-service\n```\n\n## CLI subcommands\n\n| Subcommand           | Purpose                                                         |\n| -------------------- | --------------------------------------------------------------- |\n| `run`                | Start the heartbeat + claim + log + auto-update loops.          |\n| `register`           | One-shot register with the API.  Idempotent.                    |\n| `status`             | Print the local config + heartbeat info.                        |\n| `install-service`    | Install the auto-start OS service.                              |\n| `uninstall-service`  | Remove the auto-start OS service.                               |\n| `enable`             | Set `auto_enabled = true` (resume claiming).                    |\n| `disable`            | Set `auto_enabled = false` (worker online but doesn't claim).   |\n| `set-threshold \u003cgb\u003e` | Set the max VRAM (GB) the worker is willing to claim per job.   |\n| `config`             | Print the resolved config + its on-disk path.                   |\n| `check-update`       | Check the release feed for a newer version (does not install).  |\n\n## Configuration\n\nConfig lives at:\n\n- Linux/macOS — `~/.config/minis-studio-worker/config.toml`\n- Windows — `%APPDATA%\\minis-studio-worker\\config.toml`\n\n```toml\napi_base_url        = \"https://studio.example.com\"\nbootstrap_token     = \"\u003cused only at register\u003e\"\nworker_id           = \"\u003cfilled by register\u003e\"\nauth_token          = \"\u003cfilled by register\u003e\"\nvram_threshold_gb   = 12.0                       # max GB per claim\nauto_start          = true\nauto_enabled        = true\nengine              = \"synthetic\"                # or \"gradio\"\n\n# Only used when engine = \"gradio\":\ngradio_endpoint_url = \"http://127.0.0.1:7860\"\n\n# Optional: only declare these models to the studio.\nsupported_models_override = []\n\n# Auto-update — checks the release feed on the cadence below, applies\n# updates only when no job is running, then re-execs the new binary.\nauto_update_enabled       = true\nauto_update_interval_secs = 1800\nauto_update_feed          = \"https://api.github.com/repos/webbertakken/studio-worker/releases\"\nauto_update_prerelease    = false\n```\n\n## Engines\n\n- **`synthetic`** (default) — produces deterministic, real WEBP/PNG/WAV/JSON\n  outputs keyed by SHA-256 of the prompt/text/input.  No GPU required.  Use\n  for smoke-tests, CI, and end-to-end verification of every modality.\n- **`gradio`** — talks to a Gradio app running on `127.0.0.1` (image only).\n  Drops the cloudflared tunnel entirely.  Supply the local Gradio URL in\n  `gradio_endpoint_url` and the models you've verified in\n  `supported_models_override`.\n\n### Adding a real engine\n\nImplement the `Engine` trait in `src/engine.rs` (see `SyntheticEngine`\nand `GradioEngine` for examples).  An engine declares its `capabilities`\n(per-kind supported models) and a `dispatch(model, task) -\u003e TaskResult`\nfunction.  Wire it into `engine::build()` behind a cargo feature, e.g.:\n\n```toml\n[features]\nllama = [\"dep:llama-cpp-2\"]\n```\n\nThe trait is already kind-aware so a single binary can host multiple\nengines (one per modality).\n\n## VRAM threshold\n\nThe worker reports two numbers to the API:\n\n- `vramTotalGb` — physical VRAM on the host (probed from\n  `/proc/driver/nvidia` on Linux; `0` when no NVIDIA GPU is present).\n- `vramThresholdGb` — the **max** estimated VRAM per claim, controlled by\n  the operator via `set-threshold` or by editing `config.toml`.\n\nThe studio API only hands a job to a worker if `job.vramGbEstimate ≤\nworker.vramThresholdGb` **and** `job.model ∈ worker.supportedModels`.\nJobs that no worker can take stay `queued` until either a suitable worker\nappears or the operator cancels.\n\n## Auto-update\n\nA dedicated background task polls the GitHub Releases feed every\n`auto_update_interval_secs` (default 30 min).  When a higher semver is\navailable the worker:\n\n1. Confirms no job is currently in flight (per a shared `busy` flag).\n2. Downloads the cargo-dist installer for the current platform.\n3. Runs it (it overwrites the binary in place).\n4. Re-execs itself so the new code takes over.\n\nSet `auto_update_enabled = false` to opt out.  Set\n`auto_update_prerelease = true` to track pre-releases.\n\n## Observability\n\nEach tick of the worker pushes a batch of log entries to\n`POST /workers/\u003cid\u003e/logs`.  The studio surfaces these in its LogViewer.\n\n## Development\n\n```bash\ncargo test           # 169 unit + integration tests\ncargo clippy --tests -- -D warnings\ncargo fmt --check\ncargo llvm-cov --workspace \\\n  --ignore-filename-regex 'src/main\\.rs$' \\\n  --summary-only\n```\n\nCoverage CI enforces **≥ 90% line coverage**; current is **93.45%**.\nTruly-untestable bits excluded from the gate:\n\n- `src/main.rs` — the CLI bootstrap (all logic lives in `lib.rs`).\n- `update::RealRunner::{download, run_installer}` — real network +\n  process spawn (tested through the `UpdateRunner` trait with a fake).\n- `update::restart_self` — calls `execvp`, never returns.\n- `sys::detect_vram_gb` NVIDIA-specific branch — requires NVIDIA hardware.\n\nIntegration tests live under `tests/`:\n\n- `tests/http_contract.rs` — exercises every API endpoint against a\n  wiremock-based fake studio.\n- `tests/http_errors.rs` — error-status paths for every endpoint.\n- `tests/gradio_engine.rs` — GradioEngine code paths against a fake\n  Gradio (incl. data-URL / relative-URL / object-with-url responses).\n- `tests/multi_modal.rs` — every TaskKind round-trips through the\n  synthetic engine + decoders.\n- `tests/auto_update.rs` — release feed parsing + apply_with full flow.\n- `tests/runtime_helpers.rs` — one-shot CLI helpers via wiremock.\n- `tests/runtime_ticks.rs` — per-tick coverage of the background loops.\n- `tests/runtime_loops.rs` — spawn_* wrappers driven with millisecond\n  intervals to exercise the wrapper bodies.\n- `tests/full_loop.rs` — one full claim → generate → complete cycle.\n\n## Release process\n\n1. PRs merge to `main` with conventional-commit titles\n   (`feat:`, `fix:`, `docs:`, etc. — enforced by the Commit lint workflow).\n2. `release-please` opens a release PR that bumps the version and updates\n   the changelog.\n3. Merging the release PR creates a git tag.\n4. The tag triggers the `release.yml` workflow (cargo-dist), which builds\n   binaries for all supported targets and uploads them to the GitHub\n   release alongside `installer.sh` + `installer.ps1` one-liners.\n\n## Licence\n\nMIT.  See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebbertakken%2Fstudio-worker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebbertakken%2Fstudio-worker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebbertakken%2Fstudio-worker/lists"}