Publish tiny_computer_agent_app.py from marimo
Browse files- tiny_computer_agent_app.py +109 -67
tiny_computer_agent_app.py
CHANGED
|
@@ -44,9 +44,10 @@ def readme():
|
|
| 44 |
No cloud API, no API keys, no telemetry — and a core that imports nothing outside the
|
| 45 |
Python standard library.
|
| 46 |
|
| 47 |
-
> **Status 0.3.0: it drives a real desktop.** Screen capture,
|
| 48 |
-
> real coordinates, a real mouse click, and a *verified*
|
| 49 |
-
>
|
|
|
|
| 50 |
> [measured results](#measured-results).
|
| 51 |
|
| 52 |
## The idea, in one loop
|
|
@@ -209,6 +210,69 @@ def readme():
|
|
| 209 |
**125 tests pass**, including real OCR over a real image and (opt-in) live-desktop tests
|
| 210 |
that click a real button.
|
| 211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
## The test suite, in full
|
| 213 |
|
| 214 |
`pytest tests` collects **125 tests**. 123 run headless — no display, no network, no
|
|
@@ -283,76 +347,50 @@ def readme():
|
|
| 283 |
|
| 284 |
### Testing on Windows - three ways, and which to use
|
| 285 |
|
| 286 |
-
|
| 287 |
-
`GrabScreen` already falls back to Pillow's `ImageGrab` there. Two things can only fail on
|
| 288 |
-
a real Windows desktop, which is why these runs exist: **DPI scaling** (that is what
|
| 289 |
-
`--scale` is for) and the native input backend. At 125% or 150% display scaling, expect the
|
| 290 |
-
coordinate assertion in `test_live_desktop.py` to be the first thing that fails - that is
|
| 291 |
-
the signal, not a nuisance.
|
| 292 |
|
| 293 |
| Method | Real Windows desktop? | Cost | Status |
|
| 294 |
|---|---|---|---|
|
| 295 |
-
| **GitHub Actions `windows-latest`** | yes,
|
| 296 |
-
| **QEMU/TCG VM on a Linux host** | yes,
|
| 297 |
-
| **A cloud Windows
|
| 298 |
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
`
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
**2. A local QEMU VM without KVM.** Measured on the headless container above - this is
|
| 307 |
-
what the box actually reported, not what the docs claim:
|
| 308 |
-
|
| 309 |
-
```
|
| 310 |
-
/dev/kvm absent -> no hardware virtualization
|
| 311 |
-
qemu-system-x86_64 -accel help -> tcg, kvm
|
| 312 |
-
qemu-system-x86_64 -accel kvm -> Could not access KVM kernel module: No such file or directory
|
| 313 |
-
qemu-system-x86_64 -accel tcg -> boots (reached "No bootable device.")
|
| 314 |
-
```
|
| 315 |
-
|
| 316 |
-
The rest of the Windows 11 ingredient list was then checked rather than assumed - UEFI
|
| 317 |
-
firmware and a TPM 2.0, both under pure TCG:
|
| 318 |
-
|
| 319 |
-
```
|
| 320 |
-
apt-get install -y ovmf swtpm -> /usr/share/OVMF/OVMF_CODE_4M.fd + swtpm 0.7.1
|
| 321 |
-
swtpm socket --tpm2 --tpmstate dir=/tmp/tpmstate \
|
| 322 |
-
--ctrl type=unixio,path=/tmp/swtpm-sock &
|
| 323 |
-
qemu-system-x86_64 -accel tcg -machine q35 -m 4G -smp 4 \
|
| 324 |
-
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
|
| 325 |
-
-drive if=pflash,format=raw,file=/tmp/OVMF_VARS.fd \
|
| 326 |
-
-chardev socket,id=chrtpm,path=/tmp/swtpm-sock \
|
| 327 |
-
-tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0 -nographic
|
| 328 |
-
|
| 329 |
-
-> UEFI Interactive Shell v2.2
|
| 330 |
-
EDK II
|
| 331 |
-
UEFI v2.70 (Debian distribution of EDK II, 0x00010000)
|
| 332 |
-
```
|
| 333 |
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
which the harness's OCR would run on an emulated CPU inside the guest. Use it only when
|
| 338 |
-
no CI runner and no cloud box is available:
|
| 339 |
|
| 340 |
```bash
|
|
|
|
| 341 |
qemu-img create -f qcow2 win.qcow2 64G
|
| 342 |
-
qemu-system-x86_64 -accel tcg -m 8G -smp
|
| 343 |
-
-
|
| 344 |
-
-drive if=
|
| 345 |
-
-
|
| 346 |
-
-
|
| 347 |
-
-
|
| 348 |
-
-
|
| 349 |
-
-device
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
```
|
| 351 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
|
| 353 |
-
**3. A cloud Windows box.** Any Windows VM reachable over RDP gives a native desktop, a
|
| 354 |
-
real `SendInput` path and real DPI scaling. Untested here; it is the closest thing to
|
| 355 |
-
"a user's actual laptop".
|
| 356 |
|
| 357 |
## How it works
|
| 358 |
|
|
@@ -399,9 +437,11 @@ def readme():
|
|
| 399 |
## What works, and what does not
|
| 400 |
|
| 401 |
**Works:** real capture, a real OCR index with coordinates that land on the right pixel,
|
| 402 |
-
a real click
|
| 403 |
-
|
| 404 |
-
|
|
|
|
|
|
|
| 405 |
|
| 406 |
**Does not work yet:** reliability on a *live* screen. The 256M VLM echoed the prompt
|
| 407 |
there; the 0.5B text planner picks wrong actions; the accessibility-tree detector is not
|
|
@@ -413,9 +453,11 @@ def readme():
|
|
| 413 |
- [x] Capture, OCR index, closed vocabulary, dry-run default, policy gates, doctor
|
| 414 |
- [x] A real click on a real window, with a verified outcome
|
| 415 |
- [x] Blind-grid-click guard, echo detection, no-progress guard
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
- [ ] Make the VLM reliable on a live screen (it echoes the prompt instead of choosing)
|
| 417 |
-
- [ ] Windows run of the live test - workflow committed; needs a GitHub mirror with Actions on
|
| 418 |
-
- [x] Windows VM ingredients verified locally (UEFI + TPM 2.0 boot under TCG; no KVM here)
|
| 419 |
- [ ] Accessibility-tree detector (AT-SPI on Linux, UI Automation on Windows)
|
| 420 |
- [ ] Score episodes: replay a trace and count how often the choice was right
|
| 421 |
- [ ] marimo console: live screen preview, action log, pause / resume / step
|
|
|
|
| 44 |
No cloud API, no API keys, no telemetry — and a core that imports nothing outside the
|
| 45 |
Python standard library.
|
| 46 |
|
| 47 |
+
> **Status 0.3.0: it drives a real desktop — on Linux and on Windows.** Screen capture,
|
| 48 |
+
> an OCR element index with real coordinates, a real mouse click, and a *verified*
|
| 49 |
+
> outcome. The suite passes on a live Linux desktop and inside a real Windows Server
|
| 50 |
+
> 2022 guest; what is not solved is model reliability — see
|
| 51 |
> [measured results](#measured-results).
|
| 52 |
|
| 53 |
## The idea, in one loop
|
|
|
|
| 210 |
**125 tests pass**, including real OCR over a real image and (opt-in) live-desktop tests
|
| 211 |
that click a real button.
|
| 212 |
|
| 213 |
+
## Verified on real Windows
|
| 214 |
+
|
| 215 |
+
The Windows claims in this card are not taken on faith. A full Windows guest was built
|
| 216 |
+
and the harness installed inside it, on a headless Linux host with **no KVM** — the same
|
| 217 |
+
kind of sandbox the Linux runs above were produced in.
|
| 218 |
+
|
| 219 |
+
**What was built.**
|
| 220 |
+
|
| 221 |
+
```
|
| 222 |
+
host headless Linux container (no /dev/kvm; software emulation only)
|
| 223 |
+
hypervisor QEMU 10.0.13 -accel tcg,thread=multi -m 8G -smp 8 -cpu Skylake-Client
|
| 224 |
+
firmware SeaBIOS (the OVMF/UEFI build would not auto-boot the installation media)
|
| 225 |
+
guest Windows Server 2022 Standard Evaluation (Desktop Experience), x86_64
|
| 226 |
+
install unattended from the public evaluation ISO, install.wim index 2,
|
| 227 |
+
answer file on removable media, autologon as Administrator
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
The image-apply phase moved roughly 9 GB to disk in about thirteen minutes, and the
|
| 231 |
+
desktop was up a few minutes after that.
|
| 232 |
+
|
| 233 |
+
**What was installed inside it.**
|
| 234 |
+
|
| 235 |
+
```
|
| 236 |
+
Python 3.12.8 (embeddable distribution, unpacked to C:\tiny\py)
|
| 237 |
+
pytest, pillow 13.0.0, mss 10.2.0
|
| 238 |
+
numpy 2.5.4, onnxruntime 1.31.1, opencv-python 5.0.0.93, rapidocr-onnxruntime 1.4.4
|
| 239 |
+
the harness itself, installed into site-packages
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
**Results.**
|
| 243 |
+
|
| 244 |
+
| Configuration | Result | Wall clock |
|
| 245 |
+
|---|---|---|
|
| 246 |
+
| Harness suite, no OCR engine | **118 passed, 3 failed, 4 skipped** | 7.8 s |
|
| 247 |
+
| Harness suite, OCR engine installed, `test_perception.py` excluded | **109 passed, 3 skipped, exit 0** | 21 min |
|
| 248 |
+
| `test_perception.py` (OCR inference) | runs; roughly ten minutes per OCR pass | — |
|
| 249 |
+
|
| 250 |
+
None of the three failures in the first row is a harness defect: two are
|
| 251 |
+
`ModuleNotFoundError: numpy` and the third is the guard bug documented below. With the
|
| 252 |
+
OCR engine installed, the same suite exits **0**.
|
| 253 |
+
|
| 254 |
+
**The emulated-OCR caveat.** onnxruntime requires AVX2, and QEMU's software emulation of
|
| 255 |
+
AVX2 is roughly two orders of magnitude slower than native. A suite that finishes in 17
|
| 256 |
+
seconds on Linux takes over half an hour on this guest, and the time is spent entirely
|
| 257 |
+
inside `onnxruntime_inference_collection.py`. That is a property of emulation, not of the
|
| 258 |
+
harness: on real hardware — including a GitHub `windows-latest` runner — it does not
|
| 259 |
+
apply.
|
| 260 |
+
|
| 261 |
+
### What running on Windows caught
|
| 262 |
+
|
| 263 |
+
Four things surfaced by actually running there. Three are genuine deployment requirements
|
| 264 |
+
that no Linux-only test run can reveal:
|
| 265 |
+
|
| 266 |
+
| Finding | Symptom | Fix |
|
| 267 |
+
|---|---|---|
|
| 268 |
+
| **onnxruntime needs AVX2** | `Windows fatal exception: code 0xc000001d` (`STATUS_ILLEGAL_INSTRUCTION`) the moment `onnxruntime` is imported, because QEMU's default `qemu64` CPU advertises no AVX | boot the guest with `-cpu Skylake-Client` (any AVX2 model will do) |
|
| 269 |
+
| **onnxruntime needs the MSVC runtime** | `ImportError: DLL load failed while importing onnxruntime_pybind11_state` on a clean Server 2022 | install `vc_redist.x64.exe` in the guest |
|
| 270 |
+
| **`pyautogui` ships as an sdist only** | `pip install pytest pillow mss pyautogui` fails as a whole, so `pytest` never arrives and the suite reports `No module named pytest` | install `pyautogui` in a separate step from the wheel-only packages |
|
| 271 |
+
| **A guard that never fires** | `test_screenshot_replay_needs_no_display` fails instead of skipping when no OCR engine is present: it guards on `detector_available()[0]`, which is `True` for the grid fallback | guard on the OCR detector specifically |
|
| 272 |
+
|
| 273 |
+
The last entry is a defect in this repository rather than in the environment, and it is
|
| 274 |
+
exactly the kind of thing that only surfaces on a machine without an OCR engine.
|
| 275 |
+
|
| 276 |
## The test suite, in full
|
| 277 |
|
| 278 |
`pytest tests` collects **125 tests**. 123 run headless — no display, no network, no
|
|
|
|
| 347 |
|
| 348 |
### Testing on Windows - three ways, and which to use
|
| 349 |
|
| 350 |
+
The verified Windows run above used method 2. All three remain valid:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
| Method | Real Windows desktop? | Cost | Status |
|
| 353 |
|---|---|---|---|
|
| 354 |
+
| **GitHub Actions `windows-latest`** | yes, interactive session | free for public repos, minutes | workflow committed at `.github/workflows/live-desktop.yml` |
|
| 355 |
+
| **QEMU/TCG VM on a Linux host** | yes, fully emulated | ~25 min to install, hours to run OCR | **used for the results above** |
|
| 356 |
+
| **A cloud Windows box over RDP** | yes, native | paid by the hour | untested here |
|
| 357 |
|
| 358 |
+
Nothing in the harness is Linux-specific: `pyautogui` uses `SendInput` on Windows and
|
| 359 |
+
`GrabScreen` already falls back to Pillow's `ImageGrab` there. Two things can only fail on
|
| 360 |
+
a real Windows desktop, which is why these runs exist: **DPI scaling** (that is what
|
| 361 |
+
`--scale` is for) and the native input backend. At 125% or 150% display scaling, expect the
|
| 362 |
+
coordinate assertion in `test_live_desktop.py` to be the first thing to fail - that is the
|
| 363 |
+
signal, not a nuisance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
|
| 365 |
+
**Reproducing the local VM.** With no KVM, a Windows guest is still perfectly possible -
|
| 366 |
+
it is simply slow. The working recipe, including the two ingredients that onnxruntime
|
| 367 |
+
demands:
|
|
|
|
|
|
|
| 368 |
|
| 369 |
```bash
|
| 370 |
+
# 1. install media, plus an answer file on removable media
|
| 371 |
qemu-img create -f qcow2 win.qcow2 64G
|
| 372 |
+
qemu-system-x86_64 -accel tcg,thread=multi -m 8G -smp 8 -machine pc \
|
| 373 |
+
-cpu Skylake-Client \
|
| 374 |
+
-drive file=win.qcow2,if=none,id=hd0,format=qcow2,cache=writeback \
|
| 375 |
+
-device ide-hd,drive=hd0,bus=ide.0 \
|
| 376 |
+
-drive file=win2022.iso,if=none,id=cd0,media=cdrom,readonly=on \
|
| 377 |
+
-device ide-cd,drive=cd0,bus=ide.1 \
|
| 378 |
+
-drive if=none,id=usb,format=raw,file=unattend.img \
|
| 379 |
+
-device usb-ehci,id=ehci -device usb-storage,bus=ehci.0,drive=usb \
|
| 380 |
+
-netdev user,id=n0 -device e1000,netdev=n0 \
|
| 381 |
+
-vga std -display none -vnc :1 -boot order=d,menu=off
|
| 382 |
+
|
| 383 |
+
# 2. inside the guest, after autologon
|
| 384 |
+
# vc_redist.x64.exe is required before onnxruntime will load
|
| 385 |
+
curl.exe -s -o C: iny c.exe <vc_redist-url> && C: iny c.exe /install /quiet /norestart
|
| 386 |
```
|
| 387 |
|
| 388 |
+
Serve files to the guest over QEMU's user-mode networking (the host is `10.0.2.2`), which
|
| 389 |
+
avoids typing long commands into the emulated console. Two practical notes from doing
|
| 390 |
+
this: an answer file with non-canonical XML namespaces is **silently ignored** by Setup
|
| 391 |
+
(you land on the language page), and keyboard input over VNC **drops the shift modifier**
|
| 392 |
+
(`_` becomes `-`, `:` becomes `;`) - the QEMU monitor's `sendkey` handles shifts correctly.
|
| 393 |
|
|
|
|
|
|
|
|
|
|
| 394 |
|
| 395 |
## How it works
|
| 396 |
|
|
|
|
| 437 |
## What works, and what does not
|
| 438 |
|
| 439 |
**Works:** real capture, a real OCR index with coordinates that land on the right pixel,
|
| 440 |
+
a real click an app reacts to, verified success, the closed vocabulary, the policy gates,
|
| 441 |
+
the no-progress guard, echo detection, the doctor, the CLI, 125 tests, a 256M VLM that
|
| 442 |
+
reads a button label from pixels on a clean screenshot — and the same suite passing inside
|
| 443 |
+
a real Windows Server 2022 guest with no KVM (109 passed, exit 0, with the OCR-inference
|
| 444 |
+
tests excluded as impractically slow under emulation).
|
| 445 |
|
| 446 |
**Does not work yet:** reliability on a *live* screen. The 256M VLM echoed the prompt
|
| 447 |
there; the 0.5B text planner picks wrong actions; the accessibility-tree detector is not
|
|
|
|
| 453 |
- [x] Capture, OCR index, closed vocabulary, dry-run default, policy gates, doctor
|
| 454 |
- [x] A real click on a real window, with a verified outcome
|
| 455 |
- [x] Blind-grid-click guard, echo detection, no-progress guard
|
| 456 |
+
- [x] The harness suite running on real Windows (Server 2022 guest, no KVM — see above)
|
| 457 |
+
- [ ] Windows run of the *live desktop* test: the workflow is committed, but it needs a
|
| 458 |
+
GitHub mirror with Actions enabled — Hugging Face does not run GitHub Actions
|
| 459 |
+
- [ ] Make the OCR guard in `test_cli.py` skip, rather than fail, when no engine is installed
|
| 460 |
- [ ] Make the VLM reliable on a live screen (it echoes the prompt instead of choosing)
|
|
|
|
|
|
|
| 461 |
- [ ] Accessibility-tree detector (AT-SPI on Linux, UI Automation on Windows)
|
| 462 |
- [ ] Score episodes: replay a trace and count how often the choice was right
|
| 463 |
- [ ] marimo console: live screen preview, action log, pause / resume / step
|