hkblockchaintechnology commited on
Commit
2acb889
·
verified ·
1 Parent(s): 8150d90

Publish README.md from marimo

Browse files
Files changed (1) hide show
  1. README.md +132 -15
README.md CHANGED
@@ -24,7 +24,11 @@ Python standard library.
24
 
25
  > **Status 0.3.0: it drives a real desktop — on Linux and on Windows.** Screen capture,
26
  > an OCR element index with real coordinates, a real mouse click, and a *verified*
27
- > outcome. **All 68 capabilities have a real screenshot** in the [capability gallery](#capability-gallery), and a run can be saved as a portable *trace*, replayed offline and **scored**, so
 
 
 
 
28
  > "which brain is right, and how often?" is a number in a table.
29
  > The suite passes on a live Linux desktop and inside a real Windows Server
30
  > 2022 guest; what is not solved is model reliability — see
@@ -86,7 +90,7 @@ X server. First the suite, headless:
86
  ```bash
87
  python -m pip install -e ".[desktop,ocr,dev]"
88
  pytest tests -q
89
- # 162 passed, 2 skipped in 18.7s
90
  ```
91
 
92
  The two skips are the live-desktop tests. Give them a display and they run for real:
@@ -187,6 +191,46 @@ harness *allowed* that decision to run is a separate axis: a click the policy re
187
  still the choice it was, and it is flagged `(harness refused it)` rather than passed off as
188
  a clean success. Keeping those two apart is the point.
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  ### Which brain drove which run
191
 
192
  The harness and the brain are separable, and it matters which one a result came from.
@@ -218,7 +262,7 @@ open problem: it is reliable on a clean screenshot and unreliable on a busy live
218
  `warning: no text was found on screen, so the index is 96 numbered grid cells and the
219
  agent is choosing blind` — instead of silently guessing.
220
 
221
- **164 tests pass**, including real OCR over a real image and (opt-in) live-desktop tests
222
  that click a real button.
223
 
224
  ## Verified on real Windows
@@ -260,7 +304,7 @@ the harness itself, installed into site-packages
260
 
261
  None of the three failures in the first row is a harness defect: two are
262
  `ModuleNotFoundError: numpy` and the third is the guard bug documented below. With the
263
- OCR engine installed, the same suite exits **0**. (Those Windows counts predate `trace.py`; the harness suite has since grown to 164 tests.)
264
 
265
  **The emulated-OCR caveat.** onnxruntime requires AVX2, and QEMU's software emulation of
266
  AVX2 is roughly two orders of magnitude slower than native. A suite that finishes in 17
@@ -286,7 +330,7 @@ exactly the kind of thing that only surfaces on a machine without an OCR engine.
286
 
287
  ## The test suite, in full
288
 
289
- `pytest tests` collects **164 tests**. 162 run headless — no display, no network, no
290
  weights. The last 2 are the opt-in live-desktop tests, skipped unless
291
  `TINY_COMPUTER_AGENT_LIVE_TEST=1`.
292
 
@@ -306,11 +350,15 @@ weights. The last 2 are the opt-in live-desktop tests, skipped unless
306
  | `tests/test_trace.py` | 28 | traces: a run recorded with no pixels, replayed against another brain, and scored against ground truth |
307
  | `tests/test_models.py` | 7 | the model backends: lazy loading, device choice, honest refusals |
308
  | `tests/test_api_surface.py` | 4 | the public API is a promise: everything advertised is importable, and nothing public is left out |
 
 
 
 
309
  | `tests/test_live_desktop.py` | 2 | **needs a display, opt-in**: OCR finds a real button at the pixels it was drawn at, and a real click makes the app react |
310
 
311
  ```bash
312
- pytest tests -q # 162 passed, 2 skipped
313
- TINY_COMPUTER_AGENT_LIVE_TEST=1 pytest tests -q # 164 passed, on a desktop
314
  ```
315
 
316
  ## Install
@@ -319,7 +367,15 @@ TINY_COMPUTER_AGENT_LIVE_TEST=1 pytest tests -q # 164 passed, on a desktop
319
  pip install -e ".[ocr]" # labels from pixels (RapidOCR: no system binary)
320
  pip install -e ".[desktop,ocr]" # + real capture and real input
321
  pip install -e ".[vision,ocr]" # + the tiny model brains
322
- pytest # 164 tests, no display or network needed
 
 
 
 
 
 
 
 
323
  ```
324
 
325
  ## Run it on your machine
@@ -421,7 +477,61 @@ this: an answer file with non-canonical XML namespaces is **silently ignored** b
421
  | `desktop.py` | Preflight checks, `doctor`, and `build_agent` in one call |
422
  | `demo.py` | The synthetic desktop used by tests and `--image example` |
423
  | `trace.py` | Portable traces: record a run with no pixels, replay it offline, score the choice against ground truth |
 
 
424
  | `showcase.py` | The capability gallery: one real screenshot per capability, checked against the public API |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
 
426
  ## Capability gallery
427
 
@@ -1068,15 +1178,22 @@ when no verifier is given.
1068
  - [x] A real click on a real window, with a verified outcome
1069
  - [x] Blind-grid-click guard, echo detection, no-progress guard
1070
  - [x] The harness suite running on real Windows (Server 2022 guest, no KVM — see above)
1071
- - [ ] Windows run of the *live desktop* test: the workflow is committed, but it needs a
1072
- GitHub mirror with Actions enabled — Hugging Face does not run GitHub Actions
1073
- - [ ] Make the OCR guard in `test_cli.py` skip, rather than fail, when no engine is installed
1074
- - [ ] Make the VLM reliable on a live screen (it echoes the prompt instead of choosing)
1075
- - [ ] Accessibility-tree detector (AT-SPI on Linux, UI Automation on Windows)
1076
  - [x] Score episodes: replay a trace and count how often the choice was right - see [scoring a brain, offline](#scoring-a-brain-offline)
1077
  - [x] A capability gallery: one real screenshot per capability, generated by `showcase.py`
1078
- - [ ] A **Windows** run of the gallery: the workflow builds it on `windows-latest`, but it needs a GitHub mirror with Actions enabled - Hugging Face does not run GitHub Actions
1079
- - [ ] marimo console: live screen preview, action log, pause / resume / step
 
 
 
 
 
 
 
 
 
 
 
 
1080
 
1081
  ## License
1082
 
 
24
 
25
  > **Status 0.3.0: it drives a real desktop — on Linux and on Windows.** Screen capture,
26
  > an OCR element index with real coordinates, a real mouse click, and a *verified*
27
+ > outcome. **All 73 capabilities have a real screenshot** in the [capability gallery](#capability-gallery).
28
+ > The loop can be driven from a [marimo console](#the-marimo-console) with a live preview, an
29
+ > action log and pause / resume / step; elements can come from the
30
+ > [accessibility tree](#what-the-machine-already-knows-the-accessibility-tree) instead of OCR;
31
+ > and a run can be saved as a portable *trace*, replayed offline and **scored**, so
32
  > "which brain is right, and how often?" is a number in a table.
33
  > The suite passes on a live Linux desktop and inside a real Windows Server
34
  > 2022 guest; what is not solved is model reliability — see
 
90
  ```bash
91
  python -m pip install -e ".[desktop,ocr,dev]"
92
  pytest tests -q
93
+ # 217 passed, 2 skipped in 18.7s
94
  ```
95
 
96
  The two skips are the live-desktop tests. Give them a display and they run for real:
 
191
  still the choice it was, and it is flagged `(harness refused it)` rather than passed off as
192
  a clean success. Keeping those two apart is the point.
193
 
194
+ ### Asking a tiny model in four ways (measured)
195
+
196
+ The card used to say the 256M VLM "echoes the prompt instead of choosing". That is
197
+ reproducible - and so is the fix. A live GTK window, a menu of four exact widgets
198
+ (Files, Terminal, Save, Cancel), and the goal `click 'Save' to save the document`:
199
+
200
+ ```
201
+ mode echoed seconds outcome
202
+ json no 8.9 BrainError: the reply mentions 4 menu entries, so it looks like an echo
203
+ choice no 1.9 BrainError: the constrained reply was not a menu number: '1000'
204
+ scan no 1.4 click label='Files' (the model said yes to the first candidate)
205
+ rank no 9.4 click label='Files' (highest yes/no log-odds margin, +0.94)
206
+ ```
207
+
208
+ Four ways to ask, all of them the same weights:
209
+
210
+ | mode | how the model is asked | answer space |
211
+ |---|---|---|
212
+ | `json` | reply with JSON, parsed leniently | the whole vocabulary |
213
+ | `choice` | one masked generation for a menu number | a handful of digits |
214
+ | `scan` | one masked yes/no question per item, first yes wins | two tokens |
215
+ | `rank` | the same questions, ranked by the yes/no log-odds margin | two tokens |
216
+
217
+ What the numbers actually say:
218
+
219
+ - **The echo is fixed, structurally.** In `json` mode the model hands the prompt
220
+ back (`"The goal is to click 'Save' to save the document..."`) and the harness
221
+ refuses it. In the three masked modes the reply *cannot* be the prompt, because
222
+ every one of its tokens is masked out of the logits. The failure stopped being
223
+ *detected* and started being *not representable*.
224
+ - **It is about six times faster** - 8.9 s of free text becomes 1.4 s for a
225
+ one-token answer - because the model is no longer asked to write.
226
+ - **Accuracy is not fixed, and the numbers say why.** The model answers now, and
227
+ answers *wrongly*: it accepts `Files` for a goal that names `Save`, even when the
228
+ answer space is two tokens and each candidate is judged on its own. A 256M model
229
+ cannot do this task. That is a finding about the weights, not about the harness -
230
+ which is why the rule-based planner is still the default brain.
231
+
232
+ Reproduce it with `python measure_vlm.py --screen live --detector best --open-gtk`.
233
+
234
  ### Which brain drove which run
235
 
236
  The harness and the brain are separable, and it matters which one a result came from.
 
262
  `warning: no text was found on screen, so the index is 96 numbered grid cells and the
263
  agent is choosing blind` — instead of silently guessing.
264
 
265
+ **219 tests pass**, including real OCR over a real image and (opt-in) live-desktop tests
266
  that click a real button.
267
 
268
  ## Verified on real Windows
 
304
 
305
  None of the three failures in the first row is a harness defect: two are
306
  `ModuleNotFoundError: numpy` and the third is the guard bug documented below. With the
307
+ OCR engine installed, the same suite exits **0**. (Those Windows counts predate `trace.py`; the harness suite has since grown to 219 tests.)
308
 
309
  **The emulated-OCR caveat.** onnxruntime requires AVX2, and QEMU's software emulation of
310
  AVX2 is roughly two orders of magnitude slower than native. A suite that finishes in 17
 
330
 
331
  ## The test suite, in full
332
 
333
+ `pytest tests` collects **219 tests**. 217 run headless — no display, no network, no
334
  weights. The last 2 are the opt-in live-desktop tests, skipped unless
335
  `TINY_COMPUTER_AGENT_LIVE_TEST=1`.
336
 
 
350
  | `tests/test_trace.py` | 28 | traces: a run recorded with no pixels, replayed against another brain, and scored against ground truth |
351
  | `tests/test_models.py` | 7 | the model backends: lazy loading, device choice, honest refusals |
352
  | `tests/test_api_surface.py` | 4 | the public API is a promise: everything advertised is importable, and nothing public is left out |
353
+ | `tests/test_decoding.py` | 24 | the mask, the answer space, the retry ladder, the vote - and that an echo is refused |
354
+ | `tests/test_accessibility.py` | 16 | role filtering, the coordinate contract, the fallback chain, and real AT-SPI |
355
+ | `tests/test_stepwise.py` | 11 | begin / step / pause / resume, and that it equals the batch loop |
356
+ | `tests/test_ocr_guard.py` | 4 | the OCR guard really skips with no engine - the guard that never fired |
357
  | `tests/test_live_desktop.py` | 2 | **needs a display, opt-in**: OCR finds a real button at the pixels it was drawn at, and a real click makes the app react |
358
 
359
  ```bash
360
+ pytest tests -q # 217 passed, 2 skipped
361
+ TINY_COMPUTER_AGENT_LIVE_TEST=1 pytest tests -q # 219 passed, on a desktop
362
  ```
363
 
364
  ## Install
 
367
  pip install -e ".[ocr]" # labels from pixels (RapidOCR: no system binary)
368
  pip install -e ".[desktop,ocr]" # + real capture and real input
369
  pip install -e ".[vision,ocr]" # + the tiny model brains
370
+ pytest # 219 tests, no display or network needed
371
+ ```
372
+
373
+ The accessibility detector needs system packages rather than an extra:
374
+
375
+ ```bash
376
+ apt install gir1.2-atspi-2.0 at-spi2-core gir1.2-gtk-3.0
377
+ pip install PyGObject
378
+ python -m tiny_computer_agent --doctor --detector best # is the tree reachable?
379
  ```
380
 
381
  ## Run it on your machine
 
477
  | `desktop.py` | Preflight checks, `doctor`, and `build_agent` in one call |
478
  | `demo.py` | The synthetic desktop used by tests and `--image example` |
479
  | `trace.py` | Portable traces: record a run with no pixels, replay it offline, score the choice against ground truth |
480
+ | `decoding.py` | Constrained decoding: mask the logits to a menu number or a yes/no, so an echo is impossible |
481
+ | `accessibility.py` | The accessibility tree: `AtspiReader` and `AtspiDetector`, roles and real extents |
482
  | `showcase.py` | The capability gallery: one real screenshot per capability, checked against the public API |
483
+ | `measure_vlm.py` | Measures the four ways to ask a model, on a real screen, and prints the table |
484
+
485
+ ## The marimo console
486
+
487
+ The loop is a library, but it is also a *session* you can drive one turn at a time -
488
+ which is what a console needs: a live screen preview, an action log, and
489
+ step / pause / resume.
490
+
491
+ <img src="gallery/console.png" width="900">
492
+
493
+ `AgentLoop.run()` is the batch form; `begin()`, `step_once()`, `pause()`, `resume()`
494
+ and `finished` are the stepwise one. `tests/test_stepwise.py` asserts the two cannot
495
+ drift apart - the same scenario has to produce the same episode either way. The
496
+ console cell in this notebook wires that to marimo widgets:
497
+
498
+ - **live preview** - the current screen with the element index drawn over it, so you
499
+ can see what the agent sees and what it thinks is clickable
500
+ - **action log** - one row per step: the action, and the outcome or the refusal
501
+ - **step / run / reset** - `mo.ui.run_button`, so an auto-refresh can never fire a
502
+ step by accident
503
+ - **pause** - respected *between* steps: the episode stays open, and a single step
504
+ can still be taken through it
505
+ - **panic** - creates the same kill-switch file the CLI checks, so the next step
506
+ refuses to run
507
+
508
+ Switch the screen to "this machine, live" and it is the real desktop, indexed through
509
+ OCR or through the accessibility tree.
510
+
511
+ ## What the machine already knows: the accessibility tree
512
+
513
+ OCR reads pixels and *guesses* which marks are widgets. An operating system's
514
+ accessibility tree simply knows: every application that publishes one advertises each
515
+ button with a role, a name, and its real screen extents.
516
+
517
+ <img src="gallery/atspi-index.png" width="860">
518
+
519
+ `AtspiDetector` reads that tree through PyGObject's `Atspi` typelib and keeps only the
520
+ roles worth clicking - a frame and a panel have names too, and offering them to a
521
+ model is how you get an agent that clicks the window furniture. The coordinates come
522
+ back as absolute screen pixels, so `absolute_coordinates` tells `IndexedScreen` not to
523
+ re-map them through a downscale.
524
+
525
+ <img src="gallery/desk-atspi.png" width="860">
526
+
527
+ That click was found **without reading a single pixel**: the window's own status label
528
+ reads `last action: Save` afterwards, and the app wrote `Save` to its marker file.
529
+ `--detector best` tries the tree first, then OCR, then the numbered grid.
530
+
531
+ **Honest limits.** AT-SPI is Linux-only here; **UI Automation is not implemented**, and
532
+ `accessibility_available("uia")` says exactly that rather than pretending - so an agent
533
+ on Windows keeps using OCR. Tk barely publishes a tree and a canvas publishes none,
534
+ which is why this is a detector and not a replacement.
535
 
536
  ## Capability gallery
537
 
 
1178
  - [x] A real click on a real window, with a verified outcome
1179
  - [x] Blind-grid-click guard, echo detection, no-progress guard
1180
  - [x] The harness suite running on real Windows (Server 2022 guest, no KVM — see above)
 
 
 
 
 
1181
  - [x] Score episodes: replay a trace and count how often the choice was right - see [scoring a brain, offline](#scoring-a-brain-offline)
1182
  - [x] A capability gallery: one real screenshot per capability, generated by `showcase.py`
1183
+ - [x] The OCR guard skips instead of failing: one tested guard, `conftest.require_ocr`
1184
+ - [x] Stop the 256M VLM echoing the prompt - masked decoding, measured in
1185
+ [asking a tiny model in four ways](#asking-a-tiny-model-in-four-ways-measured)
1186
+ - [x] The marimo console: live screen preview, action log, pause / resume / step
1187
+ - [x] An accessibility-tree detector on Linux (AT-SPI), driving a real click with no OCR
1188
+ - [ ] **Make the 256M VLM choose correctly.** Masking fixed the echo and the latency; the model
1189
+ still picks the wrong item from a four-item menu of exact widgets. That is now a question
1190
+ about weights, or about a better default (the rule-based planner), not about prompt shape.
1191
+ - [ ] **UI Automation** (Windows) for the accessibility detector. The interface is here and
1192
+ `accessibility_available("uia")` reports it as not implemented rather than pretending.
1193
+ - [ ] Windows run of the *live desktop* test: the workflow is committed, but it needs a
1194
+ GitHub mirror with Actions enabled — Hugging Face does not run GitHub Actions
1195
+ - [ ] A **Windows** run of the gallery: the workflow builds it on `windows-latest` (job
1196
+ `gallery-windows`), with the same GitHub-mirror caveat
1197
 
1198
  ## License
1199