File size: 12,648 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
# BeamPilot agent design (v1)

Status: v1, agreed 26 Oct 2026.
Updated 27 Oct 2026 (added `retake_darks`). All data is simulated. No lab data, no code from other projects.

## 1. Goal and pitch

BeamPilot is an agent that measures the M² of a laser beam to ISO 11146 with a camera
on a moving stage (simulated). It decides where to measure, judges whether each
measurement can be trusted, recovers from faults, and gives an honest final verdict,
including "this cannot be measured to the standard with this setup".

Why an LLM and not a fixed script: commercial M² instruments already place ISO points
automatically on clean beams. BeamPilot's value is **judgement when things go wrong**
(saturation, background drift, beam too large for the sensor, waist out of reach,
different x/y waists) and **explaining every decision**. We test this claim against a
smart scripted baseline and report the result honestly, even if the agent only wins on
fault scenarios.

## 2. Who does what

| Part | Job | Never does |
|---|---|---|
| Muse Spark (agent) | Chooses positions and exposure, accepts or rejects measurements, decides when to finish, explains each action | Compute or type physics numbers. The report numbers come from Python state. |
| Python tools | Simulator, ISO 11146 widths, weighted caustic fit, coverage and sanity checks | Decide what to measure next or discard data on their own |
| Python controller | Runs the loop, holds all state, enforces hard limits, logs everything | Trust the agent to stop or to report correct numbers |

The phases in section 5 are guidance in the system prompt, not controller code.
The model chooses the order of actions; the controller only enforces limits.

## 3. Units

- Python computes **internally in SI** (metres, radians).
- The tool interface shows z in mm, diameters in µm and wavelength in nm, because that is
  easier for the model to read. Conversion happens only at the tool boundary.
- A known-answer test checks M² end to end, so a unit error (for example a factor 10³) fails.

## 4. Tools (7 core)

Every tool has a required `reason` string. Tool schemas are strict, and verdicts are enums.

1. `get_setup(reason)`
   - Wavelength, stage range, pixel size, sensor size, exposure limits, position budget left.
2. `measure_at(z_mm, exposure_ms, reason)`
   - Moves the stage, takes **5 repeated frames** (ISO 11146-1 asks for at least 5 per position),
     applies background correction, computes ISO second-moment diameters.

   - Returns: `measurement_id`, mean ± std of d_x and d_y, quality flags (saturation fraction,

     peak / full scale, edge clipping, pixels per beam width, residual background, ellipse azimuth),

     and a *suggested* verdict. The measurement starts as `pending`.

   - Several `measure_at` calls may come in one turn (parallel tool calls); the controller runs

     them one after another, like a real stage.

3. `set_status(measurement_id, status, reason)`

   - `status` is `accept` or `reject`. Only accepted measurements go into the fit.

   - Guardrail: the controller refuses to accept a saturated measurement.

4. `evaluate(reason)`

   - Weighted caustic fit **per axis**, per-axis ISO coverage in units of zR

     ("x: 3 within ±1 zR, need about 5; 1 beyond 2 zR, need about 5"),

     waist accessibility (inside / outside / uncertain), general-astigmatism check, M² < 1

     sanity flag,

     and a compact table of all measurements.

   - If an axis fit fails, or no accepted position lies within ±1 zR of its fitted waist,

     d0, z0, zR and M² of that axis are reported as null with a `not_determined` reason

     (and `waist_side`: below/above the measured positions or within a gap,

     `waist_gap_mm`), never as an extrapolated number. The same applies to the `finish`

     report.

   - Waist accessibility per axis from z0 ± 2σ against the stage range: `inside`,

     `outside` or `uncertain` (then `waist_near_stage_end`: measure closer to that end).

     Only `outside` supports the `waist_not_accessible` verdict.

