A flow is a recorded interactive run that Reticle stores as JSON under .reticle/flows/ and replays forever with no AI model in the loop. Steps are anchored on meaning (a testid, a signal, or an auto-derived component + source location), never on a eXX ref or a coordinate, so they survive refactors; when an anchor does drift, reticle_flow_replay names what changed and reticle_verify { action: "heal" } can rebind it.
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:
The server resolves .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):
Then persist it to disk so it’s committed and any agent can read it:
Create a flow
(a) Agent-recorded. The agent drives, then saves:
(b) Human-recorded (the recorder toolbar). With the presenter on (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
Each step binds to a semantic anchor, never a 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
Delete a flow
A renamed or obsolete flow otherwise lingers in reticle_flow {action:"list"} and in every reticle_verify {action:"flows"} suite run, where it fails forever against a screen nobody intends to keep.
Deleting a flow that is not there is an error, not a no-op. It answers { error, code: "not_found" } rather than { deleted: true }, so a mistyped name cannot read as a completed cleanup while the real flow stays in the suite. Check the spelling against reticle_flow {action:"list"} and try again.
Watch it replay on the page. When the presenter is on (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 every expect held.
drift: an anchor missed (a testid was renamed, or a signal never fired). The result is legible, never a blind failure: { step, anchor, drift: { reasonKind: "testid_not_found", nearest: "send-message" } }. (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.
A testid-preserving refactor (you moved markup but kept the testids) still replays green. A step whose element has no testid is anchored on its component + source location ({ 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 a drift or error, the replay result carries a decision an agent can act on directly:
This is the feedback a human reviewer used to give, made machine-actionable, so the agent decides its next move without one.
Verify the whole suite in one call
reticle_verify {action:"flows"} replays every saved flow (or a named subset) deterministically, with no LLM per flow, and returns one consolidated verdict. This is the regression check to run after any change:
Passing flows are counted; only failures carry detail (token-cheap). Build → reticle_verify {action:"flows"} → 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_verify { action: "heal" } proposes (and optionally applies) the nearest-match rebind, so flows don’t rot:
With 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 step expect predicate (the invariant).
mark-dynamic → a flow.dynamic[] entry; replay asserts the region’s presence but not its words (the LLM-output case: assert caption:generated, ignore the caption text).
success-state → flow.success (the golden end condition). Pass signal/testid, or statePath (+ store, equals) to make the golden condition a store-truth assertion against 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 via @reticlehq/test’s flowsAsSpecs, replayed with their expect/success predicates and skipping dynamic regions. See Testing with Reticle.
Flow name must be a single safe path segment (no /, \, .., or leading dot).
Last modified on September 23, 2026