Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,263 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reticle



Your AI agent says "Feature complete." Then you open the app:

✗  a silent 500 under a page that looks perfect   ✗  a flow that used to work, now broken   ✗  mock data where the real API should be

Reticle is a proofreader for AI-written code.

Your agent writes code. Reticle checks it against the real running app — the network calls, the store, the console, the things a screenshot can never show — and hands back pass, fail, or "couldn't tell", with the file:line to fix. The agent fixes and retries until it passes, before you ever open the app.

Reticle in action: an AI agent verifying a real running app from the inside, returning pass/fail verdicts with evidence and the file:line to fix

npm downloads stars license OpenSSF Scorecard types Discord

Where the work happens in the open: what's being built this cycle, what's up for grabs, and design calls before they land.

⚡ Install in 30 seconds · How it works · What we support · vs Playwright / DevTools · The numbers · Docs · Discord

dev-only · localhost-only · your app data stays local · Apache-2.0 SDK · works with Claude Code, Cursor, and any MCP agent

Web and desktop — React, Vue, Svelte, Preact, Astro, plain HTML · Electron & Tauri · macOS, Linux, Windows


The problem

Your agent writes code, assumes it worked, and moves on. It never opens the app.

So the broken modal, the silent 500, the "Deploy succeeded" over a deploy that failed — they all ship, and you find them by clicking around afterwards. You've become your agent's QA.

The maddening part: the truth was right there in the running app the whole time. The failed response, the store that still says 0, the error in the console. It just never reaches the screen — so a screenshot shows a page that looks perfect, and your agent sees nothing at all.

An e-commerce page looks perfectly shipped, but underneath: mock data, a dead click, a hidden 500 — the failures the UI completely hid.

What Reticle does

It proofreads your agent's work, on the running app, before you ever see it.

your agent writes code  →  Reticle checks the app it produced  →  verdict goes back to the agent
        ↑                                                                      │
        └──────────────────  it fixes and tries again, until it passes  ───────┘

That's the whole idea. The agent stops guessing that it worked, and gets told — with the evidence, and the file:line to fix.

It works because Reticle runs inside the app rather than looking at it from outside. It can see what the page never displays: the network response behind the click, the value in your store, the signal your code fired, the error in the console. Then it answers one question — did the thing you claimed actually happen? — and hands back yes, no, or an honest "I couldn't tell."

You never write test syntax. You say what should be true in plain English; the agent does the rest.

Install in 30 seconds

Easiest — paste one line into your agent:

Follow https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md

It auto-detects whether Reticle is set up, runs the wizard the first time, and verifies your app every time after. Works with Claude Code, Cursor, OpenCode, and any MCP agent.

Or via CLI — auto-detects your framework, installs the kit + build plugin, and registers the MCP server for every agent in one shot:

npx @reticlehq/server init

Or register the MCP server directly in Claude Code (then restart it):

claude mcp add reticle -s user -- npx @reticlehq/server mcp
Manual setup — install + wire it yourself

1. Install the SDK kit + your framework's build plugin (the kit re-exports the browser sensor):

npm i -D @reticlehq/react @reticlehq/vite-plugin        # Vite; or pnpm / yarn / bun
# Next.js instead? npm i -D @reticlehq/react @reticlehq/next

2. Register the MCP servernpx @reticlehq/server mcp is the server:

// .mcp.json
{ "mcpServers": { "reticle": { "command": "npx", "args": ["@reticlehq/server", "mcp"] } } }

3. Connect the dev-only SDK from your app entry (tree-shaken out of production):

// main.tsx — dev only
import { reticle } from '@reticlehq/react';
if (import.meta.env.DEV) reticle.connect({ session: 'my-app' });
// React? add `import { install } from "@reticlehq/react"; install()` before connect for component → file:line.

Full walkthrough → Getting Started.

How it works

You: "Verify login works: it should call /api/login, land on the dashboard, and set the signed-in user."

Agent, via Reticle: clicks Sign inPOST /api/login → 200 (14 ms) → dashboard rendered → store now holds auth: { email: "admin@…" }✅ PASS, with that evidence attached. Had it failed, you'd get the failing check and the file:line instead of a guess.

Say "save that as a flow" and it replays on every later edit — no model, no flake — so today's fix can't quietly break last week's feature.

Your agent says 'Fixed it.' It wasn't — POST /api/order returned 500. The agent never opened the app; Reticle does.

