hkblockchaintechnology commited on
Commit
1a398be
Β·
verified Β·
1 Parent(s): 627c6cf

Publish README.md from marimo

Browse files
Files changed (1) hide show
  1. README.md +121 -110
README.md CHANGED
@@ -18,12 +18,13 @@ tags:
18
  # πŸ–±οΈ Tiny Computer Agent
19
 
20
  **An agentic harness that drives a real computer with a tiny, fully local model.**
21
- No cloud API, no API keys, no telemetry β€” and a core that imports nothing outside
22
- the Python standard library.
23
 
24
- > **Status: early alpha.** The loop runs end to end today, with a scripted screen,
25
- > dry-run execution, a CLI and 56 offline tests. The vision backend is a
26
- > documented seam, not a feature β€” see [what works](#-what-works-today).
 
27
 
28
  ## The idea, in one loop
29
 
@@ -38,143 +39,153 @@ the Python standard library.
38
  repeat until done, or the step budget runs out
39
  ```
40
 
41
- 1. **Observe** β€” grab the screen, downscale it, optionally attach OCR text or the
42
- accessibility tree so the model never has to guess where things are.
43
- 2. **Think** β€” a tiny (≀ 3B, quantised) vision-language model emits **exactly one
44
- action** from a closed vocabulary.
45
- 3. **Act** β€” the harness executes it with OS-level input (mouse, keyboard, scroll).
46
- 4. **Verify** β€” the next frame shows whether it worked. Repeat, bounded by a step
47
- budget and a kill switch.
 
48
 
49
- ## What works today
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  ```
52
- $ python -m tiny_computer_agent
53
- goal: click 'Save' to save the document
54
- mode: dry-run | screen: scripted demo | kill switch: create /tmp/tiny-computer-agent.STOP to stop
55
 
56
- step 0 | wait seconds=0.5 reason="'Save' is not on screen yet" | ok: (dry-run) would wait 0.5s
57
- step 1 | click label='Save' reason="matched 'Save' in the element index" | ok: (dry-run) would click -> pixel (360, 132)
58
- step 2 | done reason="already clicked 'Save'; goal assumed met" | ok: goal reported met
59
 
60
- result: success - brain reported done: already clicked 'Save'; goal assumed met
 
 
 
 
 
 
 
61
  ```
62
 
63
- Shipped and tested:
64
-
65
- - **the loop** β€” `AgentLoop` runs observe β†’ think β†’ act β†’ verify, **re-reads the
66
- screen after every action**, and stops on done, on a step budget, on the kill
67
- switch, on a policy refusal, on a failed action, or on a brain error. Every step
68
- is recorded in an `Episode` you can render or turn into rows.
69
- - **a closed action vocabulary** β€” `click` `type` `key` `scroll` `wait` `done`,
70
- validated *before* anything executes. A model that invents a verb gets a recorded
71
- error, not a click.
72
- - **three brains** β€” `MockBrain` replays a script; `PlannerBrain` is rule-based and
73
- genuinely clicks the element matching the goal (it waits for a dialog instead of
74
- flailing, and refuses to click the same label twice); `VLMBrain` is the seam β€”
75
- prompt assembly and reply validation are done, `_generate()` is yours.
76
- - **two executors** β€” `DryRunExecutor` is the default: it logs the plan and resolves
77
- every label to a pixel. `PyAutoGuiExecutor` does real input and **refuses to click
78
- when a label matches no element**, because a tiny model guessing pixels is how you
79
- click "Delete account". Dry-run fails exactly where live input refuses, so a
80
- rehearsal never gives false confidence.
81
- - **policy gates** β€” deny-listed text (`sudo`, `rm -rf`, "password", "api_key"),
82
- deny-listed widgets (terminal, keychain, password manager, wallet, bank), four
83
- deny-listed key chords, a kill-switch file checked before every action, and a step
84
- budget.
85
- - **real input needs two deliberate flags** β€” `--real-input` *and*
86
- `TINY_COMPUTER_AGENT_ALLOW_REAL_INPUT=1`. Default is a dry run.
87
- - **56 tests** needing no display, no model and no network.
88
-
89
- What does **not** work yet: seeing a real screen *usefully*. `GrabScreen` captures
90
- pixels through `mss`, but nothing yet turns pixels into an element index (OCR or
91
- AT-SPI), so a live screen currently yields zero elements β€” which is exactly what the
92
- label path depends on. And no model is wired up.
93
-
94
- ## Install and run
95
 
96
  ```bash
97
- pip install -e . # core: standard library only, no deps
98
- pytest # 56 offline tests
99
- python -m tiny_computer_agent # scripted demo, dry run
100
- python -m tiny_computer_agent --live # your real screen, still dry run
101
  ```
102
 
103
- ## Put it on your own task
104
 
105
- ```python
106
- from tiny_computer_agent import AgentLoop, Element, FakeScreen, MockBrain
 
 
 
 
107
 
108
- screen = FakeScreen([(Element("Save", x=320, y=120, width=80, height=24),)])
109
- brain = MockBrain([{"kind": "click", "label": "Save"},
110
- {"kind": "done", "reason": "saved"}])
111
 
112
- episode = AgentLoop(screen=screen, brain=brain).run("save the document")
113
- print(episode.render())
114
- episode.to_rows() # rows for a dataframe or notebook table
115
  ```
116
 
117
- Swap `FakeScreen` for `GrabScreen()` and `MockBrain` for your `VLMBrain` subclass and
118
- that is the whole distance between the tests and a live agent.
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- ## Why "tiny" is the whole point
121
 
122
- The harness does the heavy lifting, so the model does not have to be clever:
123
 
124
  | Job | Who does it |
125
  |---|---|
126
- | Finding *where* a button is | OCR / accessibility tree / element index β€” not the model |
127
- | Choosing *what* to do | a tiny VLM over a **closed action menu** (click, type, key, scroll, wait, done) |
128
- | Knowing whether it worked | the next frame, plus harness-side checks |
129
  | Recovering from a bad step | the retry policy in `loop.py`, not the weights |
130
 
131
- That keeps the agent offline, fast to start, and small enough that a laptop GPU
132
- (or plain CPU) can run it on the same box it is controlling.
133
 
134
- ## Candidate backends
135
-
136
- Pluggable behind one interface; nothing here needs a network call at run time.
137
-
138
- - **Moondream 2** / **SmolVLM-256M / 500M** β€” a few hundred MB, safe default
139
- - **Qwen2-VL-2B-Instruct**, **Florence-2-base** β€” stronger grounding, still tiny
140
- - **OCR + text-only planner** β€” no VLM at all; the cheapest credible baseline
141
- - **mock brain** β€” deterministic scripted actions, used to test the harness itself
142
 
143
- ## Layout
 
 
 
 
 
 
 
 
144
 
145
- ```
146
- tiny-computer-agent/
147
- β”œβ”€β”€ README.md
148
- β”œβ”€β”€ LICENSE
149
- β”œβ”€β”€ pyproject.toml
150
- β”œβ”€β”€ tiny_computer_agent_app.py # marimo console: run an episode, publish this card
151
- β”œβ”€β”€ tiny_computer_agent/
152
- β”‚ β”œβ”€β”€ actions.py # the closed action vocabulary
153
- β”‚ β”œβ”€β”€ observe.py # Observation + element index; FakeScreen, GrabScreen
154
- β”‚ β”œβ”€β”€ brain.py # MockBrain, PlannerBrain, VLMBrain (the seam)
155
- β”‚ β”œβ”€β”€ act.py # DryRunExecutor (default) + PyAutoGuiExecutor
156
- β”‚ β”œβ”€β”€ safety.py # Policy: deny lists, kill switch, step budget
157
- β”‚ β”œβ”€β”€ loop.py # AgentLoop, Episode, Step
158
- β”‚ └── __main__.py # CLI
159
- └── tests/ # 56 tests: no display, no model, no network
160
- ```
161
 
162
- ## Safety, because this thing has your mouse
 
 
163
 
164
- - Run it in a **VM, container, or throwaway desktop account** β€” not your real session.
165
- - Dry run is the default; read the whole plan before letting it near your pointer.
166
- - Deny-list for shells, terminals, payment flows and password managers.
167
- - A step budget plus a kill-switch file checked before every action.
168
- - **Local-first, for real:** screen pixels and keystrokes never leave the machine.
169
 
170
  ## Roadmap
171
 
172
- - [x] `loop.py` + `act.py` with the mock brain β€” the harness is testable before any model download
173
- - [x] policy gates: deny lists, kill switch, step budget, dry run by default
174
- - [ ] OCR / AT-SPI element index, so a live screen has clickable labels
175
- - [ ] tiny-VLM grounding backend, behind `VLMBrain._generate()`
 
176
  - [ ] marimo console: live screen preview, action log, pause / resume / step
177
- - [ ] persist episodes, so failed runs can be replayed and scored
178
 
179
  ## License
180
 
 
18
  # πŸ–±οΈ Tiny Computer Agent
19
 
20
  **An agentic harness that drives a real computer with a tiny, fully local model.**
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
 
 
39
  repeat until done, or the step budget runs out
40
  ```
41
 
42
+ 1. **Observe** β€” capture the screen, then build an **element index**: OCR (and,
43
+ optionally, the OS accessibility tree) locates text and widgets and gives each one a
44
+ label and a real screen coordinate.
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
 
104
  ```bash
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
112
 
113
+ ```bash
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 |
132
+ |---|---|
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
 
190
  ## License
191