Flows, the recorder & self-healing — record once, run forever
Reticle turns an interactive run into a git-checked, replayable program stored under.reticle/. Flows are anchored on meaning (testid + signal), not volatile element refs or coordinates, so they survive refactors — and when an anchor does drift, Reticle tells you why and can repair it. This is what makes Reticle “the project’s living test suite a human seeds and an agent maintains.”
All of this is on-disk and human-readable, so flows are reviewed in PRs and diffed like code.
The .reticle/ directory
When you record or save a contract, Reticle writes a git-checked workspace next to your app:
.reticle/ from the working directory it runs in (your project root). A fresh agent can read .reticle/contract.json to learn the testable surface without grepping your source.
The contract — advertise the testable surface
In your app, declare what’s testable (see Step 6 in Getting Started):Create a flow
(a) Agent-recorded — the agent drives, then saves:present: true), the floating panel hosts a recorder: a human clicks the golden path in the page and Reticle captures each interaction as a semantic-anchored step (testid, else role+name), then persists it via reticle_flow_save_recorded. The agent then runs and maintains it. (First cut: structured annotations only — see below; free natural-language annotations are future work.)
What a flow file looks like
eXX ref: a testid/signal when available, else an auto-derived component anchor (component name + source file:line) for an element with no testid — so the flow stays stable with zero hand-added testids. Only when none of those resolve is a step kept degraded: true (a last-resort “add a testid here” marker) rather than silently dropped.
Run a flow
present: true), a replay isn’t silent — each step drives the real page, so the synthetic cursor flies to the element, the focus ring lands, and the activity log streams the journey live. You (or a teammate) literally watch the saved journey re-walk itself on your app, then see the verdict land. It’s the fastest way to see that a flow still works — not just read a green checkmark.
reticle_flow_replay returns a status:
ok— every anchor resolved and everyexpectheld.drift— an anchor missed (a testid was renamed, or a signal never fired). The result is legible:{ step, anchor, drift: { reasonKind: "testid_not_found", nearest: "send-message" } }— never a blind failure. (This is the “whose fault is it” principle.)error— the flow file is missing/invalid, or a resolved action failed. Runtime failures include the failed step and a top-level error envelope.
{ kind: "component", component, source: { file, line } }) — an auto-derived stable anchor, so a flow records cleanly with zero hand-added testids and replay re-resolves it via reticle_query by:'component'.
The decision envelope — what to do next, not just pass/fail
On adrift or error, the replay result carries a decision an agent can act on directly:
Verify the whole suite in one call
reticle_flow_verify replays every saved flow (or a named subset) deterministically — no LLM per flow — and returns one consolidated verdict. This is the regression check to run after any change:
reticle_flow_verify → fix from each failure’s nextAction → repeat — the autonomous regression loop.
Self-healing — the agent maintains the flow
When a testid is renamed, the flow drifts.reticle_flow_heal proposes — and optionally applies — the nearest-match rebind, so flows don’t rot:
apply: false the flow file is never modified — you get the proposed diff to review. With apply: true Reticle rewrites the drifted anchor(s) to the confident nearest match and a subsequent replay passes. A drift with no confident nearest match leaves the file untouched.
Annotations (structured)
reticle_annotate attaches a structured annotation that compiles into the flow, so replay is a checked re-run, not a blind macro:
assert-signal/assert-visible→ a stepexpectpredicate (the invariant).mark-dynamic→ aflow.dynamic[]entry — replay asserts the region’s presence but not its words (the LLM-output case: assertcaption:generated, ignore the caption text).success-state→flow.success(the golden end condition). Passsignal/testid, orstatePath(+store,equals) to make the golden condition a store-truth assertion — the app’s own source of truth, which no DOM read can reach (e.g.statePath: "deployments.0.status", equals: "live"fails the flow if a deploy only looks shipped on screen). State assertions are graded as consequences, so they satisfy the business-outcome oracle.
Flows are your test suite
.reticle/ flows can be executed as CI specs — replayed with their expect/success predicates, skipping dynamic regions — via @reticlehq/test’s flowsAsSpecs. See Testing with Reticle.
Tool reference
Flownamemust be a single safe path segment (no/,\,.., or leading dot).