hkblockchaintechnology commited on
Commit
a77c2ab
Β·
verified Β·
1 Parent(s): f321127

Publish README.md from marimo

Browse files
Files changed (1) hide show
  1. README.md +92 -64
README.md CHANGED
@@ -21,10 +21,10 @@ tags:
21
  No cloud API, no API keys, no telemetry β€” and a core that imports nothing outside the
22
  Python standard library.
23
 
24
- > **Status 0.2.0: it runs, on pixels.** The screen is captured for real, OCR turns it
25
- > into a short labelled menu, a **256M** vision model picks from that menu, and the
26
- > harness resolves the choice to a real pixel and (behind two locks) clicks it.
27
- > What is *not* solved is model accuracy β€” see [measured results](#measured-results).
28
 
29
  ## The idea, in one loop
30
 
@@ -45,59 +45,65 @@ Python standard library.
45
  2. **Think** β€” the model is shown a *short numbered menu* built from that index and
46
  answers with one number (plus a verb). It never emits coordinates.
47
  3. **Act** β€” the harness maps the chosen label back to pixels and executes it.
48
- 4. **Verify** β€” re-read the screen, and stop if the same action repeats against an
49
- unchanged screen, because that is a stuck agent, not a persistent one.
50
 
51
  ## Measured results
52
 
53
- Everything below was run from this repo, **CPU only, dry-run**, against the synthetic
54
- screenshot (`--image example`), because the build machine has no desktop. Nothing here
55
- is aspirational.
56
 
57
- **A rule-based planner, end to end:** OCR read five labels off the screenshot, matched
58
- the goal, and resolved it to a pixel.
 
 
 
59
 
60
  ```
61
- $ python -m tiny_computer_agent --image example --brain planner
 
62
  index: auto - OCR when it finds text, grid otherwise (rapidocr-onnxruntime ready (CPU, offline))
63
- elements detected: 5
64
 
65
- step 0 | click label='Save' reason="matched 'Save' in the element index" | ok: (dry-run) would click -> pixel (295, 284)
66
- step 1 | done reason="already clicked 'Save'; goal assumed met" | ok: goal reported met
 
67
  ```
68
 
69
- **A 256M vision model β€” SmolVLM-256M-Instruct, CPU β€” read the button off the pixels:**
 
 
 
 
70
 
71
  ```
72
- $ python -m tiny_computer_agent --image example --brain vlm
73
  elements detected: 5
74
-
75
- step 0 | click label='Save' reason="the reply names 'Save'" | ok: (dry-run) would click -> pixel (295, 284)
76
- step 1 | click label='Save' ...
77
- step 2 | ... ERROR no progress: repeating click label='Save' against an unchanged screen
78
  ```
79
 
80
- It named the correct target from pixels alone. The episode is recorded as a *failure*
81
- only because the replayed screenshot never changes, so the no-progress guard stops a
82
- loop that a real desktop would have moved past.
83
-
84
- **A 0.5B text planner over the OCR index β€” Qwen2.5-0.5B-Instruct β€” is not reliable:**
85
 
86
  ```
87
- $ python -m tiny_computer_agent --image example --brain text-planner
88
- step 0 | click label='Cancel' reason='menu index 5'
89
- step 1 | click label='Files' reason="recovered 'Files' from the model's reply: {"kind": "click", "index": 0, ...}"
90
- step 2 | click label='Save' reason='menu index 4'
91
- result: failure - step budget of 3 exhausted
92
  ```
93
 
94
- It clicked Cancel before Save, then wandered. The harness held up regardless: every
95
- action was validated against the closed vocabulary, a garbled JSON reply was recovered
96
- into a usable action, and the budget stopped it. **A tiny VLM beats a tiny text model
97
- here** β€” which is why the vision path is the recommended one.
 
 
 
 
 
 
 
 
 
98
 
99
- **116 tests pass**, including real OCR over a real image asserting the `Save` button's
100
- coordinates, and a screenshot replay that runs the whole pipeline with no display.
101
 
102
  ## Install
103
 
@@ -105,7 +111,7 @@ coordinates, and a screenshot replay that runs the whole pipeline with no displa
105
  pip install -e ".[ocr]" # labels from pixels (RapidOCR: no system binary)
106
  pip install -e ".[desktop,ocr]" # + real capture and real input
107
  pip install -e ".[vision,ocr]" # + the tiny model brains
108
- pytest # 116 tests, no display or network needed
109
  ```
110
 
111
  ## Run it on your machine
@@ -114,18 +120,37 @@ pytest # 116 tests, no display or network needed
114
  python -m tiny_computer_agent --doctor # what can this machine do?
115
  python -m tiny_computer_agent --image example # replay a screenshot, dry run
116
  python -m tiny_computer_agent --live # your real screen, dry run
117
- python -m tiny_computer_agent --live --real-input "click 'OK'" # needs the env var too
 
118
  ```
119
 
120
  `--doctor` is the first thing to run on a new machine: it checks the display session,
121
- capture backend, pyautogui, the OCR engine, the model runtime, and the kill switch, and
122
- prints the exact fix for anything missing. Real input needs **two** deliberate acts:
123
 
124
  ```bash
125
  export TINY_COMPUTER_AGENT_ALLOW_REAL_INPUT=1
126
- python -m tiny_computer_agent --live --real-input "click 'Save'"
127
  ```
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  ## How it works
130
 
131
  | Module | Job |
@@ -133,57 +158,60 @@ python -m tiny_computer_agent --live --real-input "click 'Save'"
133
  | `actions.py` | The closed vocabulary β€” `click` `type` `key` `scroll` `wait` `done`, validated before anything executes |
134
  | `observe.py` | `Element`/`Observation`, `FakeScreen`, `ImageScreen`, `GrabScreen` (mss β†’ Pillow fallback) |
135
  | `perception.py` | Detectors: `OcrDetector` (RapidOCR), `GridDetector`, `AutoDetector`, `IndexedScreen` |
136
- | `brain.py` | `DecisionMenu` (the numbered menu), `ModelBrain`, lenient `parse_decision`, `MockBrain`, `PlannerBrain` |
137
  | `models.py` | `VLMBrain` (SmolVLM-256M) and `TextPlannerBrain` (Qwen2.5-0.5B), lazily loaded |
138
  | `act.py` | `DryRunExecutor` (the default) and `PyAutoGuiExecutor` |
139
  | `safety.py` | `Policy`: deny lists, kill switch, step budget, the blind-grid-click guard |
140
- | `loop.py` | `AgentLoop`, `Episode`, `Step`, and the no-progress guard |
141
  | `desktop.py` | Preflight checks, `doctor`, and `build_agent` in one call |
142
  | `demo.py` | The synthetic desktop used by tests and `--image example` |
143
 
144
  ## Why a tiny model can do this at all
145
 
146
- The harness does the heavy lifting, so the model never has to be clever:
147
-
148
  | Job | Who does it |
149
  |---|---|
150
  | Finding *where* a button is | OCR / accessibility tree β€” **not** the model |
151
  | Choosing *what* to do | the model, but only by picking a number from a short menu |
152
- | Knowing whether it worked | the next frame, plus the no-progress guard |
153
  | Recovering from a bad step | the retry policy in `loop.py`, not the weights |
154
 
155
- The menu is the trick: a 256M model cannot emit `(295, 284)` reliably, but it can pick
156
- "4" out of five options β€” and the harness owns the coordinate lookup.
 
157
 
158
  ## Safety, because this thing has your mouse
159
 
160
  - Run it in a **VM, container, or throwaway desktop account**, not your real session.
161
  - **Dry run is the default**, so you can read a whole episode before anything clicks.
162
- - Deny lists for typing (`sudo`, `rm -rf`, "password"), for widgets (terminal, keychain,
163
- wallet, bank) and for key chords.
164
  - A **kill-switch file** checked before every action, plus a hard step budget.
165
- - **Blind grid clicks are refused with real input** unless you pass `--allow-grid`. When
166
- OCR finds nothing on screen, the fallback index is a numbered grid β€” coordinates with
167
- no meaning β€” and a model choosing one is guessing about your desktop.
 
 
168
  - **Local-first, for real:** pixels and keystrokes never leave the machine.
169
 
170
  ## What works, and what does not
171
 
172
- **Works:** real capture, a real OCR index with real coordinates, a 256M VLM choosing the
173
- right labelled target, the closed vocabulary, the policy gates, the no-progress guard,
174
- the doctor, the CLI, 116 tests.
 
175
 
176
- **Does not work yet:** accuracy. A 0.5B text planner picks wrong actions; a 256M VLM can
177
- answer "done" before doing anything, and the harness currently trusts that; the
178
- accessibility-tree detector (AT-SPI / UI Automation) is not implemented; and nothing here
179
- has been run against a live multi-window desktop, only against a synthetic screenshot.
180
 
181
  ## Roadmap
182
 
183
  - [x] Capture, OCR index, closed vocabulary, dry-run default, policy gates, doctor
184
- - [x] A 256M VLM that picks a labelled target from pixels
185
- - [ ] Make the harness distrust a `done` that arrives before any action has been taken
186
- - [ ] Accessibility-tree detector (AT-SPI on Linux, UI Automation on Windows) for exact widgets
 
187
  - [ ] Score episodes: replay a trace and count how often the choice was right
188
  - [ ] marimo console: live screen preview, action log, pause / resume / step
189
 
 
21
  No cloud API, no API keys, no telemetry β€” and a core that imports nothing outside the
22
  Python standard library.
23
 
24
+ > **Status 0.3.0: it drives a real desktop.** Screen capture, an OCR element index with
25
+ > real coordinates, a real mouse click, and a *verified* outcome β€” all through the
26
+ > harness, in one episode. What is not solved is model reliability: see
27
+ > [measured results](#measured-results).
28
 
29
  ## The idea, in one loop
30
 
 
45
  2. **Think** β€” the model is shown a *short numbered menu* built from that index and
46
  answers with one number (plus a verb). It never emits coordinates.
47
  3. **Act** β€” the harness maps the chosen label back to pixels and executes it.
48
+ 4. **Verify** β€” re-read the screen; stop if the same action repeats against an unchanged
49
+ screen, and only call the episode a success if a verifier can see it worked.
50
 
51
  ## Measured results
52
 
53
+ Everything below was run from this repo on CPU, dry-run unless stated. Nothing is
54
+ aspirational.
 
55
 
56
+ ### On a live desktop, with a real click
57
+
58
+ A real Tk window (a Save dialog with Save/Cancel buttons) on a **real X server**
59
+ (`Xvfb :99`), driven with real mouse input. The button sits at window `+120+120`,
60
+ `x=20, y=155`, `110x40`, so its centre is `(195, 295)`:
61
 
62
  ```
63
+ $ python -m tiny_computer_agent --live --real-input --verify-text Saved "click 'Save' to save the document"
64
+ mode: REAL INPUT (mouse and keyboard live) | screen: real desktop (monitor 1) | brain: planner
65
  index: auto - OCR when it finds text, grid otherwise (rapidocr-onnxruntime ready (CPU, offline))
 
66
 
67
+ step 0 | click label='Save' reason="matched 'Save' in the element index" | ok: clicked (195, 296)
68
+
69
+ result: success - verified after 1 action(s)
70
  ```
71
 
72
+ The click landed on the button, the app changed its own window to "Saved", and the
73
+ verifier confirmed it before the episode was called a success. **Pixels in, a real click
74
+ out, and evidence that it worked.**
75
+
76
+ ### On a replayed screenshot
77
 
78
  ```
79
+ $ python -m tiny_computer_agent --image example --brain planner
80
  elements detected: 5
81
+ step 0 | click label='Save' ... -> pixel (295, 284) # OCR found the button
 
 
 
82
  ```
83
 
84
+ **SmolVLM-256M-Instruct** (256M parameters, CPU) read the button off the pixels and
85
+ named the right target:
 
 
 
86
 
87
  ```
88
+ step 0 | click label='Save' reason="the reply names 'Save'" -> pixel (295, 284)
 
 
 
 
89
  ```
90
 
91
+ ### What did *not* work, and what caught it
92
+
93
+ - **The 256M VLM echoes the prompt on a busy live screen.** Asked to click Save on the
94
+ live Tk window it replied with the prompt text back. The harness's echo detection
95
+ refused it (`BrainError: the reply mentions 4 menu entries, so it looks like an echo of
96
+ the prompt`) and stopped β€” **no blind click**. On the cleaner synthetic screenshot the
97
+ same model chose correctly, so this is a prompt/robustness problem, not a wiring one.
98
+ - **Qwen2.5-0.5B text planner over the OCR index is not reliable**: it clicked Cancel
99
+ before Save, then recovered a garbled JSON reply, then found Save β€” budget exhausted.
100
+ - **The grid fallback announced itself.** After the dialog closed, OCR found no text, the
101
+ index degraded to 96 numbered cells, and the run printed
102
+ `warning: no text was found on screen, so the index is 96 numbered grid cells and the
103
+ agent is choosing blind` β€” instead of silently guessing.
104
 
105
+ **123 tests pass**, including real OCR over a real image and (opt-in) live-desktop tests
106
+ that click a real button.
107
 
108
  ## Install
109
 
 
111
  pip install -e ".[ocr]" # labels from pixels (RapidOCR: no system binary)
112
  pip install -e ".[desktop,ocr]" # + real capture and real input
113
  pip install -e ".[vision,ocr]" # + the tiny model brains
114
+ pytest # 123 tests, no display or network needed
115
  ```
116
 
117
  ## Run it on your machine
 
120
  python -m tiny_computer_agent --doctor # what can this machine do?
121
  python -m tiny_computer_agent --image example # replay a screenshot, dry run
122
  python -m tiny_computer_agent --live # your real screen, dry run
123
+ python -m tiny_computer_agent --live --real-input "click 'OK'"
124
+ python -m tiny_computer_agent --live --real-input --verify-text "Saved" "click 'Save'"
125
  ```
126
 
127
  `--doctor` is the first thing to run on a new machine: it checks the display session,
128
+ capture backend, pyautogui, the OCR engine, the model runtime and the kill switch, and
129
+ prints the fix for anything missing. Real input needs **two** deliberate acts:
130
 
131
  ```bash
132
  export TINY_COMPUTER_AGENT_ALLOW_REAL_INPUT=1
 
133
  ```
134
 
135
+ **Pass `--verify-text`.** Without it, a successful action that removes its own target
136
+ (clicking Save closes the dialog) is indistinguishable from a failure, and the episode is
137
+ recorded as one. With it, the harness only reports success when it can see the text it
138
+ was given.
139
+
140
+ ### No desktop? Use a virtual one
141
+
142
+ Xvfb is enough for everything except a real model's hallucinations:
143
+
144
+ ```bash
145
+ Xvfb :99 -screen 0 1280x800x24 &
146
+ export DISPLAY=:99
147
+ TINY_COMPUTER_AGENT_LIVE_TEST=1 python -m pytest tests/test_live_desktop.py
148
+ ```
149
+
150
+ That test starts its own Tk window, asserts OCR finds the button at the coordinates it
151
+ was actually drawn at, clicks it for real, and checks the app reacted. It is opt-in
152
+ because it moves your mouse β€” run it on a throwaway desktop.
153
+
154
  ## How it works
155
 
156
  | Module | Job |
 
158
  | `actions.py` | The closed vocabulary β€” `click` `type` `key` `scroll` `wait` `done`, validated before anything executes |
159
  | `observe.py` | `Element`/`Observation`, `FakeScreen`, `ImageScreen`, `GrabScreen` (mss β†’ Pillow fallback) |
160
  | `perception.py` | Detectors: `OcrDetector` (RapidOCR), `GridDetector`, `AutoDetector`, `IndexedScreen` |
161
+ | `brain.py` | `DecisionMenu` (the numbered menu), `ModelBrain`, lenient `parse_decision`, echo detection, `MockBrain`, `PlannerBrain` |
162
  | `models.py` | `VLMBrain` (SmolVLM-256M) and `TextPlannerBrain` (Qwen2.5-0.5B), lazily loaded |
163
  | `act.py` | `DryRunExecutor` (the default) and `PyAutoGuiExecutor` |
164
  | `safety.py` | `Policy`: deny lists, kill switch, step budget, the blind-grid-click guard |
165
+ | `loop.py` | `AgentLoop`, `Episode`, `Step`, the no-progress guard and the verifier |
166
  | `desktop.py` | Preflight checks, `doctor`, and `build_agent` in one call |
167
  | `demo.py` | The synthetic desktop used by tests and `--image example` |
168
 
169
  ## Why a tiny model can do this at all
170
 
 
 
171
  | Job | Who does it |
172
  |---|---|
173
  | Finding *where* a button is | OCR / accessibility tree β€” **not** the model |
174
  | Choosing *what* to do | the model, but only by picking a number from a short menu |
175
+ | Knowing whether it worked | the verifier and the no-progress guard, not the model's word |
176
  | Recovering from a bad step | the retry policy in `loop.py`, not the weights |
177
 
178
+ A 256M model cannot emit `(295, 284)` reliably, but it can pick "4" out of five options β€”
179
+ and the harness owns the coordinate lookup, the vocabulary check and the definition of
180
+ success.
181
 
182
  ## Safety, because this thing has your mouse
183
 
184
  - Run it in a **VM, container, or throwaway desktop account**, not your real session.
185
  - **Dry run is the default**, so you can read a whole episode before anything clicks.
186
+ - Deny lists for typing (`sudo`, `rm -rf`, "password"), widgets (terminal, keychain,
187
+ wallet, bank) and key chords.
188
  - A **kill-switch file** checked before every action, plus a hard step budget.
189
+ - **Blind grid clicks are refused with real input** unless you pass `--allow-grid`: when
190
+ OCR finds nothing, the fallback index is coordinates with no meaning, and a model
191
+ choosing one is guessing about your desktop.
192
+ - **A brain's "done" is a claim, not evidence**: pass `--verify-text` and the harness
193
+ decides success itself.
194
  - **Local-first, for real:** pixels and keystrokes never leave the machine.
195
 
196
  ## What works, and what does not
197
 
198
+ **Works:** real capture, a real OCR index with coordinates that land on the right pixel,
199
+ a real click that an app reacts to, verified success, the closed vocabulary, the policy
200
+ gates, the no-progress guard, echo detection, the doctor, the CLI, 123 tests β€” and a
201
+ 256M VLM that reads a button label from pixels on a clean screenshot.
202
 
203
+ **Does not work yet:** reliability on a *live* screen. The 256M VLM echoed the prompt
204
+ there; the 0.5B text planner picks wrong actions; the accessibility-tree detector is not
205
+ implemented, so labels come from OCR only; and the harness still trusts a model's `done`
206
+ when no verifier is given.
207
 
208
  ## Roadmap
209
 
210
  - [x] Capture, OCR index, closed vocabulary, dry-run default, policy gates, doctor
211
+ - [x] A real click on a real window, with a verified outcome
212
+ - [x] Blind-grid-click guard, echo detection, no-progress guard
213
+ - [ ] Make the VLM reliable on a live screen (it echoes the prompt instead of choosing)
214
+ - [ ] Accessibility-tree detector (AT-SPI on Linux, UI Automation on Windows)
215
  - [ ] Score episodes: replay a trace and count how often the choice was right
216
  - [ ] marimo console: live screen preview, action log, pause / resume / step
217