beampilot / docs /hosting-plan.md
Farshadm's picture
Deploy main ca11afa
0ffbb74 verified
|
Raw History Blame Contribute Delete
19.6 kB

A newer version of the Gradio SDK is available: 6.31.0

Upgrade

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, GPU Spaces, CPU table note
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
PRO price $9 per month; the list includes "Host ZeroGPU, Gradio & Docker Spaces". 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
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
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
Dependencies requirements.txt is installed by the runtime; sdk_version sets the Gradio version. Gradio Spaces
Rebuild Every new commit rebuilds and restarts the Space. 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, Xet

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, , 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, ".

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.
    ---
    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).

    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, an example error log. 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):

    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):

    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, , 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.