5. `finish(verdict, explanation)`

   - `verdict` is one of: `iso_compliant`, `estimate_not_compliant`, `waist_not_accessible`,

     `general_astigmatism_out_of_scope`, `measurement_failed`.

   - Python builds the report numbers from stored state. The model writes only the verdict and the text.

   - Guardrail: if the model claims `iso_compliant` but `evaluate` says coverage is not met,

     or flags general astigmatism or M² < 1, the controller returns an error and the agent

     must continue or choose another verdict. `waist_not_accessible` is refused unless an

     axis is `outside` and an accepted position lies within 5 % of the stage length from

     that stage end. A waist up to about 1 zR outside the stage may still give an accurate

     M²; the verdict is then `waist_not_accessible` by definition (no near-waist points on

     both sides), not because the number is unusable.

   - If a hard limit (model calls, tokens, cost) or an API error stops the agent first,

     Python writes a fallback report from session state: `estimate_not_compliant` if M²

     is determined on both axes, otherwise `measurement_failed`, with `stopped_by_limit`

     holding the reason.

6. `retake_darks(reason)`

   - Blocks the beam and takes a fresh dark set (50 frames) with the current background, so a

     background change (e.g. room light switched on) becomes part of the dark.

   - Costs one position from the budget. Use it when `measure_at` reports `background_changed`.

7. `view_frame(measurement_id, reason)`

   - Optional. Returns the first frame of a measurement as a PNG image for the model (Muse

     Spark image input), on a square-root brightness scale, so the agent can see what the

     second moments hide (e.g. a ghost reflection).

   - Separate small budget (4 views per run), no position cost. The image goes to the model

     inside the tool output and is not stored in logs or run files.

   - Only the 2 most recent images stay in the resent history; older ones become a text

     stub. `AgentConfig(view_frame=False)` switches the tool off (evaluation ablation).


Stretch tool (only after the core works): `change_lens(f_mm, reason)` for an inaccessible
waist (ISO uses an artificial waist made with an aberration-free lens, and M² stays the same).

## 5. Loop guidance (in the system prompt)

```

Scout:    get_setup, a few measurements spread over the stage range, fix exposure if needed

Locate:   evaluate → first estimates of z0 and zR for x and y

Fill:     measure where coverage is missing, accept/reject each result, evaluate again

Finish:   when coverage is met on both axes, or when the setup cannot meet it

```

Hard limits in the controller: maximum positions, maximum agent turns, token budget per run.
Exact numbers are set after the first test runs.

## 6. Physics (ISO 11146-1)

- Second-moment diameter d = 4σ, per principal axis (x and y, not rotated).
- Integration area: centred on the centroid, 3 × the beam widths, iterated until it converges
  (2005 wording; the 2021 edition relaxed this, exact text unverified).
- Background: subtract a dark frame, then a fine offset from corner patches (2–5 % of the image),
  and **keep negative pixel values** (ISO/TR 11146-3).
- Caustic fit: d(z)² = a + b·z + c·z², fitted by minimising squared **relative** diameter
  deviations, weighted by the repeat variance (`scipy.optimize.least_squares`, not plain `polyfit`).
- Results: z0 = −b/(2c), d0 = √(a − b²/(4c)), Θ = √c (full angle), zR = d0/Θ,
  M² = (π/(8λ)) · √(4ac − b²).
- Point rule: at least 10 positions, about half within one zR of the waist and about half beyond
  2 zR, applied **separately for x and y**.
- Detector rules: at least 20 pixels across the beam (spatial resolution ≥ 1/20 of the beam width),
  clipping loss under 1 % of the power. These create real trade-offs: far from the waist the beam
  can outgrow the sensor; near the waist it can get too small for the pixels.
- General astigmatism: if the azimuth of two non-circular profiles differs by more than 10°,
  ISO 11146-2 applies. That is out of scope: the agent detects it and stops.
- M² > 1 in the simulator: Gaussian profile with an enlarged width ("embedded Gaussian").
  Hermite-Gaussian mode mixes are a later option.

Source of these points: the review read the ISO 11146-1:2005 and ISO/TR 11146-3 texts from
third-party copies. The maintainer should check the key rules once before claiming compliance.

