Chrome Extension · AI · Edge ML · 2025
Arkhon AI
Chat with any page, fill forms, browse autonomously — powered by a local LLM running entirely in your browser. Nothing ever leaves your machine.
700MB · 100% on-device
Gemma 3 1B · GGUF
Zero cloud · Zero egress
163 tests · 9 suites
Solo
Chrome MV3
WebAssembly
OPFS
CDP Agent
CHROME BROWSER
┌────────────────────────────────────────────────────────┐
│ │
│ background.js (Service Worker) │
│ ├─ Tab lifecycle · Message router │
│ ├─ CDP agent · State cache │
│ └─ ensureModelHost() ──────────────────────────────┐ │
│ │ │
│ model-host.html (Pinned tab — survives SW kill) │ │
│ ├─ Gemma 3 1B Q4_K_M via wllama WASM │◄─┘ │
│ ├─ nomic-embed-text ONNX (~70MB) │
│ └─ BM25 retrieval · RAG · Form fill · Agent step │
│ │
│ Every web page (content.js) │
│ └─ FAB (Shadow DOM) → iframe: sidepanel/index.html │
│ React app · Chat · Fill · Agent UI │
└────────────────────────────────────────────────────────┘
Capabilities
Three modes, one local model.
01 · CHAT
Talk to any page
Read this page → extract DOM text → BM25 retrieval → stream answers from Gemma 3 1B. Works on articles, docs, YouTube transcripts, PDFs.
- One-click page indexing
- 3 AI-suggested starter questions
- 60-message per-tab history
- YouTube transcript extraction
- PDF iframe text extraction
02 · FILL
Smart form fill
Personal knowledge base embedded via nomic-embed-text ONNX. ORT cosine or BM25 retrieval maps your info to form fields — no typing.
- Detects inputs across iframes
- React-compatible (dispatches input + change)
- Multi-step paginated forms (Greenhouse/Lever)
- LLM outputs structured JSON → fills by index
- Handles select, textarea, disabled fields
03 · AGENT
Autonomous browsing
Attach Chrome DevTools Protocol to any tab. Walk the accessibility tree, score elements via ORT, execute click/fill/scroll via CDP — loop until goal is done.
- Full AX tree via Accessibility.getFullAXTree
- ORT cosine filter narrows to top-K elements
- GBNF grammar forces valid JSON actions
- Auto-reattach if DevTools open externally
- Clean CDP detach on stop / navigation
Engineering
Key flows, end to end.
Chat query
- 01 User clicks "Read this page" — content script extracts DOM text
- 02 PC_CMD_INDEX → chunks text (450 chars, 60 overlap, max 500 chunks) → BM25 store per tab
- 03 User sends message → PC_CMD_CHAT → BM25 top-k + first 2 intro chunks → cap at 8 000 chars
- 04 Gemma prompt → wllama.createCompletion({ stream: true })
- 05 Per token: PC_TOKEN → persistent port → React appends → re-render
Agent loop
- 01 User types goal → AGENT_START → background attaches CDP to active tab
- 02 GET_DOM_TREE_CDP → Accessibility.getFullAXTree → filter interactive nodes
- 03 PC_CMD_EMBED_FILTER → ORT cosine scores each DOM line → top-K returned
- 04 PC_CMD_AGENT_STEP → Gemma + GBNF grammar → JSON action (click/fill/scroll)
- 05 EXECUTE_ACTION_CDP → DOM.resolveNode → Runtime.callFunctionOn → repeat or done
Model host lifecycle
- 01 On startup/install → create pinned model-host tab (survives SW's 30s kill)
- 02 loadModels() downloads Gemma 3 1B GGUF → caches in OPFS (~700MB)
- 03 nomic-embed-text ONNX loaded lazily on first fill/agent call (~70MB)
- 04 If pinned tab closed → 25s delay (OPFS lock) → auto-recreate with 3× retry
- 05 SW restart → restore hostTabId from session storage → adopt or recreate
Form fill pipeline
- 01 User triggers fill → content script scans all input/select/textarea across all iframes
- 02 PC_CMD_FILL → ORT cosine (if embeddings ready) or BM25 over knowledge store
- 03 LLM outputs {"fieldIndex":"value"} JSON → validated
- 04 content.js fills each field by frameId + idx, dispatches input + change events
- 05 React-compatible — works on Greenhouse, Lever, Workday-style apps
Engineering depth
Hard problems solved.
Reliability
SW kill immunity
Chrome kills service workers after 30s idle. Arkhon hosts the model in a pinned tab — stays alive for the full browser session. Alarms keep the SW responsive during long agent loops.
OPFS lock handling
Closing the model-host tab while the 700MB GGUF is memory-mapped holds the OPFS SyncAccessHandle for up to 30s. Reconnect waits 25s before recreating — prevents corrupt reads.
Pin-tab deduplication
ensureModelHost() uses a promise-guard (_ensurePromise) so 20 concurrent NEEDS_HOST messages only create exactly one tab — stress-tested across 163 Playwright tests.
Performance
Two-phase FAB injection
FAB appears at opacity:0 immediately at document_end — no SW round-trip. Iframe injected after tab ID is resolved. CSP-blocked hosts fall back to Chrome's native side panel automatically.
BM25 + ORT hybrid retrieval
BM25 is always available (no model needed). ORT cosine search activates after embeddings are indexed. For agent DOM filtering: ORT scores each element against the goal, keeping context tight.
Context capping
4 096 token context window. Page text capped at 8 000 chars (~2 000 tokens) before prompting — prevents WASM heap overflow on long pages while keeping retrieval quality high.
Tech stack
Chrome MV3
Gemma 3 1B
wllama
WebAssembly
OPFS
ONNX Runtime
nomic-embed-text
BM25
React 18
Shadow DOM
Chrome DevTools Protocol
GBNF Grammar
Playwright
Service Worker