localagentbrowser 1.61__tar.gz → 1.63__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {localagentbrowser-1.61 → localagentbrowser-1.63}/CHANGELOG.md +115 -1
  2. {localagentbrowser-1.61 → localagentbrowser-1.63}/MANIFEST.in +1 -0
  3. {localagentbrowser-1.61/localagentbrowser.egg-info → localagentbrowser-1.63}/PKG-INFO +105 -15
  4. {localagentbrowser-1.61 → localagentbrowser-1.63}/README.md +104 -14
  5. localagentbrowser-1.63/agent_browser.py +23 -0
  6. localagentbrowser-1.63/localagentbrowser/__init__.py +26 -0
  7. localagentbrowser-1.63/localagentbrowser/agent.py +481 -0
  8. localagentbrowser-1.63/localagentbrowser/checklist.py +93 -0
  9. localagentbrowser-1.63/localagentbrowser/checks.py +402 -0
  10. localagentbrowser-1.63/localagentbrowser/config.py +109 -0
  11. localagentbrowser-1.63/localagentbrowser/coverage.py +273 -0
  12. localagentbrowser-1.63/localagentbrowser/grounding.py +201 -0
  13. localagentbrowser-1.63/localagentbrowser/pages.py +414 -0
  14. localagentbrowser-1.63/localagentbrowser/prompts.py +130 -0
  15. localagentbrowser-1.63/localagentbrowser/report.py +316 -0
  16. localagentbrowser-1.63/localagentbrowser/text.py +60 -0
  17. {localagentbrowser-1.61 → localagentbrowser-1.63/localagentbrowser.egg-info}/PKG-INFO +105 -15
  18. {localagentbrowser-1.61 → localagentbrowser-1.63}/localagentbrowser.egg-info/SOURCES.txt +13 -0
  19. localagentbrowser-1.63/localagentbrowser.egg-info/entry_points.txt +2 -0
  20. localagentbrowser-1.63/localagentbrowser.egg-info/top_level.txt +2 -0
  21. {localagentbrowser-1.61 → localagentbrowser-1.63}/pyproject.toml +3 -2
  22. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/fakes.py +4 -2
  23. localagentbrowser-1.63/tests/golden/prompts.json +2288 -0
  24. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/test_checklist.py +164 -10
  25. localagentbrowser-1.63/tests/test_decisions.py +675 -0
  26. localagentbrowser-1.63/tests/test_golden_prompts.py +133 -0
  27. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/test_main_loop.py +12 -11
  28. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/test_model_turns.py +102 -1
  29. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/test_page_text.py +2 -1
  30. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/test_regression.py +69 -0
  31. {localagentbrowser-1.61 → localagentbrowser-1.63}/tools/live_test.py +3 -2
  32. {localagentbrowser-1.61 → localagentbrowser-1.63}/tools/regression.py +113 -8
  33. localagentbrowser-1.61/agent_browser.py +0 -1805
  34. localagentbrowser-1.61/localagentbrowser.egg-info/entry_points.txt +0 -2
  35. localagentbrowser-1.61/localagentbrowser.egg-info/top_level.txt +0 -1
  36. localagentbrowser-1.61/tests/test_decisions.py +0 -367
  37. {localagentbrowser-1.61 → localagentbrowser-1.63}/LICENSE +0 -0
  38. {localagentbrowser-1.61 → localagentbrowser-1.63}/localagentbrowser.egg-info/dependency_links.txt +0 -0
  39. {localagentbrowser-1.61 → localagentbrowser-1.63}/localagentbrowser.egg-info/requires.txt +0 -0
  40. {localagentbrowser-1.61 → localagentbrowser-1.63}/setup.cfg +0 -0
  41. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/conftest.py +0 -0
  42. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/test_duplicates.py +0 -0
  43. {localagentbrowser-1.61 → localagentbrowser-1.63}/tests/test_journal.py +0 -0
@@ -1,11 +1,125 @@
1
1
  # Changelog
2
2
 
3
- Every version of `agent_browser.py`, newest first. Each released version is also a tagged commit (`v1.11` … `v1.61`); 1.54 to 1.56 and 1.58 to 1.60 were internal steps
3
+ Every version of LocalAgentBrowser, newest first. Each released version is also a tagged commit (`v1.11` … `v1.62`); 1.54 to 1.56 and 1.58 to 1.60 were internal steps
4
4
  measured on the regression suite and released as part of 1.57 and 1.61 (see
5
5
  [docs/regression-history.md](docs/regression-history.md)).
6
6
  Timings are from live runs on the development machine (Intel i5-9500, 6 cores, no GPU, Qwen2.5-7B-Instruct
7
7
  Q4_K_M in LM Studio), where reading the prompt takes about 90% of every model call.
8
8
 