## 7. Scenarios (all simulated)

Core:
1. Clean beam, M² close to 1.
2. Saturation at some positions only.
3. Background drift during the run ("room light turned on").
4. Beam too large for the sensor at far positions.
5. Waist outside the stage range → `waist_not_accessible` with explanation.
6. Different waist positions in x and y (simple astigmatism). All code is per-axis from day one.

Ghost reflection (with `view_frame`): `ghost_everywhere` (expected `measurement_failed`) and
`ghost_in_range` (expected `iso_compliant` after rejecting the ghost positions).
Held-out faults for evaluation only (not in `SCENARIOS`): iris aperture over a z range,
hot pixels, pointing drift (see `docs/evaluation-plan.md`).

Stretch: `change_lens`, general astigmatism detection demo.

Demo features: fault buttons the judge can press during a run, a "mystery beam" button with
random hidden parameters (ground truth shown at the end), and a replay mode for recorded runs.

## 8. Evaluation

Three contenders on the same set of randomised hidden scenarios (fixed seed):
1. Naive uniform scan
2. Smart scripted ISO rule (scout, fit, place points, refit, simple fault rules)
3. Muse Spark agent

Metrics: ISO-compliance rate, M² error versus ground truth, faults noticed, positions used.
The scripted baseline must be a fair, honest attempt, not built to fail.
Batch size is decided after measuring the cost of one agent run.

## 9. Testing

- Known-answer tests: ideal Gaussian gives M² = 1; a known M² > 1 is recovered; constant offset
  is removed; near-waist-only sampling gives a biased result (divergence badly constrained).
- Optional test oracle: the open-source `laserbeamsize` package, as a dev dependency only
  (licence and use to be checked and agreed first).
- The scripted baseline also tests the tools and controller without spending API credits.

## 10. Muse Spark settings

- Responses API with function calling. Output cap is `max_output_tokens` (reasoning tokens count
  toward it). Resend the system instructions on every call (`previous_response_id` does not keep them).
  Tool results go back as `function_call_output` with the matching `call_id`.
- `reasoning_effort` low or medium for normal steps. Measure latency and tokens; don't guess.
- Development batches may use the cheaper contributor model (data used for training; we only
  send simulated data). Whether hackathon credits cover it: unverified.
- Log tokens and the `reason` field for every step.

## 11. Schedule (Nuremberg time; adjust to real available hours)

| Day | Goal |
|---|---|
| Tue 27 Oct | Simulator (per-axis Gaussian, M², noise, offset, saturation, dark frame) + tests. Workshop 17:00. |
| Wed 28 Oct | ISO widths (corner offset, iterative 3× area) + tests. Workshop 17:00. |
| Thu 29 Oct | Weighted fit and `evaluate` (coverage, checks) + tests |
| Fri 30 Oct | Tool wrappers, controller, scripted baseline from the command line. Check HF Spaces plan. |
| Sat 31 Oct – Sun 1 Nov | Muse Spark loop, logging, first agent runs |
| Mon 2 Nov | Gradio UI (caustic plot, decision log, fault buttons, replay), deploy. **Feature freeze.** |
| Tue 3 Nov | Small honest evaluation, README, record the video |
| Wed 4 Nov | Buffer. Submit in the evening. |

Drop in this order if behind: SAM 3.1 → ghost scenario and `view_frame` → `change_lens` →
polished x/y-waist demo (keep the per-axis code). Never drop: known-answer tests, scripted
baseline, replay mode, the video.

## 12. Open decisions

1. Hosting: Gradio on Hugging Face Spaces is the likely choice. Check the current free/paid
   rules for Spaces before deciding (the review reported changes; unverified).
2. `laserbeamsize` as a dev-only test oracle: yes or no.
3. Impact framing for the video: who benefits (for example small labs and teaching labs without a
   commercial M² instrument). To be written without claims we cannot back up.
4. Whether SAM 3.1 gets its one-hour test at all.