spotlit v1
spotlit ranks the elements of a web page against a browser agent's goal, in the browser, so the agent's language model reads the 50 elements that matter instead of the whole page. It has two stages, ships as 6.7 MB, and needs no server and no GPU: about a quarter of a second per page on eight WebAssembly threads.
On Mind2Web's three test splits, run once after the model was frozen, the element a person actually clicked is in the top 50 on 80.8%, 75.8% and 75.9% of steps (cross-task, cross-website, cross-domain), against about 44% for keyword search, while the agent reads 32 to 43 times fewer tokens.
Source, training code and the browser demo: https://github.com/Sardor-M/spotlit
Files
| Path | Size | What it is |
|---|---|---|
web/spotlit-v1.weights.gz |
5.04 MB | Everything a web page needs in one gzipped file, read by spotlit.loadPacked. spotlit's own format, not a PyTorch checkpoint. |
web/spotlit.js, tinylab.js, wordpiece.js |
The browser runtime (MIT) | |
onnx/reranker.int8.onnx |
4.49 MB | Stage 2 as shipped: BERT-tiny cross-encoder, INT8 ONNX |
reranker/ |
17.5 MB | Stage 2 at full precision: model.safetensors, config.json and tokenizer, for transformers |
stage1/stage1.tlab |
1.99 MB | Stage 1 as shipped: hashed byte n-gram table, INT8 |
stage1/model.safetensors |
1.99 MB | The same table and scales as safetensors, for inspection |
stage1/config.json |
The encoder that turns text into table rows | |
config.json |
The release at a glance: where each stage is, and the operating point (stage 2 re-reads 400, returns 50) | |
manifest.json |
sha256 of every file, input formats, operating point |
How it works
- Describe. Each element becomes one line: its tag, then its labels (aria-label, title,
alt, placeholder, name, type, role, value; 60 characters each), then up to 24 words of its
own text, then
|and up to 12 words of its parent's text. - Stage 1 embeds the goal and every element with the hashed n-gram table and ranks them by cosine similarity. About 35 ms for 500 elements.
- Stage 2 reads
[CLS] goal [SEP] element [SEP](128 tokens) for stage 1's top 400 and re-scores them. About 185 ms with eight WebAssembly threads. - The agent receives the top 50.
The goal is the task plus up to three previous actions: goal + " Previous actions: " + a; b; c.
Use
In a web page
import * as ort from "onnxruntime-web"; // 1.30.0, WebAssembly backend
import * as spotlit from "./spotlit.js"; // from web/ in this repository
const ranker = await spotlit.loadPacked(ort, "spotlit-v1.weights.gz");
const top50 = await ranker.rank(goal, spotlit.collectCandidates(document), {
k: 50, history: lastThreeActions,
});
loadPacked checks every part against its sha256. Keep the INT8 re-ranker on the
WebAssembly backend: in ONNX Runtime Web 1.30 it scores wrongly on WebGPU. Threads need a
cross-origin isolated page (COOP and COEP headers); without them a page takes about a second.
Stage 2 in Python
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
repo = "Sardori/spotlit-v1"
tokenizer = AutoTokenizer.from_pretrained(repo, subfolder="reranker")
model = AutoModelForSequenceClassification.from_pretrained(repo, subfolder="reranker").eval()
goal = "Add the cheapest tent to the cart"
elements = [
"button Add to cart | Trail 1-person tent $129.00 Add to cart",
"a Returns policy | Shipping Returns policy Contact us",
]
batch = tokenizer([goal] * len(elements), elements, padding=True, truncation=True,
max_length=128, return_tensors="pt")
with torch.no_grad():
scores = model(**batch).logits[:, 0] # higher is more relevant
Element lines must follow the format above. Stage 2 alone re-scores whatever candidates it is given; the released system re-scores stage 1's top 400.
Stage 1 outside the browser
The table is only useful with its encoder, which turns text into rows. stage1/config.json
specifies it: byte 3, 4 and 5-grams of normalized text, hashed with 64-bit FNV-1a into 6,144
signed buckets. web/tinylab.js implements it in about 100 lines of dependency-free
JavaScript, and the GitHub repository has the Python and C++ versions.
Evaluation
Mind2Web, share of steps whose clicked element is in the top k. The test splits were run once, after the files were frozen.
| Split | Steps | Keyword search (BM25) r@50 | Stage 1 r@50 | r@10 | r@50 | r@100 | Fewer tokens, top 50 |
|---|---|---|---|---|---|---|---|
| Dev (held-out training websites) | 1,106 | 38.2% | 55.2% | 49.3% | 73.1% | 80.1% | 46x |
| Test, cross-task | 2,094 | 44.2% | 69.2% | 57.2% | 80.8% | 85.8% | 43x |
| Test, cross-website | 1,373 | 43.8% | 56.7% | 48.7% | 75.8% | 82.3% | 34x |
| Test, cross-domain | 5,911 | 44.5% | 57.7% | 51.5% | 75.9% | 83.6% | 32x |
For reference, MindAct's DeBERTa-v3-base ranker (the benchmark authors', 184M parameters with its embedding table) reports 88.9, 85.3 and 85.7% r@50 on the test splits. Candidate descriptions differ, and steps whose clicked element is missing from the page HTML (4 to 7%) count as misses here, so the comparison is approximate.
Inside Claude Code (Sonnet), on seven multi-page browser tasks, an agent reading spotlit's top 50 used 427,787 input tokens against 2,700,384 when reading every element, and both versions finished all seven tasks.
Limits
- About one step in four loses the clicked element from the top 50. An agent should be able to ask for more elements or fall back to the full page.
- Trained on English websites from 2023; other languages and very different page structures are untested.
- The reference element collector does not look inside iframes or shadow DOM.
- Token counts are estimates at four characters per token.
Training
Mind2Web's train split only: 6,669 steps for training and 1,106 steps from held-out websites
for development. The test splits were used for evaluation only. Stage 1 started from a tinylab text-search table distilled
from BAAI/bge-small-en-v1.5 and was trained with a softmax over each page's candidates for
three epochs. Stage 2 is google/bert_uncased_L-2_H-128_A-2 fine-tuned as a cross-encoder on
the clicked element and 31 others from stage 1's top 400, for three epochs. INT8 export changed
development r@50 by +0.6 points.
License
The model files are Apache-2.0 (LICENSE, NOTICE). The runtime in web/ is MIT
(web/LICENSE). spotlit was fine-tuned from Google's BERT-tiny (Apache-2.0) and trained on
Mind2Web (CC BY 4.0); NOTICE lists every source.
Citation
@misc{spotlit2026,
title = {spotlit: an in-browser element ranker for web agents},
author = {Madaminov, Sardor},
year = {2026},
month = {October},
url = {https://github.com/Sardor-M/spotlit}
}
References
- Deng et al. Mind2Web: Towards a Generalist Agent for the Web. NeurIPS 2023.
- Weinberger et al. Feature Hashing for Large Scale Multitask Learning. ICML 2009.
- Turc et al. Well-Read Students Learn Better: On the Importance of Pre-training Compact Models. 2019.
- Downloads last month
- -
Model tree for Sardori/spotlit-v1
Base model
google/bert_uncased_L-2_H-128_A-2