What that looks like underneath (one call, ~33 tokens, no screenshot)
// The agent clicked "Pay". Did the right things actually happen?
reticle_assert({
  predicate: { allOf: [
    { kind: "net",     method: "POST", urlContains: "/api/order", status: 200 },
    { kind: "element", query: { role: "dialog", name: "Order confirmed" }, state: "visible" },
    { kind: "signal",  name: "order:saved" },          // the charge actually committed
    { kind: "console", level: "error", absent: true }  // …and nothing errored
  ]}
})
// → { pass: false,
//     failureReason: "POST /api/order returned 500, expected 200",
//     source: { file: "src/checkout/PayButton.tsx", line: 42 } }   ← caught before you ever saw it
flowchart LR
    A["Your AI agent<br/>(Claude Code, Cursor…)"] -->|"look · act · observe · assert"| B(("Reticle"))
    B <-->|"structured events,<br/>not pixels"| C["Your real running app<br/>DOM · network · console<br/>store · React fiber"]
    B -->|"verdict + evidence<br/>+ file:line to fix"| A
    style B fill:#8b7bff,stroke:#5b4bd0,color:#fff
    style A fill:#15131f,stroke:#3a3550,color:#fff
    style C fill:#1c2433,stroke:#2f3d57,color:#fff
Loading

One call checks many things at once and comes back with proof — deterministic (structured events, not a vision model), cheap (any model, no screenshot), and pointed at the code. Record that journey once and Reticle replays it deterministically on every later edit: no model, 0% flake, ~47 tokens for a whole suite — a regression net that runs inside the agent's loop instead of waiting for CI.

"Can't Playwright / DevTools / a browser agent already do this?"

Fair question, and the honest answer is: they all stand outside the browser looking in. For a site you don't own, that's exactly right. For the app you're building, it's the wrong side of the glass — the bugs that matter never reach the pixels or the DOM.

Tool What it sees What it misses on the app you own
Screenshot / browser agent pixels the silent 500, the wrong store value, the double-submit, the render storm — none reach the screen
Playwright MCP / DevTools MCP the DOM + raw CDP app state, custom signals, the React commit stream — and no file:line to hand back
Reticle the program: network, store state, signals, console, React fiber (built for apps you own — it can't test a site you don't ship; that's Playwright's job)

Concretely — every one of these looks fine on screen, and only Reticle catches it:

The bug Reticle catches it because it reads…
Pay button silently returns 500 the network response, tied to the click
A console error slipped in, UI still renders the console stream since the action
The form fired the request twice request cardinality (net { count: 1 })
The badge shows "12" but the store holds 0 the app's state, not the rendered number
"Deploy succeeded" — the deploy actually failed the store's real status
The component re-renders 60×/sec for nothing the React commit stream

Use both. Playwright is the right tool for a site you don't own, many browsers, or true pixels. Reticle is your cheap, deterministic, state-aware inner loop while the agent codes. Full when-to-use-which in the docs.

Your coding agent isn't built for this

Not a criticism — it's the job description. A coding agent is optimised to produce a change: read the code, reason about it, write the edit. Its feedback loop closes on the code it just wrote.

Verification is the opposite motion. It means going and finding out whether the change did what it claimed, in the running app, and being willing to come back with no. A builder is optimistic by construction — that is what makes it good at building, and it is exactly why it says "Feature complete" and moves on.

So the gap isn't something your agent forgot. It's a different job, and nothing in the write-code loop does it. Reticle is that second motion: it opens the app, checks the claim against what actually happened, and hands the answer back — so the optimism gets checked before it reaches you.

The numbers

We injected 88 real regressions into a controlled app and ran Reticle head-to-head against a Playwright script. Every number is produced by a committed harness — reproduce it with pnpm bench.

Bugs caught by category: Reticle 86/86, Playwright 60/86. Reticle catches all 8 state bugs, 6 business-logic, 4 signal and 4 net-status bugs where Playwright catches 0, 0, 0 and 1. On the other 13 categories both catch everything.

Reticle Playwright (script)
Critical bugs caught (silent 500s, wrong data, bad state) 26 / 26 9 / 26
All injected bugs caught 86 / 88 60 / 88
False alarms on a clean build 0 0
Reads app state / signals / React commits ✗ — DOM only
Hands back the file:line to fix
Regression replay 0% flake · no model · ~47 tok/suite re-drive with the LLM

The gap is widest exactly where it hurts: 26 vs 9 on the bugs that corrupt data or hide a failure. And the file:line isn't cosmetic — in our ablation it cut an agent's fix-loop tool calls by 45%.

The proof that mattered most: before we instrumented anything, Reticle's first pass on our own production dashboard flagged two live 500s (GET /projects, /recovery/incidents) that the UI completely hid. The page looked perfect. A screenshot would have called it done.

Full scorecard, including where we lose · Confidence, claim by claim · What Reticle catches that Playwright can't, and why

