|
Download docs/hosting-plan.md from Farshadm/beampilot: direct link, hf CLI and curl.
- Browser
- Download file 19.6 kB
-
https://huggingface.co/spaces/Farshadm/beampilot/resolve/main/docs/hosting-plan.md
- Command line
-
hf download hf://spaces/Farshadm/beampilot/docs/hosting-plan.md
-
curl -L -o hosting-plan.md https://huggingface.co/spaces/Farshadm/beampilot/resolve/main/docs/hosting-plan.md
19.6 kB
| # Hosting plan: BeamPilot demo on Hugging Face Spaces | |
| Status: **approved 31 Oct 2026**, built on branch `add-hosting`. Decisions taken | |
| by the maintainer: | |
| 1. **Plan:** HF PRO, subscribed by the maintainer and kept until after the | |
| community-vote result on 16 Nov 2026. | |
| 2. **Lens replays:** ship **both**: the v3 L5 run and the seed-920 smoke run, so | |
| five replay files in total. | |
| 3. **Delivery:** upload with the `hf` CLI (section 6). | |
| Goal: a public URL where anyone can **replay** saved agent runs (no key needed) | |
| and, while the budget lasts, start **live runs** of the Muse Spark agent. | |
| ## 1. Platform: what the Hugging Face docs say (checked 31 Oct 2026) | |
| | Topic | What the docs say | Source | | |
| |---|---|---| | |
| | Free CPU tier | CPU Basic (2 vCPU, 16 GB) has no hourly cost, **but** Gradio and Docker Spaces "require a paid plan to create" (PRO for personal accounts). Only static Spaces are free for everyone. | [Spaces overview](https://huggingface.co/docs/hub/spaces-overview#hardware-resources), [GPU Spaces, CPU table note](https://huggingface.co/docs/hub/spaces-gpus) | | |
| | Free-account exception | Free accounts in good standing (verified email, account older than 30 days) can host up to 2 Gradio Spaces on **ZeroGPU**. | [ZeroGPU](https://huggingface.co/docs/hub/spaces-zerogpu) | | |
| | PRO price | $9 per month; the list includes "Host ZeroGPU, Gradio & Docker Spaces". | [Pricing](https://huggingface.co/pricing) | | |
| | Sleep | A `cpu-basic` Space sleeps when inactive "currently, 48 hours"; the next visitor restarts it automatically. You can't change the sleep time on free hardware. | [GPU Spaces, sleep time](https://huggingface.co/docs/hub/spaces-gpus#sleep-time) | | |
| | Secrets | Secrets are private. Their value can't be read back from the settings page, and they are not copied when someone duplicates the Space. The app sees them as environment variables (`os.getenv`). | [Spaces overview, secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets) | | |
| | Variables | Variables are public and are copied to duplicates. Use them for non-secret settings only. | same | | |
| | Front matter | Keys in a YAML block at the top of `README.md`: `title`, `emoji`, `colorFrom`, `colorTo`, `sdk`, `sdk_version`, `python_version`, `app_file`, `short_description`, `license`, `pinned`, `suggested_hardware`. | [Config reference](https://huggingface.co/docs/hub/spaces-config-reference) | | |
| | Dependencies | `requirements.txt` is installed by the runtime; `sdk_version` sets the Gradio version. | [Gradio Spaces](https://huggingface.co/docs/hub/spaces-sdks-gradio) | | |
| | Rebuild | Every new commit rebuilds and restarts the Space. | [Spaces overview](https://huggingface.co/docs/hub/spaces-overview) | | |
| | Network | Outbound requests may use ports 80, 443 and 8080 only. The Meta Model API is HTTPS on port 443. | same | | |
| | Git auth and large files | Push access needs a login (`hf auth login`, or a token in the git credential prompt). Binary files go through Git Xet. | [Repositories: getting started](https://huggingface.co/docs/hub/repositories-getting-started), [Xet](https://huggingface.co/docs/hub/xet/using-xet-storage) | | |
| **Verdict: the free CPU tier does not fit as asked.** A free account can no | |
| longer create a Gradio Space on CPU Basic. | |
| **Alternative (recommended): HF PRO for one month ($9).** Everything else in this | |
| plan stays the same: Gradio SDK, `cpu-basic`, and the same secrets. Cancel after | |
| the judging period. | |
| Not recommended: ZeroGPU on a free account. ZeroGPU is built for functions that | |
| use the `@spaces.GPU` decorator, and BeamPilot runs no GPU code. It would be an | |
| untested misuse of GPU hardware, and the 30-day account-age rule may not be met. | |
| **Decision 1 (taken):** PRO. | |
| ## 2. Replay without a key | |
| Replay reads JSON files from `docs/runs/` and calls no API. It works with no | |
| secret set. Five files ship; three were already tracked: | |
| | File | What it shows | Status | | |
| |---|---|---| | |
| | `docs/runs/mystery_s7.json` | Mystery beam (632.8 nm, MΒ² β 1.43) with a built-in **laser power Γ3** fault: the agent met 6 saturated frames, lowered the exposure, and reached `iso_compliant` in 18 positions ($0.09). | tracked | | |
| | `docs/runs/clean_room_light_live.json` | Clean beam with a **room-light fault** added by the operator before measurement 6: +3 counts of background ($0.07). | tracked | | |
| | `docs/runs/clean_operator_note.json` | Operator note ("stage blocked 60β80 mm") respected ($0.05). | tracked | | |
| | `docs/runs/lens_v3_L5.json` (byte-identical copy of `runs/evaluation-v3/L5_agent.json`) | **Lens run** from the pre-registered v3 evaluation: the waist lies outside the stage; the agent inserts +750 mm at β20 mm and reaches `iso_compliant` with the back-calculated original beam ($0.12). | added | | |
| | `docs/runs/lens_smoke_s920.json` (byte-identical copy of `runs/20261031-183949_waist_outside_range_s920_muse-spark-1.2.json`) | **Lens run**, the change_lens smoke run before v3: +25 mm at β20 mm, `iso_compliant` ($0.13). | added | | |
| **Decision 2 (taken):** ship both lens runs. | |
| **Labels.** Every replay is labelled on the page as "Recorded run, <date>, | |
| muse-spark-1.2", both in the run list and above the replay, so that a visitor | |
| never takes a replay for a live run. The model name comes from the file | |
| (`config.model`). | |
| The run files themselves hold no date. So the date comes from a small manifest, | |
| `docs/runs/recorded.csv` (file, date, source of the date): | |
| - the two lens runs: 31 Oct 2026, from the original file timestamps; | |
| - the three older runs: 27 Oct 2026, the date of commit becabe2, which added | |
| them. Their original timestamped files are no longer kept. | |
| A test checks that every replay file has a manifest entry. An uploaded file is | |
| labelled "Recorded run, date unknown, <model>". | |
| Read-only checks done on all five candidates on 31 Oct: | |
| - Searched for `C:\`, `Users`, `farsh`, the maintainer's e-mail domain, `MODEL_API_KEY`, `BASETEN`, `sk-`, `api_key`, `Bearer`, `.env`: **0 matches in every file**. | |
| - Searched for path-like strings (`X:\β¦`, `/home/β¦`, `/Users/β¦`): **none**. | |
| - Top-level keys are only scenario/case, seed, positions, truth, scenario | |
| parameters, fault events, lens changes, tool log, config (model name and limits), | |
| operator note, stop reason, usage, cost, report and steps. | |
| - `load_run` and `replay_events` load L5 (68 events), seed 920 (88) and | |
| mystery_s7 (59) without errors. | |
| A new test (section 5) repeats this scan for every `docs/runs/*.json` file, so a | |
| file added later can't leak a path or key. | |
| ## 3. Live runs: only with a secret, and capped | |
| A live run is possible only when the Space secret `MODEL_API_KEY` is set **and** | |
| the live switch is on. The protections: | |
| | Protection | Today in `app.py` | Planned | | |
| |---|---|---| | |
| | Queue concurrency | `queue(default_concurrency_limit=2)`, `MAX_CONCURRENT_RUNS = 2` | **1 live run at a time.** Set `concurrency_limit=1, concurrency_id="live"` on the Start event only, and `MAX_CONCURRENT_RUNS = 1`. Replay events keep the default concurrency, so replay never waits behind a 15-minute live run. | | |
| | Per-run caps | `AgentConfig`: $0.50, 1.5 M tokens and 60 model calls (lens: 80 calls, 28 positions); 15-minute timeout | Normal runs unchanged. **Live lens runs use the v3 limits:** 80 model calls, 3 M tokens, $0.50 and 28 positions. | | |
| | Per-tab caps | 3 runs and 2 M tokens per tab (convenience only) | 3 runs; **3 M tokens** per tab, so that one lens run can use its full v3 token limit. | | |
| | Process caps | 50 runs; `BEAMPILOT_TOKEN_CAP` (default 60 M tokens, about $20) | unchanged, plus the spend cap below | | |
| | **Spend cap per restart** | none | New env var `BEAMPILOT_SPEND_CAP_USD`, **default 5**. A thread-safe running total adds `run.cost_usd` after each run. A run starts only if spent + $0.55 (the per-run cap plus one call of overshoot) β€ cap. So the cap cannot be exceeded, even by the last run. | | |
| | **Live switch** | none | New env var `BEAMPILOT_LIVE_RUNS` (`on`/`off`, default `on` so local use is unchanged). On the Space it is a public **Variable**. Setting it to `off` turns live runs off without a code push. | | |
| | **Clear messages** | a "needs MODEL_API_KEY" text, shown only after Start is clicked | A banner when the page loads, and the Start button disabled, in three cases (checked in this order): **switched off** ("Live runs are switched off on this demo right now. Replay works."), **no key** ("Live runs are not set up on this demo (no MODEL_API_KEY). Replay works without one."), **budget used** ("This demo has used its live-run budget for now; use Replay."). The server-side check stays the real guard; the banner is only information. | | |
| Caveat: the $5 budget counter lives in process memory. It resets on every restart: | |
| a push, a settings change, or waking from sleep after 48 h. Visitors can't force a | |
| restart, but the cap is per restart, not lifetime. A typical live run costs | |
| $0.05β0.13, so $5 is roughly 40β100 runs. If the Meta Model API console offers a | |
| spending limit on the key, set it as a lifetime backstop (I have not checked | |
| whether it does). | |
| Scope: all changes are in `src/beampilot/app.py` (the UI) and new files. The | |
| agent, prompt and tools stay frozen. With `change_lens` off, nothing changes, so | |
| the hash test still holds. | |
| ## 4. The key is never logged, printed or saved | |
| - **Where the key lives:** only in the Space secret `MODEL_API_KEY`. `make_client()` reads it from the environment; there is no `.env` on the Space. The UI never asks for a key. | |
| - **What can leak today:** nothing found. API errors are already shown by type and HTTP status only, never by message text. `app.py` and `agent.py` contain no `print` or `logging` calls. Run files store the model name and limits, not the client. | |
| - **New test:** set `MODEL_API_KEY` to a sentinel string and run a live run against a fake client. Then assert the sentinel appears in none of: the saved run JSON, the UI outputs (log, report, messages), and the captured stdout and stderr. | |
| - **Hard rules:** | |
| - no Gradio `debug=True`; | |
| - no `show_error=True`; | |
| - no `OPENAI_LOG` variable on the Space; | |
| - live run files stay on the Space's temporary disk (`runs/`) and are not offered for download. | |
| - **Baseten:** the Baseten key is **not** set on the Space. The app uses only the Meta Model API. | |
| ## 5. Files needed | |
| 1. **Space README `space/README.md`.** The GitHub `README.md` gets **no** front | |
| matter. The deploy step copies `space/README.md` over `README.md` in the | |
| exported folder only. The Space README has the front matter below, plus a few | |
| plain lines: | |
| - what the demo does; | |
| - that all data is simulated; | |
| - that replays need no key; | |
| - a link to the GitHub repository. | |
| ```yaml | |
| --- | |
| title: BeamPilot | |
| emoji: π― | |
| colorFrom: indigo | |
| colorTo: blue | |
| sdk: gradio | |
| sdk_version: 6.30.0 | |
| python_version: "3.12" | |
| app_file: scripts/space_app.py | |
| short_description: Muse Spark agent measures laser MΒ² to ISO 11146 | |
| license: mit | |
| pinned: false | |
| suggested_hardware: cpu-basic | |
| --- | |
| ``` | |
| A test reads `space/README.md` and checks the front matter against the | |
| project: | |
| - `sdk_version` equals the Gradio pin in `pyproject.toml`; | |
| - `app_file` exists; | |
| - `short_description` is at most 60 characters. | |
| 2. **`requirements.txt`**, pinned to the versions tested locally. Gradio is | |
| **not** listed, because `sdk_version` pins it (6.30.0, the same pin as the | |
| `demo` extra). | |
| ```text | |
| numpy==2.5.4 | |
| scipy==1.18.1 | |
| scikit-image==0.26.0 | |
| matplotlib==3.11.2 | |
| openai==3.38.0 | |
| python-dotenv==1.2.4 | |
| ``` | |
| A test checks that these names match `[project] dependencies` in `pyproject.toml`. | |
| 3. **Entry point `scripts/space_app.py`** (about 10 lines). It puts `src/` on | |
| `sys.path` and calls `beampilot.app.main()`. This keeps the src layout and the | |
| `REPO_ROOT` lookup that finds `docs/runs/`; a root `app.py` would also work. The | |
| package is not pip-installed on the Space, because an installed copy would | |
| not find `docs/runs/`. | |
| 4. **`docs/runs/lens_v3_L5.json`, `docs/runs/lens_smoke_s920.json`, and | |
| `docs/runs/recorded.csv`**: the two lens replays and the manifest of | |
| recording dates (section 2). | |
| 5. **Code and tests:** | |
| - the `app.py` changes from section 3 and the replay labels; | |
| - tests for the spend cap (fake costs), the live switch, the no-key banner, the key sentinel, the run-file scan and the requirement pins; | |
| - the run-file scan looks for drive and home paths, e-mail addresses and key-like strings. It also looks for the local user name, home folder and git e-mail of whoever runs the tests, read at test time, so no personal data is written into the test; | |
| - the new env vars added to `.env.example` as comments (no values; Baseten entries untouched). | |
| 6. **README:** a "Hosted demo" line with the Space URL, added after the first | |
| successful deploy. | |
| ## 6. Code delivery: a git remote is a problem, so upload instead | |
| You asked for a separate git remote for the Space and a push of `main`. Two | |
| problems block a plain push: | |
| 1. **History contains a binary file.** `docs/figures/hero.png` (330 kB) is stored | |
| as a plain git blob in commits 18397f1 and 6c1b414. Forum reports show the Hub | |
| rejects pushes that contain binary files not stored through Xet, and that | |
| removing the file from the latest commit is not enough. Examples: | |
| [HF forum: push rejected due to binary files](https://discuss.huggingface.co/t/push-reject-due-to-binary-files/88990), | |
| [an example error log](https://huggingface.co/spaces/Alexramsal/AcuarelaPortraits/blob/main/git_error.txt). | |
| The fix would be **rewriting history**, which our rules forbid. | |
| 2. **A new Space already has a first commit** (`.gitattributes`, `README.md`), | |
| created by the Hub. Pushing `main` on top of it is not a fast-forward, so it | |
| would need a **force push** (forbidden) or a merge of the Space's commit into | |
| `main`. | |
| **Recommended instead: upload a clean export of `main` with the `hf` CLI.** | |
| - `git archive main` exports exactly the committed files: no `.env`, no `runs/`, no `.venv`. | |
| - `hf upload` sends them as one new commit on top of the Space's first commit. That is no force push and no history rewrite. The CLI handles the PNG through Xet by itself. | |
| - GitHub stays the only git history. The Space is a deploy target. | |
| **Decision 3 (taken):** upload with the `hf` CLI. | |
| **`.gitattributes`:** the repository has none (checked 31 Oct 2026). So the | |
| upload cannot overwrite the Space's own `.gitattributes`, which the Hub created | |
| for Xet/LFS handling. The deploy command still passes | |
| `--exclude ".gitattributes"`, so that a file added later can't replace it. | |
| ## 7. Steps for the maintainer (browser and PowerShell) | |
| I will not handle any token or password. Steps 1β4 are in the browser; steps 5β7 | |
| are in PowerShell, run by you. | |
| 1. **Plan:** subscribe to PRO (Decision 1), and keep it until after the vote result | |
| on 16 Nov: https://huggingface.co/subscribe/pro | |
| 2. **Create the Space:** https://huggingface.co/new-space. Settings: | |
| - Owner: your user; name `beampilot`; licence MIT; | |
| - SDK **Gradio**; hardware **CPU Basic (free)**; visibility **Public**. | |
| 3. **Add the secret and variables** under the Space's *Settings β Variables and secrets*: | |
| - **New secret** `MODEL_API_KEY`: paste the key there yourself. | |
| If the Meta Model API console allows a second key, create a **dedicated | |
| key for the Space**, and revoke it after judging; then the Space can never | |
| spend from your main key. (Unverified whether the console allows a second | |
| key.) | |
| - **New variable** `BEAMPILOT_LIVE_RUNS` = `off` for the first deploy. Turn it on after the replay check. | |
| - **New variable** `BEAMPILOT_SPEND_CAP_USD` = `5`. | |
| 4. **Create a write token** at https://huggingface.co/settings/tokens. Choose | |
| *Fine-grained*, with write access to this one Space only. | |
| 5. **Install the `hf` CLI in its own tools venv** (not in the project venv): | |
| ```powershell | |
| py -3.12 -m venv C:\dev\hf-tools | |
| C:\dev\hf-tools\Scripts\python.exe -m pip install -U huggingface_hub | |
| C:\dev\hf-tools\Scripts\hf.exe auth login | |
| ``` | |
| `hf auth login` asks for the token in a hidden prompt; paste it there. Answer | |
| **n** to "Add token as git credential?" (we don't push with git). | |
| 6. **Deploy main** (replace `<HF_USER>`; run from `C:\dev\beampilot` on a clean, | |
| up-to-date `main`): | |
| ```powershell | |
| git switch main | |
| git status | |
| git pull | |
| Remove-Item -Recurse -Force "$env:TEMP\beampilot-space" -ErrorAction SilentlyContinue | |
| git archive --format=zip -o "$env:TEMP\beampilot-space.zip" main | |
| Expand-Archive "$env:TEMP\beampilot-space.zip" -DestinationPath "$env:TEMP\beampilot-space" -Force | |
| Copy-Item "$env:TEMP\beampilot-space\space\README.md" "$env:TEMP\beampilot-space\README.md" -Force | |
| C:\dev\hf-tools\Scripts\hf.exe upload <HF_USER>/beampilot "$env:TEMP\beampilot-space" . --repo-type space --exclude ".gitattributes" --commit-message "Deploy main $(git rev-parse --short main)" | |
| ``` | |
| What each line does: | |
| 1. `git switch main`: switch to `main`. | |
| 2. `git status`: check there are no local changes. | |
| 3. `git pull`: fetch the latest `main` from GitHub. | |
| 4. `Remove-Item`: delete the previous export. | |
| 5. `git archive`: export the committed files of `main` (no `.env`, no `runs/`) as a zip. | |
| 6. `Expand-Archive`: unzip it. | |
| 7. `Copy-Item`: put the Space README, with its front matter, in place of the GitHub README. | |
| 8. `hf upload`: upload the folder as one commit to the Space, keeping the Space's own `.gitattributes`. | |
| The Space builds and restarts. Watch the *Logs* tab on the Space page. | |
| 7. **Later deploys:** repeat step 6. Files deleted from `main` are not deleted on | |
| the Space by an upload; remove them in the Space's *Files* tab, if any. | |
| 8. **Optional:** `C:\dev\hf-tools\Scripts\hf.exe auth logout` when done; revoke the | |
| token after the hackathon. | |
| ## 8. Dry run on the Space (after the first deploy) | |
| 1. With `BEAMPILOT_LIVE_RUNS=off`, check: | |
| - the banner says live runs are off; | |
| - the Start button is disabled; | |
| - all **five** replays play, each labelled "Recorded run, <date>, | |
| muse-spark-1.2", including both lens runs with their segment plots. | |
| 2. Search the build and run logs for the first characters of the key; there | |
| should be no match. Check by eye, and never paste the key anywhere. | |
| 3. Set `BEAMPILOT_LIVE_RUNS=on` and do one live run (about $0.05β0.13). | |
| - Check that a second tab queues instead of running in parallel. | |
| - Check the cost line in the report. | |
| 4. Temporarily set `BEAMPILOT_SPEND_CAP_USD=0.3` and confirm that the budget | |
| message appears; then set it back to 5. | |
| 5. Record the Space URL in the README (one commit), then upload again. | |
| ## 9. Time estimate | |
| | Work | Who | Time | | |
| |---|---|---| | |
| | Code: switch, spend cap, concurrency, banner, entry point, requirements, front matter, lens replay file | Claude | 1.5 h | | |
| | Tests (section 4β5) and the ruff/pytest gate, small commits on a branch | Claude | 1 h | | |
| | Browser steps 1β4 and CLI steps 5β6 | maintainer | 20β30 min | | |
| | First build, dry run (section 8), fixes | both | 1 h | | |
| | Buffer for Space-specific surprises | β | 1 h | | |
| | **Total** | | **about 4.5β5 h**; API cost β€ $1 for the dry run | | |
| This fits comfortably before the deadline (Wed 4 Nov, 21:00 ET), leaving the | |
| video script as the next step. | |