9
+ ## [1.63] - 2026-11-01
10
+ ### Added
11
+ - A model call the server fails is tried again after a wait (5, 15, 30, 60 and 120 seconds, about 4 minutes in all),
12
+ instead of ending the run. LM Studio loads a model by itself when a request names one that is not loaded, and
13
+ unloads idle ones, so this can carry a run through an unloaded model (tested) or, if the app comes back within
14
+ about 4 minutes, a restarted app (not yet tested). Tested live: with the model
15
+ unloaded in the middle of turn 2, LM Studio refused that call ("400 - Model unloaded by user or API request"), the
16
+ agent waited 5 seconds, LM Studio loaded the model again, and the run finished with the right answer (before, the
17
+ same unload ended the run). Only server failures are retried; that refusal is the one 400 that is.
18
+ - Regression suite: `--model` runs the tasks with another model LM Studio has; the model is loaded first and its
19
+ context length checked, so every model is measured under the same conditions, and the run's folder and summary
20
+ name it.
21
+ - Regression suite: honesty measures for each finished result: what it says was not found, the code's caveats,
22
+ gaps filled from the model's own knowledge ("the text does not specify, but it is generally recommended ..."),
23
+ numbers no discovery contains, uncited lines and citations of missing discoveries, in a table in every summary.
24
+ ### Fixed (from the first tests on other models: Qwen2.5 3B, Llama 3.1 8B, Qwen2.5 14B)
25
+ - A runaway reply no longer stalls a run. A 3B model kept writing one turn's reply for 10 minutes (2,500+ tokens,
26
+ where real replies are at most a few hundred) until the connection timed out, and the request was then sent again;
27
+ two of its tasks never got past one turn. A browsing reply is now capped at 600 tokens, a timeout is not retried
28
+ (the model is still writing, not lost), and the client's own silent retries are off.
29
+ - The final result is asked for in a required field, `{"result": "..."}`, like every browsing reply. Asked for plain
30
+ text, Llama 3.1 8B answered 3 of 6 results with a made-up tool call (`{"name": "get_mountain_info", ...}`), and a 3B
31
+ model "finished" a how-to with its own prompt repeated back. A reply that is still JSON or repeats the prompt's
32
+ sections is asked for once more; if that fails too, the report says no result was written and points to the
33
+ findings, instead of showing the broken reply as a result.
34
+ - A finding recorded on the search results page is refused: "NAVIGATE to the result that looks right and DISCOVER from
35
+ the page itself". A result's snippet is a summary written to be clicked, not a source; the 3B recorded snippets in 5
36
+ of 7 tasks, and every model did it at least once.
37
+ ### Fixed (from the full suite on four models, and an untimed suite on the 7B)
38
+ - **Commands are checked as exact text.** A command in a finding must be on the page shown. That covers text in
39
+ backticks, every line of a code block, and any path or placeholder (a backslash, `<venv>`, two slashes); prose
40
+ slashes such as "macOS/Linux" and web addresses are not counted. Word-by-word matching cannot see a wrong
41
+ command:
42
+ - The 7B recorded `pip install package_name`, which the page never gives. It scored 60% against the page, above
43
+ the two lines it really copied, because its words repeat the objective.
44
+ - Copying the official venv activation table, it swapped `bin` and `Scripts` (`C:\> <venv>\bin\activate.bat`)
45
+ in two runs.
46
+
47
+ Across every earlier suite run, 21 of 525 findings fail this check, all on the documentation task and all real.
48
+ The refusal quotes the page's own line for the same command. With that, the 7B recorded only the page's commands
49
+ in 3 of 6 tries, and repeated the refused one in the other 3, which is refused again; told only to leave the
50
+ command out, it kept the invented command and dropped the real ones 6 times out of 6.
51
+ - **The final result's commands must come from a finding.** If one doesn't, its sentence (or code-block line) is cut
52
+ and the command is named after the result: "Left out, because no discovery gives them". In earlier runs this
53
+ caught 5 of 119 results, including the 14B's `<venv>/Scripts/activate` and a code block with `pip install
54
+ package_name`. The report warns about such commands, and the suite's honesty table counts them.
55
+ - **Fewer findings recorded from the wrong place.** A finding is refused, with what to do instead, when:
56
+ - it is empty;
57
+ - it copies the page's headings or outline;
58
+ - it describes the page ("this guide covers…") instead of giving its facts;
59
+ - it only asks questions;
60
+ - it repeats the objective or the searches instead of the page;
61
+ - it repeats the checklist's own wording ("covered by [2]").
62
+ - **Cut-off replies are rescued.** A reply cut off inside its parameter is kept for SEARCH, NAVIGATE and READ_MORE.
63
+ It is cut where the words start repeating, and a search is limited to 20 words. A cut-off finding on the results
64
+ page is refused as usual.
65
+ - **Coverage check:**
66
+ - When the model cites only discovery numbers twice, it is asked a yes-or-no question about the cited
67
+ discovery alone, and a "yes" counts only with words copied from it. A "no" still counts when the discovery has
68
+ words that belong to that item alone: the 7B said "no" 3 times out of 3 for "the light-dependent reactions,
69
+ which take place in the thylakoids". The question accepts a partial answer ("answer the item, or part of
70
+ it"); asked whether the discovery "says what the item asks for", the 7B said "no" 6 times out of 6 for that
71
+ same partial answer.
72
+ - Things are counted only when a finding names them.
73
+ - The words that answered each item are shown to the final result, which had said "the discoveries do not
74
+ specify what goes into photosynthesis" over a discovery naming water, sunlight and carbon dioxide.
75
+ - **Final result:**
76
+ - "Not specified …, but <something from the model>" is cut back to the gap.
77
+ - Items whose findings give no amounts are named, so none are supplied.
78
+ - A "My view" paragraph that repeats the result is dropped.
79
+ - Refusal hints no longer quote a sentence where the page describes itself, or a question.
80
+ - The 14B's model calls took longer than the client library's 10-minute default, which ended its runs. The timeout is
81
+ now 30 minutes.
82
+ - A run stopped while its final result was being written says so in the report, and shows the unchecked draft; the
83
+ suite no longer counts it as finished.
84
+
85
+ ### Changed
86
+ - The README says near the top which goals work and which can't yet: anything behind a form, a login or a button,
87
+ such as flight prices. An automated write-up of the project, built from the README, had suggested "the three
88
+ cheapest flights to Denver" as an example goal. The privacy line also says that searches, written from your
89
+ objective, go to DuckDuckGo and pages come from the web; the objective itself, the model and the report stay on your
90
+ computer.
91
+ - Two real reports from the 1.62 suite run, unedited, in `docs/examples/`, with a note on what each got right and
92
+ wrong (a path slip in one; in the other, an opinion that leans on the one part it found nothing about). Linked
93
+ from the README.
94
+ - `llms.txt`, a short plain-text summary for AI tools that read the repository (the convention described at
95
+ llmstxt.org), saying the same as the README: good goals, what it can't do yet, speed, privacy. Nothing in it is
96
+ hidden or addressed to a model as an instruction.
97
+ - A project site (`docs/`, served by GitHub Pages at https://snowfxxx.github.io/LocalAgentBrowser/) with the
98
+ findings, a quick start and the challenge, plus two write-ups of this round: an article, "We Can Do Better: A
99
+ Small AI That Can't Cover Up", and a technical paper, "Verification over Trust". Every regression run so far is
100
+ attached to the release as a zip.
101
+ - Corrected: the README and the regression history had counted the 14B's results that the time limit cut off while
102
+ they were being written. Of its five finishes, two count (2 of 7).
103
+
104
+ ## [1.62] - 2026-10-25
105
+ No change in what the agent does: the same prompts, decisions and results as 1.61.
106
+ ### Changed
107
+ - The code is now a package, `localagentbrowser`, with one file per job (settings, prompts, page reading,
108
+ grounding, checklist, coverage, checks, report, and the loop) instead of one 1,805-line `agent_browser.py`. The
109
+ code was moved, not rewritten: every line of the old file is in the package once, in its original order with its
110
+ comments. `python agent_browser.py`, the `localagentbrowser` command and `import agent_browser` work as before.
111
+ The README's Code layout section says which file holds what.
112
+ ### Added
113
+ - Golden prompts (`tests/test_golden_prompts.py`): five scripted runs, including one through the outline pointer,
114
+ a refusal of a finding not on the page, a coverage send-back with a fresh prompt and caveats, compared character
115
+ for character with a recorded copy of every message sent to the model, the journal and the report. It was
116
+ recorded before the move and passed unchanged after it; a one-word change to a check's wording makes it fail and
117
+ name the model call that differs.
118
+
119
+ Regression run on the reorganized code: 7 of 7 finished (168.3 model minutes), against 6 of 7 (222.1) on 1.61. With
120
+ the same behaviour this is run-to-run variation (the documentation task finished in 5 turns this time), not an
121
+ improvement; each task's first prompt was identical to 1.61's, and every check still fired where its path came up.
122
+
9
123
  ## [1.61] - 2026-10-21
10
124
  Released together with the internal steps 1.58, 1.59 and 1.60, each measured on the regression suite
11
125
  ([docs/regression-history.md](docs/regression-history.md) has every step's results, task by task).
@@ -2,3 +2,4 @@
2
2
  include CHANGELOG.md
3
3
  recursive-include tests *.py
4
4
  recursive-include tools *.py
5
+ recursive-include tests *.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localagentbrowser
3
- Version: 1.61
3
+ Version: 1.63
4
4
  Summary: An autonomous web agent driven by a local LLM (LM Studio) and Playwright. Runs entirely on your own machine.
5
5
  Author: SnowFX
6
6
  License-Expression: MIT
@@ -48,7 +48,9 @@ A local language model served by [LM Studio](https://lmstudio.ai) decides what t
48
48
  [Playwright](https://playwright.dev/python/) carries it out in a visible Chromium window you can watch. Built
49
49
  and tuned to work well with a **small 7B model on a CPU, no GPU required**.
50
50
 
51
- - 🔒 **No cloud AI and no API keys.** The model runs in your own LM Studio.
51
+ - 🔒 **No cloud AI and no API keys.** The model runs in your own LM Studio. (Its searches, written from your
52
+ objective, go to DuckDuckGo, and it reads pages from the web like any browser; the objective itself, the model and
53
+ the report stay on your computer.)
52
54
  - 🧭 **Goes from objective to findings unattended.** It searches, opens pages, reads long pages part by part,
53
55
  records what's relevant and finishes when the objective is met.
54
56
  - 🛡️ **Keeps a small model on track.** Rules enforced in code stop loops, repeats and drifting, and the model
@@ -56,9 +58,35 @@ and tuned to work well with a **small 7B model on a CPU, no GPU required**.
56
58
  - ⚡ **Built for slow hardware.** It reuses LM Studio's prompt cache, so most turns process only what changed.
57
59
  - 📝 **A readable report every time.** `research_report.md` holds the final result and every finding with its
58
60
  source, written however the run ends; `research_log.txt` keeps every action and reason.
59
- - ✅ **Tested.** 165 tests on Linux and Windows, Python 3.10–3.13, run in a few seconds with no model or browser,
61
+ - ✅ **Tested.** 251 tests on Linux and Windows, Python 3.10–3.13, run in a few seconds with no model or browser,
60
62
  plus a [regression suite](#regression-suite) of live runs on seven different kinds of task.
61
63
 
64
+ **New with 1.63:** the [project site](https://snowfxxx.github.io/LocalAgentBrowser/), an article,
65
+ [We Can Do Better: A Small AI That Can't Cover Up](https://snowfxxx.github.io/LocalAgentBrowser/article.html), and a
66
+ technical paper, [Verification over Trust](https://snowfxxx.github.io/LocalAgentBrowser/paper.html).
67
+
68
+ ### What it's good for, and what it can't do yet
69
+
70
+ - **Good goals:** questions you would research by reading pages. Examples:
71
+ - a fact ("How tall is Kilimanjaro, in meters and feet?")
72
+ - a list with conditions ("Three free, open-source alternatives to Microsoft Office, and what each is best at")
73
+ - a comparison with a recommendation
74
+ - a how-to
75
+ - an explanation of a topic
76
+ - views from several sources
77
+ - looking something up in official documentation
78
+
79
+ These are the seven kinds of task the [regression suite](#regression-suite) measures.
80
+ - **Not yet:**
81
+ - **Anything behind a form, a login or a button.** It reads pages; it does not click, fill in forms or sign
82
+ in. So goals like "the three cheapest flights to Denver next month" won't work: flight and hotel prices,
83
+ shopping and booking sites need searches it cannot type into.
84
+ - **Fast answers.** On a CPU a run takes about 10 to 45 minutes, and a 7B model sometimes returns thin findings,
85
+ so check the result against its listed sources.
86
+
87
+ See [two example reports](https://github.com/SnowFXXX/LocalAgentBrowser/blob/main/docs/examples/README.md), unedited,
88
+ with what each got right and wrong.
89
+
62
90
  📖 **The story behind it:** [Teaching a 7B model on a CPU to finish what it starts](https://dev.to/snowfxxx/teaching-a-7b-model-on-a-cpu-to-finish-what-it-starts-1amc),
63
91
  six lessons from 30 versions of keeping a small local model on track.
64
92
 
@@ -222,7 +250,7 @@ current page, and answers with exactly one JSON action:
222
250
  |---|---|
223
251
  | `SEARCH` | Searches DuckDuckGo (HTML version) and shows a numbered list of results with their real URLs |
224
252
  | `NAVIGATE` | Opens a URL taken from the results or from the current page's links |
225
- | `READ_MORE` | Shows the next part of a long page (pages are read in parts of about 2,500 characters) |
253
+ | `READ_MORE` | Shows the next part of a long page (pages are read in parts of about 2,500 characters), or the unread part whose outline label names an item still missing |
226
254
  | `DISCOVER` | Records information from the current page as a finding |
227
255
  | `FINISH` | Ends the run (after checking each part of the objective is covered); the final result is then written in a separate step |
228
256
 
@@ -237,6 +265,25 @@ flowchart LR
237
265
  C -->|FINISH| R[research_report.md]
238
266
  ```