What it catches, what it doesn't, and what it costs

A verification tool that oversells its reach is worse than none, so here are the edges — including the ones we lose.

Where it fits, and where it doesn't

Bug class Reticle Why
Silent failed request under a healthy-looking UI strong it reads the response, tied to the click
State that disagrees with the screen strong it reads the store, not the rendered number
Stale cache — UI showing data the server has since changed strong a stale-cache bug fires no network request; outside-in tools see silence and call it healthy. The TanStack Query adapter reads the cache itself
Double-submit / retry storm strong request cardinality (net { count: 1 })
A write that failed while the UI moved on strong this is the contradiction detector's core case
Races around one action partial it detects request-never-settled and duplicate-request within an action's window; it is not a scheduler-level race analyser
Event-sourced write conflicts, cross-tab consistency weak these live in your backend's ordering, not in the browser. Reticle can tell you the client's story disagrees with itself; it can't referee two writers
Cross-browser rendering, visual regressions on a site you don't own not the tool that's Playwright

What it can and can't observe

Observed: DOM, network (including WebSocket and SSE frames), console, routing, localStorage / sessionStorage / cookies, app state, custom signals, React commits, and Electron/Tauri IPC.

Not observed today: IndexedDB, Web Workers, and anything inside a closed shadow root or a cross-origin iframe.

The part that matters more than the list: when Reticle can't see something, it says so. A result carries coverage: partial and names the reason — a closed shadow root, a cross-origin frame, a fetch someone wrapped before we did, events dropped because the app out-ran the sampling cap. And a verdict is yes / no / unknown — where unknown means "the evidence couldn't decide", never a quiet pass. You should trust it exactly as far as it claims, which is the point.

What it costs

Production bundle Zero. The SDK is imported behind import.meta.env.DEV and dead-code-eliminated from prod builds; a runtime guard refuses to connect under NODE_ENV=production as defence in depth
Dev bundle a dev dependency, like your test runner — it never reaches users
Runtime observers coalesce aggressively — rendering 5,000 list rows produced 41 events, not 5,000
Memory a bounded ring buffer (2,000 events / 60 s), plus a capped ref registry — both fixed ceilings, not growth with app lifetime
Network localhost WebSocket to a daemon on your machine. No app data leaves the machine

What we support — frameworks, desktop, platforms

The short answer: if it renders in a browser, Reticle observes it — web and desktop, on macOS, Linux and Windows. Web apps (React, Vue, Svelte, Preact, Astro, plain HTML) and desktop apps (Electron and Tauri) are all supported, and reticle init wires most of them unattended. The tables below say exactly how far that goes for each one.

The SDK observes the DOM, network, console, routing, storage and animations of anything that renders in a browser — that part is framework-agnostic and needs no adapter. What varies is the layer above it: whether reticle init wires your project unattended, and whether a DOM node can be traced back to the component and source line that rendered it.

Verified means a gate scaffolds that stack from scratch, installs Reticle into it, boots it, opens a real browser and waits for a session — every release. Not "it should work".

Framework Install (reticle init) Source → component mapping Verified every release
Next.js (App Router) auto ✅ via @reticlehq/next (keeps SWC) ✅ install gate + e2e
Next.js (Pages Router) auto ✅ install gate
Vite + React auto ✅ via @reticlehq/vite-plugin ✅ install gate + e2e
Monorepo sub-package auto ✅ install gate
Create React App auto (no build plugin — the kit alone) detection only
SvelteKit auto detection only
Astro auto detection only
Preact auto — init flags the stack UNVERIFIED ❌ — #129
Vue 3 auto — init flags the stack UNVERIFIED ❌ — #76
Svelte (standalone) auto
Angular not detected ❌ — #128
Plain HTML / anything else manual (connect())

A ❌ in "source mapping" is not a broken install. Vue, Preact, Svelte, Astro, SvelteKit and CRA apps are wired by init, connect normally, and every tool works — refs, roles, test-ids, network, console, storage, state, animations, assertions and verdicts. What you don't get is reticle_query answering "which component rendered this, and in which file", because that needs a build plugin that stamps data-reticle-source. On the unverified stacks init says so on the spot rather than reporting an unqualified green.

Desktop apps — yes, Electron and Tauri

Both are fully supported, not experimental: Reticle observes the renderer and the main-process/Rust IPC boundary that a browser-only tool cannot see at all.

Desktop runtime What Reticle observes Verified every release
Electron @reticlehq/electron — renderer + main-process IPC, window capture ✅ a real Electron main process, driven headless
Tauri packages/tauri (Rust) — webview + IPC, window capture ✅ a packaged binary, driven headless

