The resolution ladder
August 26, 2026 ยท View on GitHub
What each compiled step carries
Each compiled step carries a template crop, an OCR label, geometry landmarks, a structural locator, and postconditions derived from what the demo actually changed on screen. At replay time a resolution ladder tries them in order: a structural element match where the backend owns a DOM/UIA tree, then local template match, global template match, OCR, landmark geometry, then (optionally) a grounding model. Healthy scripts normally resolve on the first rung. Individual deterministic resolution steps complete in milliseconds; end-to-end workflow time depends on the target application. The healthy path makes no model calls and incurs no per-run model cost.
What happens under drift
When bounded UI drift preserves enough evidence, a lower rung can find the same
target and the fix lands in the bundle as a diff you can review. An optional
model may propose a repair only when explicitly enabled, and a human can teach
a guarded correction after a halt (openadapt-flow teach). These are different
modes, not a blanket promise of adaptation. When the screen stops matching
expectations entirely, the run halts with a report instead of guessing, and
steps tagged irreversible will not act on a low-confidence match at all.
Vision-first, not vision-limited
The runtime is vision-first: it can operate a pure pixel surface
(PNG in, clicks and keys out), but it is not limited to pixels. Where a backend
owns a structured layer, a browser DOM or a native UI Automation / accessibility
tree, the ladder's top rung re-finds the recorded target as an element and
acts on it deterministically; the visual rungs are the fallback floor for
pixel-only substrates (RDP, Citrix, canvas). On a desktop drift benchmark the
structural rung resolved 21/21 targets where visual replay alone managed 6/21
(../benchmark/structural_action/STRUCTURAL_ACTION.md).
Structure never bypasses the identity gate; it makes identity stronger, an
exact element rather than a pixel guess. But the identity gate only covers
armed steps, and today's bundles arm a subset of clicks (the live OpenEMR
bundle armed 4-7 of 12), so an unarmed click has no identity check at all.
The per-step coverage is auditable in workflow.json and reported in every run;
see what it doesn't do yet.
Related: SURFACES.md for which substrate owns a structured
layer, and VISUALIZE.md to see the ladder a bundle will try
before it runs.