239
267
 
268
+ ### Code layout
269
+
270
+ The code is in the `localagentbrowser` package, one file per job, each depending only on the ones above it:
271
+
272
+ | File | What it holds |
273
+ |---|---|
274
+ | `config.py` | Settings: the LM Studio connection (from environment variables) and every limit and threshold |
275
+ | `prompts.py` | Every fixed text the model reads: the system prompt, turn instructions, the checklist, coverage and final-result prompts |
276
+ | `text.py` | Small word helpers the checks share |
277
+ | `pages.py` | Reading the web: search results, page parts, the outline and its pointer, links |
278
+ | `grounding.py` | Whether a finding is really on the page shown, or already recorded |
279
+ | `checklist.py` | The objective split into a fixed checklist, and findings linked to its items |
280
+ | `coverage.py` | The coverage check before finishing, verified in code, and what is reported as not found or not proven |
281
+ | `checks.py` | Decisions refused or confirmed before they are carried out (leaving, reading on, finishing, ...) |
282
+ | `report.py` | The journal, the research log, the final result and the report file |
283
+ | `agent.py` | The loop: each turn's conversation with the model, in-turn retries, and `main()` |
284
+
285
+ `agent_browser.py` keeps `python agent_browser.py` working from a clone.
286
+
240
287
  ### Keeping a small model on track