Both are driven by pnpm test:e2e:desktop; the Rust side is compiled by CI's rust / rust-macos jobs. See Desktop apps.

Operating systems

macOS, Linux and Windows are all supported and all first-class. CI runs a dedicated windows job on every commit.

Missing yours? #128 (Angular) and #76 (Vue) are open and marked help wanted.

Does it work with my state library?

registerStore duck-types on { getState, subscribe }, so zustand and Redux work with no adapter at all. Shipped adapters cover TanStack Query, Jotai, XState, Valtio, MobX, Recoil, Svelte stores and Pinia, and a generic pushStore handles Context or anything hand-rolled — you push, Reticle reads. None of the adapters import their library, so they add no dependency and no weight for an app that doesn't use them.

Missing yours? It's ~9 lines — and a genuinely good first PR. An adapter is a pure function returning { getState, subscribe }; because it takes an already-constructed store and uses structural types, it never imports the library it supports. Here is the whole Valtio one:

export function valtioStore<T extends object>(
  proxy: T,
  snapshot: (p: T) => unknown,
  subscribe: (p: T, listener: () => void) => () => void,
): StoreLike {
  return {
    getState: () => snapshot(proxy),
    subscribe: (listener) => subscribe(proxy, listener),
  };
}

Effector, Nanostores, Signals — whatever you use, add it in store-adapters.ts with a test beside it and open a PR — see CONTRIBUTING.md, or say hello in #contributors first. Until then the generic pushStore path covers you.

Go deeper

Getting Started · Full guide: every tool, predicate & the flow DSL · One browser, a fleet of agents in parallel · Desktop apps (Electron & Tauri)


If Reticle proves useful, a ⭐ helps other developers find it.

Built in the open, for the long run. Everyone who stars, forks, or contributes is credited below.

Contributors

💬 Community

Reticle is built in the open. Pick the channel that fits:

You want to… Go here
See what's being built now, ask a question, claim work Discord#roadmap, #help, #contributors
Report a bug or request a feature Open an issue
Find something to work on good first issue · help wanted
Know where the project is headed ROADMAP · what shipped · how we release
Send a change CONTRIBUTING.md
Report a vulnerability, or reach the team privately SECURITY.md · hey@reticle.sh

What's inside

A pnpm + turbo monorepo — each audience installs only what it needs (apps embed @reticlehq/react; agents run @reticlehq/server):

Package Role
@reticlehq/core the wire contract (types, zod schemas, constants) everything imports — depends only on zod
@reticlehq/browser the dev-only instrumentation SDK (DOM / network / console / state observers)
@reticlehq/react the React kit: SDK + adapter, DOM ref → component → source file:line
@reticlehq/vite-plugin · -next · -babel-plugin dev-only source mapping + connect() injection (Vite / Next.js / React 19)
@reticlehq/server the bridge + MCP server + the reticle CLI
@reticlehq/test · -eslint-plugin declarative CI specs · the "state change must fire a signal" lint rule

Status & safety

Dev-only and localhost-only by design: the SDK is tree-shaken out of production builds, the bridge binds to localhost, and no app data ever leaves your machine — Reticle observes your app on your machine. The CLI reports anonymous, opt-out usage metrics only (a random id + event names; no code, no PII — full policy); opt out with reticle telemetry disable. The one exception is feedback you or your agent deliberately send us (reticle feedback / reticle_feedback) — never collected passively, redacted before it is sent, and separately disabled with RETICLE_FEEDBACK=0.

License

A per-package model, so it's safe to embed in your app and fair to build a business on (each package's LICENSE is authoritative; see the root LICENSE):

  • Embedded in your app → Apache-2.0. core, browser, react, next, vite-plugin, babel-plugin, eslint-plugin compile into your application. Use them anywhere, including apps you ship to customers. No copyleft; explicit patent grant.
  • Server / CLI / MCP → FSL-1.1-ALv2. server and test are free for any use except offering Reticle itself as a competing hosted service; each release converts to Apache-2.0 after two years.
  • Enterprise features → Reticle Enterprise License. Source-available under packages/server/src/ee/; free to evaluate, a key is required in production.

New here? See CONTRIBUTING.md, GOVERNANCE.md, RELEASING.md, and the ROADMAP. Contributions are certified under the DCO — just git commit -s. OEM / commercial licensing: hey@reticle.sh

© 2026 Reticle HQ · Install · Docs · Benchmarks · reticle.sh

About

AI agents can generate code, but they still struggle to understand what they build. Reticle gives them runtime perception of web applications.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

288 stars

Watchers

4 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages