File size: 19,631 Bytes
0ffbb74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# 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.