241
288
 
242
289
  - **Rules enforced in code, not just stated in the prompt.** Repeated searches, visiting the current page,
@@ -253,6 +300,11 @@ flowchart LR
253
300
  its own knowledge when a page says "follow these 6 steps" and the steps come later. The refusal points the way:
254
301
  it quotes the one page sentence closest to what the model wrote, to record instead, or names what the parts
255
302
  shown never mention ("Japan, Navajo"), so the model reads on.
303
+ - **Commands must be on the page, exactly.** Commands and paths in a finding (in backticks, in a code block, or
304
+ any word with a backslash, a `<placeholder>` or two slashes) must appear on the page as written, however often
305
+ the finding is repeated. Word matching cannot catch a wrong command: a 7B recorded `pip install package_name`,
306
+ which the official page never gives, and swapped `bin` and `Scripts` when copying its activation table. The same
307
+ goes for the final result: a command no finding gives is cut and named after the result.
256
308
  - **An objective checklist.** At the start, one short model call splits the objective into a fixed checklist of
257
309
  what the result must contain, keeping conditions like "free, open-source" or "three". Each finding names the
258
310
  items it serves, the checklist with its findings is in every prompt, and every check goes through these items
@@ -264,8 +316,10 @@ flowchart LR
264
316
  its own, and the code checks the verdict instead of taking it on trust: an item nothing was copied for, fewer
265
317
  things than asked for ("only 1 of 3 found"), and things not shown, in words copied from a finding about them,
266
318
  to meet a condition such as "open-source". A claim that something is missing is dropped when every word of it is
267
- already in the findings. With gaps, the FINISH is sent back once, with the next step named ("SEARCH for
268
- 'OnlyOffice free and open-source' to check it, or replace it"), and the next turn starts from a fresh prompt.
319
+ already in the findings. Only gaps the code verifies send the FINISH back, once: an item nothing was copied for,
320
+ fewer things than asked for ("only 1 of 3 found"), or nothing recorded. The model's other verdicts, and things
321
+ not shown to meet a condition, only shape the final result. The send-back names the next step, and the next turn
322
+ starts from a fresh prompt.
269
323
  - **Numbered findings.** Findings are numbered in the prompt and listed with every check, and the model names
270
324
  the finding that covers each part of the objective ("covered by [2]") instead of just saying "covered".
271
325
  - **Knowing when it's done.** After every new finding the model is asked whether the objective is now fully
@@ -280,9 +334,10 @@ flowchart LR
280
334
  cite findings by number, give as many items as asked for (never repeating one, and saying how many were not
281
335
  found), and add a "My view:" paragraph only when the objective asks for an opinion. A small model writes far
282
336
  better when that is its only job, and every claim stays traceable to a recorded finding. What no finding
283
- answers is not left to the model: those checklist items, and things not shown to meet a condition, are left out
284
- of what it is asked to write, and the code adds them after the result as "Not found in the pages read" and
285
- "Left out, not shown to meet the conditions". The report flags any number in the final result that appears in
337
+ answers is not left to the model: those checklist items are left out of what it is asked to write, and the code
338
+ adds them after the result as "Not found in the pages read". A thing not shown to meet a condition stays in the
339
+ result, and the code marks it where it is first named ("not shown in the pages read to be open-source"), or lists
340
+ it after the result if the result does not name it. The report flags any number in the final result that appears in
286
341
  none of the findings, the most harmful kind of slip in a how-to.
287
342
  - **Error pages are recognised.** A page answering 404, 403 and so on counts as a failed visit, not content.
288
343
  - **Readable pages.** Navigation menus, footers, cookie banners and site notices such as fundraising banners are
@@ -307,8 +362,8 @@ the conversation nears the context limit. The journal uses a compact one-line-pe
307
362
  | `LMSTUDIO_API_KEY` | `lm-studio` | API key, if your server requires one |
308
363
  | `LMSTUDIO_CONTEXT_LENGTH` | `8192` | Context length the model is loaded with; the conversation budget follows it |
309
364
 
310
- Behaviour limits (part size, retry counts, check intervals and so on) are named constants at the top of
311
- `agent_browser.py`, each with a comment explaining it.
365
+ Behaviour limits (part size, retry counts, check intervals and so on) are named constants in
366
+ `localagentbrowser/config.py`, each with a comment explaining it.
312
367
 
313
368
  ## Performance
314
369
 
@@ -337,13 +392,32 @@ It was developed and tested with Qwen2.5-7B-Instruct (Q4_K_M). Any instruction-t
337
392
  serves should work, as long as the server supports `response_format` with a JSON schema (LM Studio 0.3 and
338
393
  later). Set `LMSTUDIO_MODEL` and `LMSTUDIO_CONTEXT_LENGTH` to match. Reports with other models are very welcome.
339
394
 
395
+ The full suite, run on four models with the same code, 90 minutes per task, CPU only (October 2026):
396
+
397
+ | Model | Tasks finished | Model time | Gaps filled from the model's own knowledge |
398
+ |---|---|---|---|
399
+ | Qwen2.5-7B-Instruct | 6 of 7 | 197 min | 0 |
400
+ | Qwen2.5-14B-Instruct | 2 of 7 | 535 min | 0 |
401
+ | Llama-3.1-8B-Instruct | 2 of 7 | 278 min | 0 |
402
+ | Qwen2.5-3B-Instruct | 0 of 7 | 302 min | — |
403
+
404
+ Where they finished, every model's results were honest by the suite's measures. What separates them is speed and
405
+ follow-through:
406
+ - The 14B was about 2.5 times slower than the 7B on a CPU. It reached FINISH on five tasks, but on three of them
407
+ the time limit hit while the final result was being written, so only two count.
408
+ - Llama found most items but rarely finished in time.
409
+ - The 3B recorded findings on every task but never finished one.
410
+
411
+ With no time limit, the 7B finished 7 of 7 (277 minutes). These measures are word checks: the suite counted a
412
+ documentation result with an invented command as clean, which led to the command check above.
413
+
340
414
  **Can I use another OpenAI-compatible server instead of LM Studio?**
341
415
  Probably: point `LMSTUDIO_BASE_URL` at it. It has only been tested with LM Studio, and the speed tricks rely
342
416
  on the server reusing cached prompt prefixes (llama.cpp-based servers do).
343
417
 
344
418
  **Is anything sent to the internet?**
345
419
  No AI service is involved, and nothing about your run is uploaded. The agent does browse the real web,
346
- though: search queries go to DuckDuckGo, and pages it opens load normally, as if you visited them yourself.
420
+ though: search queries, written from your objective, go to DuckDuckGo, and pages it opens load normally, as if you visited them yourself.
347
421
 
348
422
  **Where are the results?**
349
423
  In the folder you ran it from: `research_report.md` has the final result and every finding with its source,
@@ -381,7 +455,8 @@ turn that brings in a new page takes a few minutes. See [Performance](#performan
381
455
  - **Very strict objectives.** An objective that forbids finishing until everything is "fully validated" used
382
456
  to keep the agent looking for something the web may not contain. On 1.50 it finished such an objective in 25
383
457
  minutes, but with thin findings and without naming what it could not find, so check the result against the
384
- objective.
458
+ objective. Since 1.57 the code lists checklist items no finding answers after the result; this has not been
459
+ tested on a strict objective yet.
385
460
  - **Check the citations.** The final result is told to use only recorded findings, but a 7B model sometimes
386
461
  puts the wrong number on a citation, adds a line no finding supports, leaves out a step or misreads a part of
387
462
  the question (on 1.57 it answered "how long it keeps in the fridge" with the steeping time), so verify a claim
@@ -399,7 +474,7 @@ turn that brings in a new page takes a few minutes. See [Performance](#performan
399
474
  - [x] Write the final result in a separate, rule-bound step and flag unsupported numbers (1.32–1.38)
400
475
  - [x] Finish as soon as the objective is covered (1.35)
401
476
  - [x] Get the model to weigh each part of a long page before reading on (1.42)
402
- - [ ] Let very strict objectives end with the gaps named instead of searching indefinitely (they finish since 1.50; naming the gaps is still open)
477
+ - [ ] Let very strict objectives end with the gaps named instead of searching indefinitely (they finish since 1.50; since 1.57 the code names checklist items no finding answers, not yet tested on a strict objective)
403
478
  - [x] Measure every change on a regression suite of seven kinds of task (1.53)
404
479
  - [x] Keep a fixed checklist of the objective's parts instead of re-splitting it at every check (1.53)
405
480
  - [ ] Check that a finding really covers its checklist item, including conditions like "open source" (partly, since
@@ -411,8 +486,13 @@ turn that brings in a new page takes a few minutes. See [Performance](#performan
411
486
  - [ ] Rewind dead ends: save the conversation before an expensive step such as reading a long page, and if it
412
487
  leads nowhere, return to that point with a short note instead of carrying the unused page text in every later
413
488
  prompt (LM Studio reuses its cache up to the saved point)
489
+ - [ ] Catch an objective too ambiguous to research before the run starts, and only then ask one question: never
490
+ during a run, and only for extreme ambiguity, so the run can still be left unattended but isn't spent chasing an
491
+ objective it can't pin down
492
+ - [ ] A small project site on GitHub Pages: the README's essentials, the example reports and `llms.txt` at the
493
+ site's root, where AI tools look for it
414
494
  - [ ] Try more kinds of objectives (seven so far) and adjust the rules that don't generalise
415
- - [ ] Make fresh prompts (the slowest kind of turn) cheaper still (31% cheaper since 1.34)
495
+ - [ ] Make fresh prompts (the slowest kind of turn) cheaper still (31% cheaper since 1.34; about 2,500 tokens since 1.61, from 3,500–4,900)
416
496
  - [ ] Interacting with pages (clicking, forms) for tasks beyond reading
417
497
 
418
498
  Ideas and requests are welcome in [issues](https://github.com/SnowFXXX/LocalAgentBrowser/issues).
@@ -428,6 +508,12 @@ python -m pytest
428
508
  python -m ruff check .
429
509
  ```
430
510
 
511
+ `tests/test_golden_prompts.py` replays five scripted runs and compares every message sent to the model, and the
512
+ journal and report, with a recorded copy in `tests/golden/prompts.json`. A change meant to leave the agent's
513
+ behaviour alone must keep it passing unchanged; a deliberate change to what the model sees is recorded again with
514
+ `GOLDEN_UPDATE=1 python -m pytest tests/test_golden_prompts.py`, and the diff of the recorded copy reviewed with
515
+ the rest of the change.
516
+
431
517
  To try the real agent with a fixed objective and a time limit, logging the time and tokens of every model
432
518
  call:
433
519
 
@@ -441,6 +527,10 @@ To measure a change on all seven kinds of task (about four hours on a CPU), comp
441
527
  python tools/regression.py --baseline regression_runs/<earlier run>/summary.json
442
528
  ```
443
529
 
530
+ To measure another model on the same tasks, name it as LM Studio lists it, for example
531
+ `--model meta-llama-3.1-8b-instruct`. The model is loaded before the first task and its context length checked, so
532
+ every run is measured with the same context length; the run's folder and summary name the model.
533
+
444
534
  Each run gets a folder in `regression_runs/` with every task's report, journal, console output and prompt
445
535
  trace, and a `summary.md` that is updated after each task: whether it finished, turns, model time, how many
446
536
  checklist items got findings, which checks fired, and problems found by simple word checks (missing answers,
@@ -16,7 +16,9 @@ A local language model served by [LM Studio](https://lmstudio.ai) decides what t
16
16
  [Playwright](https://playwright.dev/python/) carries it out in a visible Chromium window you can watch. Built
17
17
  and tuned to work well with a **small 7B model on a CPU, no GPU required**.
18
18
 
19
- - 🔒 **No cloud AI and no API keys.** The model runs in your own LM Studio.
19
+ - 🔒 **No cloud AI and no API keys.** The model runs in your own LM Studio. (Its searches, written from your
20
+ objective, go to DuckDuckGo, and it reads pages from the web like any browser; the objective itself, the model and
21
+ the report stay on your computer.)
20
22
  - 🧭 **Goes from objective to findings unattended.** It searches, opens pages, reads long pages part by part,
21
23
  records what's relevant and finishes when the objective is met.
22
24
  - 🛡️ **Keeps a small model on track.** Rules enforced in code stop loops, repeats and drifting, and the model
@@ -24,9 +26,35 @@ and tuned to work well with a **small 7B model on a CPU, no GPU required**.
24
26
  - ⚡ **Built for slow hardware.** It reuses LM Studio's prompt cache, so most turns process only what changed.
25
27
  - 📝 **A readable report every time.** `research_report.md` holds the final result and every finding with its
26
28
  source, written however the run ends; `research_log.txt` keeps every action and reason.
27
- - ✅ **Tested.** 165 tests on Linux and Windows, Python 3.10–3.13, run in a few seconds with no model or browser,
29
+ - ✅ **Tested.** 251 tests on Linux and Windows, Python 3.10–3.13, run in a few seconds with no model or browser,
28
30
  plus a [regression suite](#regression-suite) of live runs on seven different kinds of task.
29
31
 
32
+ **New with 1.63:** the [project site](https://snowfxxx.github.io/LocalAgentBrowser/), an article,
33
+ [We Can Do Better: A Small AI That Can't Cover Up](https://snowfxxx.github.io/LocalAgentBrowser/article.html), and a
34
+ technical paper, [Verification over Trust](https://snowfxxx.github.io/LocalAgentBrowser/paper.html).
35
+
36
+ ### What it's good for, and what it can't do yet
37
+
38
+ - **Good goals:** questions you would research by reading pages. Examples:
39
+ - a fact ("How tall is Kilimanjaro, in meters and feet?")
40
+ - a list with conditions ("Three free, open-source alternatives to Microsoft Office, and what each is best at")
41
+ - a comparison with a recommendation
42
+ - a how-to
43
+ - an explanation of a topic
44
+ - views from several sources
45
+ - looking something up in official documentation
46
+
47
+ These are the seven kinds of task the [regression suite](#regression-suite) measures.
48
+ - **Not yet:**
49
+ - **Anything behind a form, a login or a button.** It reads pages; it does not click, fill in forms or sign
50
+ in. So goals like "the three cheapest flights to Denver next month" won't work: flight and hotel prices,
51
+ shopping and booking sites need searches it cannot type into.
52
+ - **Fast answers.** On a CPU a run takes about 10 to 45 minutes, and a 7B model sometimes returns thin findings,
53
+ so check the result against its listed sources.
54
+
55
+ See [two example reports](https://github.com/SnowFXXX/LocalAgentBrowser/blob/main/docs/examples/README.md), unedited,
56
+ with what each got right and wrong.
57
+
30
58
  📖 **The story behind it:** [Teaching a 7B model on a CPU to finish what it starts](https://dev.to/snowfxxx/teaching-a-7b-model-on-a-cpu-to-finish-what-it-starts-1amc),
31
59
  six lessons from 30 versions of keeping a small local model on track.
32
60
 
@@ -190,7 +218,7 @@ current page, and answers with exactly one JSON action:
190
218
  |---|---|
191
219
  | `SEARCH` | Searches DuckDuckGo (HTML version) and shows a numbered list of results with their real URLs |
192
220
  | `NAVIGATE` | Opens a URL taken from the results or from the current page's links |
193
- | `READ_MORE` | Shows the next part of a long page (pages are read in parts of about 2,500 characters) |
221
+ | `READ_MORE` | Shows the next part of a long page (pages are read in parts of about 2,500 characters), or the unread part whose outline label names an item still missing |
194
222
  | `DISCOVER` | Records information from the current page as a finding |
195
223
  | `FINISH` | Ends the run (after checking each part of the objective is covered); the final result is then written in a separate step |
196
224
 
@@ -205,6 +233,25 @@ flowchart LR
205
233
  C -->|FINISH| R[research_report.md]
206
234
  ```
207
235
 
236
+ ### Code layout
237
+
238
+ The code is in the `localagentbrowser` package, one file per job, each depending only on the ones above it:
239
+
240
+ | File | What it holds |
241
+ |---|---|
242
+ | `config.py` | Settings: the LM Studio connection (from environment variables) and every limit and threshold |
243
+ | `prompts.py` | Every fixed text the model reads: the system prompt, turn instructions, the checklist, coverage and final-result prompts |
244
+ | `text.py` | Small word helpers the checks share |
245
+ | `pages.py` | Reading the web: search results, page parts, the outline and its pointer, links |
246
+ | `grounding.py` | Whether a finding is really on the page shown, or already recorded |
247
+ | `checklist.py` | The objective split into a fixed checklist, and findings linked to its items |
248
+ | `coverage.py` | The coverage check before finishing, verified in code, and what is reported as not found or not proven |
249
+ | `checks.py` | Decisions refused or confirmed before they are carried out (leaving, reading on, finishing, ...) |
250
+ | `report.py` | The journal, the research log, the final result and the report file |
251
+ | `agent.py` | The loop: each turn's conversation with the model, in-turn retries, and `main()` |
252
+
253
+ `agent_browser.py` keeps `python agent_browser.py` working from a clone.
254
+
208
255
  ### Keeping a small model on track
209
256
 
210
257
  - **Rules enforced in code, not just stated in the prompt.** Repeated searches, visiting the current page,
@@ -221,6 +268,11 @@ flowchart LR
221
268
  its own knowledge when a page says "follow these 6 steps" and the steps come later. The refusal points the way:
222
269
  it quotes the one page sentence closest to what the model wrote, to record instead, or names what the parts
223
270
  shown never mention ("Japan, Navajo"), so the model reads on.
271
+ - **Commands must be on the page, exactly.** Commands and paths in a finding (in backticks, in a code block, or
272
+ any word with a backslash, a `<placeholder>` or two slashes) must appear on the page as written, however often
273
+ the finding is repeated. Word matching cannot catch a wrong command: a 7B recorded `pip install package_name`,
274
+ which the official page never gives, and swapped `bin` and `Scripts` when copying its activation table. The same
275
+ goes for the final result: a command no finding gives is cut and named after the result.
224
276
  - **An objective checklist.** At the start, one short model call splits the objective into a fixed checklist of
225
277
  what the result must contain, keeping conditions like "free, open-source" or "three". Each finding names the
226
278
  items it serves, the checklist with its findings is in every prompt, and every check goes through these items
@@ -232,8 +284,10 @@ flowchart LR
232
284
  its own, and the code checks the verdict instead of taking it on trust: an item nothing was copied for, fewer
233
285
  things than asked for ("only 1 of 3 found"), and things not shown, in words copied from a finding about them,
234
286
  to meet a condition such as "open-source". A claim that something is missing is dropped when every word of it is
235
- already in the findings. With gaps, the FINISH is sent back once, with the next step named ("SEARCH for
236
- 'OnlyOffice free and open-source' to check it, or replace it"), and the next turn starts from a fresh prompt.
287
+ already in the findings. Only gaps the code verifies send the FINISH back, once: an item nothing was copied for,
288
+ fewer things than asked for ("only 1 of 3 found"), or nothing recorded. The model's other verdicts, and things
289
+ not shown to meet a condition, only shape the final result. The send-back names the next step, and the next turn
290
+ starts from a fresh prompt.
237
291
  - **Numbered findings.** Findings are numbered in the prompt and listed with every check, and the model names
238
292
  the finding that covers each part of the objective ("covered by [2]") instead of just saying "covered".
239
293
  - **Knowing when it's done.** After every new finding the model is asked whether the objective is now fully
@@ -248,9 +302,10 @@ flowchart LR
248
302
  cite findings by number, give as many items as asked for (never repeating one, and saying how many were not
249
303
  found), and add a "My view:" paragraph only when the objective asks for an opinion. A small model writes far
250
304
  better when that is its only job, and every claim stays traceable to a recorded finding. What no finding
251
- answers is not left to the model: those checklist items, and things not shown to meet a condition, are left out
252
- of what it is asked to write, and the code adds them after the result as "Not found in the pages read" and
253
- "Left out, not shown to meet the conditions". The report flags any number in the final result that appears in
305
+ answers is not left to the model: those checklist items are left out of what it is asked to write, and the code
306
+ adds them after the result as "Not found in the pages read". A thing not shown to meet a condition stays in the
307
+ result, and the code marks it where it is first named ("not shown in the pages read to be open-source"), or lists
308
+ it after the result if the result does not name it. The report flags any number in the final result that appears in
254
309
  none of the findings, the most harmful kind of slip in a how-to.
255
310
  - **Error pages are recognised.** A page answering 404, 403 and so on counts as a failed visit, not content.
256
311
  - **Readable pages.** Navigation menus, footers, cookie banners and site notices such as fundraising banners are
@@ -275,8 +330,8 @@ the conversation nears the context limit. The journal uses a compact one-line-pe
275
330
  | `LMSTUDIO_API_KEY` | `lm-studio` | API key, if your server requires one |
276
331
  | `LMSTUDIO_CONTEXT_LENGTH` | `8192` | Context length the model is loaded with; the conversation budget follows it |
277
332
 
278
- Behaviour limits (part size, retry counts, check intervals and so on) are named constants at the top of
279
- `agent_browser.py`, each with a comment explaining it.
333
+ Behaviour limits (part size, retry counts, check intervals and so on) are named constants in
334
+ `localagentbrowser/config.py`, each with a comment explaining it.
280
335
 
281
336
  ## Performance
282
337
 
@@ -305,13 +360,32 @@ It was developed and tested with Qwen2.5-7B-Instruct (Q4_K_M). Any instruction-t
305
360
  serves should work, as long as the server supports `response_format` with a JSON schema (LM Studio 0.3 and
306
361
  later). Set `LMSTUDIO_MODEL` and `LMSTUDIO_CONTEXT_LENGTH` to match. Reports with other models are very welcome.
307
362
 
363
+ The full suite, run on four models with the same code, 90 minutes per task, CPU only (October 2026):
364
+
365
+ | Model | Tasks finished | Model time | Gaps filled from the model's own knowledge |
366
+ |---|---|---|---|
367
+ | Qwen2.5-7B-Instruct | 6 of 7 | 197 min | 0 |
368
+ | Qwen2.5-14B-Instruct | 2 of 7 | 535 min | 0 |
369
+ | Llama-3.1-8B-Instruct | 2 of 7 | 278 min | 0 |
370
+ | Qwen2.5-3B-Instruct | 0 of 7 | 302 min | — |
371
+
372
+ Where they finished, every model's results were honest by the suite's measures. What separates them is speed and
373
+ follow-through:
374
+ - The 14B was about 2.5 times slower than the 7B on a CPU. It reached FINISH on five tasks, but on three of them
375
+ the time limit hit while the final result was being written, so only two count.
376
+ - Llama found most items but rarely finished in time.
377
+ - The 3B recorded findings on every task but never finished one.
378
+
379
+ With no time limit, the 7B finished 7 of 7 (277 minutes). These measures are word checks: the suite counted a
380
+ documentation result with an invented command as clean, which led to the command check above.
381
+
308
382
  **Can I use another OpenAI-compatible server instead of LM Studio?**
309
383
  Probably: point `LMSTUDIO_BASE_URL` at it. It has only been tested with LM Studio, and the speed tricks rely
310
384
  on the server reusing cached prompt prefixes (llama.cpp-based servers do).
311
385
 
312
386
  **Is anything sent to the internet?**
313
387
  No AI service is involved, and nothing about your run is uploaded. The agent does browse the real web,
314
- though: search queries go to DuckDuckGo, and pages it opens load normally, as if you visited them yourself.
388
+ though: search queries, written from your objective, go to DuckDuckGo, and pages it opens load normally, as if you visited them yourself.
315
389
 
316
390
  **Where are the results?**
317
391
  In the folder you ran it from: `research_report.md` has the final result and every finding with its source,
@@ -349,7 +423,8 @@ turn that brings in a new page takes a few minutes. See [Performance](#performan
349
423
  - **Very strict objectives.** An objective that forbids finishing until everything is "fully validated" used
350
424
  to keep the agent looking for something the web may not contain. On 1.50 it finished such an objective in 25
351
425
  minutes, but with thin findings and without naming what it could not find, so check the result against the
352
- objective.
426
+ objective. Since 1.57 the code lists checklist items no finding answers after the result; this has not been
427
+ tested on a strict objective yet.
353
428
  - **Check the citations.** The final result is told to use only recorded findings, but a 7B model sometimes
354
429
  puts the wrong number on a citation, adds a line no finding supports, leaves out a step or misreads a part of
355
430
  the question (on 1.57 it answered "how long it keeps in the fridge" with the steeping time), so verify a claim
@@ -367,7 +442,7 @@ turn that brings in a new page takes a few minutes. See [Performance](#performan
367
442
  - [x] Write the final result in a separate, rule-bound step and flag unsupported numbers (1.32–1.38)
368
443
  - [x] Finish as soon as the objective is covered (1.35)
369
444
  - [x] Get the model to weigh each part of a long page before reading on (1.42)
370
- - [ ] Let very strict objectives end with the gaps named instead of searching indefinitely (they finish since 1.50; naming the gaps is still open)
445
+ - [ ] Let very strict objectives end with the gaps named instead of searching indefinitely (they finish since 1.50; since 1.57 the code names checklist items no finding answers, not yet tested on a strict objective)
371
446
  - [x] Measure every change on a regression suite of seven kinds of task (1.53)
372
447
  - [x] Keep a fixed checklist of the objective's parts instead of re-splitting it at every check (1.53)
373
448
  - [ ] Check that a finding really covers its checklist item, including conditions like "open source" (partly, since
@@ -379,8 +454,13 @@ turn that brings in a new page takes a few minutes. See [Performance](#performan
379
454
  - [ ] Rewind dead ends: save the conversation before an expensive step such as reading a long page, and if it
380
455
  leads nowhere, return to that point with a short note instead of carrying the unused page text in every later
381
456
  prompt (LM Studio reuses its cache up to the saved point)
457
+ - [ ] Catch an objective too ambiguous to research before the run starts, and only then ask one question: never
458
+ during a run, and only for extreme ambiguity, so the run can still be left unattended but isn't spent chasing an
459
+ objective it can't pin down
460
+ - [ ] A small project site on GitHub Pages: the README's essentials, the example reports and `llms.txt` at the
461
+ site's root, where AI tools look for it
382
462
  - [ ] Try more kinds of objectives (seven so far) and adjust the rules that don't generalise
383
- - [ ] Make fresh prompts (the slowest kind of turn) cheaper still (31% cheaper since 1.34)
463
+ - [ ] Make fresh prompts (the slowest kind of turn) cheaper still (31% cheaper since 1.34; about 2,500 tokens since 1.61, from 3,500–4,900)
384
464
  - [ ] Interacting with pages (clicking, forms) for tasks beyond reading
385
465
 
386
466
  Ideas and requests are welcome in [issues](https://github.com/SnowFXXX/LocalAgentBrowser/issues).
@@ -396,6 +476,12 @@ python -m pytest
396
476
  python -m ruff check .
397
477
  ```
398
478
 
479
+ `tests/test_golden_prompts.py` replays five scripted runs and compares every message sent to the model, and the
480
+ journal and report, with a recorded copy in `tests/golden/prompts.json`. A change meant to leave the agent's
481
+ behaviour alone must keep it passing unchanged; a deliberate change to what the model sees is recorded again with
482
+ `GOLDEN_UPDATE=1 python -m pytest tests/test_golden_prompts.py`, and the diff of the recorded copy reviewed with
483
+ the rest of the change.
484
+
399
485
  To try the real agent with a fixed objective and a time limit, logging the time and tokens of every model
400
486
  call:
401
487
 
@@ -409,6 +495,10 @@ To measure a change on all seven kinds of task (about four hours on a CPU), comp
409
495
  python tools/regression.py --baseline regression_runs/<earlier run>/summary.json
410
496
  ```
411
497
 
498
+ To measure another model on the same tasks, name it as LM Studio lists it, for example
499
+ `--model meta-llama-3.1-8b-instruct`. The model is loaded before the first task and its context length checked, so
500
+ every run is measured with the same context length; the run's folder and summary name the model.
501
+
412
502
  Each run gets a folder in `regression_runs/` with every task's report, journal, console output and prompt
413
503
  trace, and a `summary.md` that is updated after each task: whether it finished, turns, model time, how many
414
504
  checklist items got findings, which checks fired, and problems found by simple word checks (missing answers,
@@ -0,0 +1,23 @@
1
+ """LocalAgentBrowser: an autonomous web agent driven by a local language model.
2
+
3
+ Run: python agent_browser.py (or `localagentbrowser` once installed; it asks for the objective)
4
+
5
+ The code lives in the localagentbrowser package (see its __init__.py for how it is organized). This file keeps
6
+ `python agent_browser.py` working from a clone, and `import agent_browser` working for the tests and tools: it
7
+ gives access to every name in the package. To replace a part of the agent in a test, patch the module that
8
+ uses it (for example localagentbrowser.agent.AsyncOpenAI), not this file.
9
+ """
10
+ from localagentbrowser.config import * # noqa: F403
11
+ from localagentbrowser.prompts import * # noqa: F403
12
+ from localagentbrowser.text import * # noqa: F403
13
+ from localagentbrowser.pages import * # noqa: F403
14
+ from localagentbrowser.grounding import * # noqa: F403
15
+ from localagentbrowser.checklist import * # noqa: F403
16
+ from localagentbrowser.coverage import * # noqa: F403
17
+ from localagentbrowser.checks import * # noqa: F403
18
+ from localagentbrowser.report import * # noqa: F403
19
+ from localagentbrowser.agent import * # noqa: F403
20
+ from localagentbrowser.agent import main, run # noqa: F401
21
+
22
+ if __name__ == "__main__":
23
+ run()