npx @reticlehq/server serve --http --drive <preview-url> beside each preview, and POSTs to /verify after every generate or edit, gating the ship on run.verdict.status === 'pass' and feeding run.repair.failurePackets[] back to a fixer agent.
The one guide for adopting Reticle, both for a team using a coding agent on its own app and for an AI app-builder platform (Lovable / Emergent / Bolt) embedding Reticle in its generation pipeline. Reticle reads the program from inside a running app and returns a verdict with evidence (“did it actually work?”), not a screenshot. Enterprise/premium access lives in enterprise.md.
The loop
One call replays the app’s key journeys and asserts program truth (network cardinality, store/state, emitted signals, console), then returns a deterministic, un-hallucinatable verdict.Quickstart
A. A team, agent on your own app (~10 min)
npx skills add reticlehq/reticle --skill reticle. The agent runs reticle init in your app (build plugin, SDK connect file, agent rules), then proves one flow. No agent? Run reticle init in the app yourself.
B. A platform / CI, driven from your pipeline (no MCP, no human)
reticle verify <preview-url> drives the preview, replays the saved flows, prints the verdict, and exits non-zero on fail (ideal for a CI step).
In-app SDK integration: the effort, by layer
Reticle embeds a dev/preview-only SDK (@reticlehq/browser, Apache-2.0, tree-shaken from production). For a platform you add this once to your generated-app template → every generated app is verifiable.
Copyable patterns:
apps/bench-app/src/reticle-dev.ts, apps/next-smoke/app/reticle-dev.tsx. Without instrumentation, Layer-1 checks still work via the driven browser; Layers 2 and 3 are what no out-of-page tool can see.
What it catches that a screenshot can’t
Proven in CI:
server/src/judgement/runs/generated-app-bugs.test.ts.
The minimal attach contract (for a harness or MCP host)
Reticle is a stdio MCP server. A host that wires it correctly does four things, and each one is a step Reticle can see:- Spawn it.
npx @reticlehq/server mcp, over stdio, one process per host. That process is the proxy: it starts the local daemon on demand, reconnects on its own when the stream drops, and answers from cache while a daemon comes back. - Complete
initialize. Reticle answers the handshake itself when the daemon is slow, so a host that waits for a reply gets one either way. - Send
tools/list, and send it again onnotifications/tools/list_changed. This is the step hosts skip. Registering a server is not the same as enumerating it: a host can register Reticle and never ask for the tool list until the client is restarted, and the model then has no Reticle tools at all while the install looks healthy from every other angle. Reticle declarestools.listChangedso a host that honours the notification recovers without a restart. - Call the tools.
tools/call, with the tool name and its arguments.
reticle status reports how far a host has got. mcpClient: "never-attached" means no client has started the server on this port. mcpClient: "never-enumerated" means one started it and never asked for the tool list, which is the state a client restart fixes. mcpClient: "enumerated" means the client side is done, and anything still missing is the app rather than the host. The record is per port rather than per client, and it begins the first time a client starts a Reticle version that keeps it, so status says both of those out loud instead of naming a client it cannot see.
Hosts that cap the number of tools
Some editors cap the total tool count across every MCP server, which is why Reticle advertises a small surface instead of everything it can do. Nothing is out of reach:reticle_tools { names: [...] } lists the tools and returns the full argument grammar for the ones you name, and reticle_run { tool, args } invokes any of them, advertised or not. A host that is short on tool slots pays for the advertised handful and still reaches the whole surface through that pair. See reticle_tools and reticle_run.
Exact steps per platform
The shape is identical (in-app SDK in the template → verify in the sandbox → act on the verdict); the specifics differ by where each platform runs the preview.Emergent (Kubernetes pod per build, reverse-proxied preview URL)
- Add
@reticlehq/browser+registerStore/reticle.signalto the generated-app scaffold (one time). - In the build pod, alongside the preview:
reticle serve --http --http-token "$POD_TOKEN" --drive "$PREVIEW_URL"(or importReticleRunnerin-process). - In the orchestrator’s generate→test→iterate loop,
POST /verifyafter the preview boots. - FAIL → route
repair.failurePackets[].suggestedPromptto the fixer subagent → re-verify (closes the loop). PASS → publish + attach theprod-previewrun as the user-facing “verified ✓”.
Lovable (Vite/React generated apps, hosted preview)
- Add the Reticle Vite plugin + dev-only
reticle.connectto the project template (Lovable already templates Vite/React, so it’s one plugin line + the connect file). - Run
npx @reticlehq/server serve --http --drive <preview-url>against the preview build in the generation worker. - Call
/verifyafter each generate/edit; gate the “your app is ready” signal onverdict.status === 'pass'; feed repair packets back into the edit agent.
Bolt.new / StackBlitz (WebContainer, in-browser runtime)
- Add the SDK to the WebContainer app template; the app + Reticle bridge run in the WebContainer.
- Since the runtime is in-browser, drive via the connected session (the SDK dials the bridge) rather than
--drive; call verify from the Bolt agent after a build. - Same act-on-verdict: gate + self-heal with the repair packets. (Bolt already detects terminal/compile errors; Reticle adds the runtime program-truth layer it’s blind to.)
Honest note: a platform can build a verification step itself. Reticle’s case is the depth (program-state and source mapping), the determinism (no LLM in the loop), the un-hallucinatable verdict, and a stable drop-in artifact. The reproducible benchmark in bench/ measures the observation-cost and detection differences against other browser-automation MCPs.
The verdict artifact
POST /verify (and reticle_run_export) return a stable, versioned ReticleVerificationRun (defined in @reticlehq/core): verdict (pass/fail/partial, confidence, blockingRisks), flows[], checks[], risks[] (auth/payment/db/…), repair.failurePackets[] (what + where to fix), evidence. Render a legible report with renderRunReport() or reticle_run({ tool: "reticle_run_export", args: { format: "report" } }). Profiles: dev (full) and prod-preview (source and state redacted for sharing). POST /verify uses prod-preview unless you pass profile: "dev".
Why trust it: the verdict is mechanical, derived only from observed outcomes, so it can’t report green for something it never ran (a severed backend reads as fail, never a confident pass). Proof: server/src/judgement/runs/false-green.test.ts.
Building a hosted verifier on the engine
A service that drives apps it did not build uses these seams.- Follow a drive to another origin.
new Bridge({ ..., followNavigation })lets a session continue on an origin the bridge was not built with. A page on a new origin can only continue a session the bridge already admitted, after the token or nonce check, and only whenfollowNavigation({ sessionId, from, to, url })returnstrue. Returntrueonly when your own browser’s top frame is now onto. Without the option, nothing changes. - Refuse
cleanover what the engine could not see. Everyreticle_act_and_waitandreticle_assertresult carriesengineBlindSpots: what this engine could not observe in the session so far. It is cumulative, so the last verdict result of a run is the run’s list, and[]means none.pixels-unobserved: an action ran while a<canvas>was visible, and no canvas digest is recorded.claim-arithmetic-unchecked: a claim under test (the call’sintent, or an intent declared in the run) states an amount, and no verdict ran acompare. Do not call a run clean while its list has an entry. The kinds and the schema areEngineBlindSpotKindandEngineBlindSpotSchemain@reticlehq/core. - Say that you injected the SDK. When your init script connects the SDK into a page that does not import it, call
reticle.connect({ ..., injected: true }). The HELLO then carriesinstrumentation: "injected",Session.instrumentationholds it, and the session listing shows it. Do not count such a session as an app with the SDK installed: signals, module-scope stores and file:line still need the app. To read React component state and provider-passed stores (Redux, TanStack Query) on such a page, bundleinstall()from@reticlehq/reactinto the same init script and call it beforeconnect. That entry imports only@reticlehq/browserand@reticlehq/coreat runtime, so the bundle holds no copy of React.
Licensing for embedding
Every Reticle package is Apache-2.0, so you can ship the SDK in your customers’ apps. Hosted and team features:enterprise.md. OEM terms: hey@reticle.sh.