Bind the Goal, Not the Gesture
Record a person doing a task and you can replay it. But you've captured the wrong thing. You captured the clicks — the gesture — when what you wanted was the goal. The difference decides whether your automation quietly upgrades itself over time or shatters the first time a button moves.
Thesis: Durable automation stores the what and the why as immutable, and treats the how as disposable — re-resolved against the strongest available interface on every single run. An automation bound to a gesture is frozen at the weakest actuator it was born with. An automation bound to a goal hardens itself from eight UI clicks to one API call with zero edits, the day a better path appears.
This is Post 3 of the "Intent Over Instructions" series. Post 1 argued for bounding a task by outcome; Post 2 applied it to authored instructions. This post takes it all the way down — into how an automation is stored, compiled, and run.
The Macro-Recorder Mental Model Is Wrong
The instinctive way to automate a workflow is to record it. Capture the clicks, the keystrokes, the field entries, and play them back. Every macro recorder, every "record and replay" test tool, nearly every screen-scraping RPA bot works this way. And the overwhelming majority of them are brittle for the same reason: it froze the gesture instead of the goal.
A recorded gesture says: click the element at (840, 210), type into the third field, press the blue button. It's a transcript of one person's path through one interface on one day. Move the button, rename the field, ship a redesign — and the "automation" is now confidently doing the wrong thing, or nothing.
The mistake is treating the demonstration as the thing to repeat. It isn't. The demonstration is only evidence of intent. The person clicked those eight things because they were trying to file an issue. "File an issue" is the durable asset. The eight clicks are disposable — one of many possible paths to the same end.
Three Layers, One Volatile
The fix is to stop storing an automation as a flat script and start storing it as a single unit — call it an Action — split into three layers, stacked by how fast they change.
flowchart TD
subgraph action["One stored Action"]
direction TB
Intent["Intent — stable<br/>'File a bug report titled X'<br/>(never names an interface)"]
Plan["Plan — semi-stable<br/>ordered steps + parameters<br/>(still no concrete actuator)"]
Bindings["Bindings — volatile<br/>the actual endpoint / click path<br/>re-resolved every run"]
Intent --> Plan --> Bindings
end
Bindings -.->|"re-resolved against the<br/>strongest interface available<br/>THIS run"| World["UI click · CLI · REST · MCP tool"]
style Intent fill:#2d6a4f,color:#fff
style Plan fill:#5a5a8a,color:#fff
style Bindings fill:#9d4444,color:#fff
style World fill:#5a5a8a,color:#fff
- Intent is stable. "Post the weekly snippet to the team channel." It names an outcome and never names an interface. It's the layer you version, review, and trust.
- Plan is semi-stable. The ordered steps and their parameters — still described in terms of what each step accomplishes, not which button does it.
- Bindings are volatile — and this is the whole trick. The binding is the concrete actuator: this endpoint, this click path, this MCP tool. It is not stored as truth. It is re-resolved on every run against whatever interface is strongest at that moment.
Only the bottom layer changes. Intent and plan never name a concrete actuator, so they never break when the actuator does. As one design doc I worked from puts it: the volatile layer is "re-resolved every run"; the top two are "untouched the whole time."
The Self-Hardening Automation
This is where it stops being tidy architecture and starts being magic.
Say the only way to file an issue today is the web UI. The automation is born bound to eight clicks. That's fine — but the first time it runs those clicks, something clever happens: the run is instrumented. It watches the network traffic while the UI does its thing, and the real API call the UI was making all along falls out of the capture.
flowchart LR
subgraph day1["Day 1 — only the UI exists"]
I1["Intent:<br/>'file an issue'"] --> B1["Binding:<br/>8 UI clicks"]
B1 -->|"first drive is<br/>instrumented"| Cap["network capture:<br/>POST /api/v3/issue<br/>falls out"]
end
subgraph day2["Day 2 — same Action, no edits"]
I2["Intent:<br/>'file an issue'<br/>(unchanged)"] --> B2["Binding re-resolves to:<br/>1 POST /api/v3/issue"]
end
Cap -.->|"stronger actuator<br/>now available"| B2
style I1 fill:#2d6a4f,color:#fff
style I2 fill:#2d6a4f,color:#fff
style B1 fill:#9d4444,color:#fff
style B2 fill:#2d6a4f,color:#fff
style Cap fill:#5a5a8a,color:#fff
Now the next run has a choice: eight brittle clicks, or one clean POST /api/v3/issue. It re-resolves the binding to the API. The eight clicks collapse to one POST — and the intent and plan were never touched. The same stored Action just got faster, more reliable, and less fragile, without a human editing a single line.
The design principle underneath is worth stating flatly: the strength of a binding "reflects semantic strength, not the transport that drove it." A step that was demonstrated through a UI is not a UI step forever. It's a "file an issue" step that happened to be driven by a UI once and can be re-bound to anything that achieves the same effect. Treating the UI drive as a discovery instrument rather than the final answer is, in the author's words, "the single most-violated intuition in the design."
Watching Pixels Lies; Watching the Wire Doesn't
Here's a concrete, slightly damning illustration of why the how must be pinned by real effect and never by the richest-looking input.
In an A/B test I ran on the same workflow, the "submit the weekly snippet" task was derived two ways. One run inferred the steps from a screen recording — watching the pixels. The other observed a live capture — watching the actual network calls. They agreed on the intent perfectly. They disagreed on the final binding:
| Derived the submit step as | Status | |
|---|---|---|
| Screen-inferred run | POST /api/views.update |
a plausible guess from the UI |
| Live-captured run | POST /api/views.submit |
the call that actually fired |
Watching the pixels gave a plausible how. Watching the wire gave the how. They differ by one endpoint — and if you'd trusted the screen recording, your automation would be calling the wrong API forever, looking completely reasonable the whole time.
The lesson isn't "screen recordings are bad." (In the same A/B, the screen run actually verified a git step by effect that the live run left as an unproven hypothesis — neither modality is strictly superior; each sees a different slice of the truth.) The lesson is that the derived intent can be stable while the binding is modality-dependent — so the binding must be re-resolved and re-verified against real effect, never frozen from whatever input looked richest.
Why This Is the Same Idea, Scaled Up
Everything in this series has been one idea at different altitudes.
- In a single prompt (Post 1): state the outcome and the reason; don't script the steps.
- In an authored skill (Post 2): write the durable intent; delete the fragile mechanics that rot as models improve.
- In a stored automation (this post): version the goal; re-resolve the interface every run.
It's the same decoupling each time, and it buys the same thing each time: the what/why is the part you keep, and the how is the part you let the world — a smarter model, a newer API, a stronger actuator — improve underneath you.
Automation that stores the gesture is a photograph: accurate the instant it's taken, more wrong every day after. Automation that stores the goal is a standing order: re-interpreted against current reality every time it runs. One decays. The other compounds.
Where a Fixed Binding Still Earns Its Place
Re-resolving every run is the default, not a religion. Pin the binding when:
- Determinism is the requirement. A compliance job that must hit the exact audited endpoint every time should pin it and alert on drift, not creatively re-resolve.
- Discovery is expensive or risky. If instrumenting a fresh binding means a live write to a production system, you don't want to re-derive it casually every run. Cache the resolved binding; re-verify on a schedule.
- The interface is genuinely stable and the stakes are low. If it's one REST call that hasn't changed in three years, the three-layer ceremony is overkill. Bind it and move on.
The judgment call is the same as always: how volatile is the how, and what does a wrong resolution cost? High volatility and cheap re-resolution → re-bind every run. Low volatility or expensive/dangerous resolution → pin and monitor.
What to Do Next
- Audit one automation for frozen gestures. Find every place it names a concrete selector, endpoint, or click path. Each one is a fault line. Ask what outcome that step was really after.
- Separate intent from binding in storage. Even a lightweight split — a goal description above, a resolved actuator below — lets you re-resolve the bottom without rewriting the top.
- Instrument your UI drives. If you must start with a brittle UI path, capture the network traffic while it runs. The real API usually falls out, and now you can harden to it.
- Pin the binding by effect, not by input richness. Verify the step did what the intent wanted; don't trust the most detailed-looking recording to have guessed the right call.
- Re-resolve on failure, don't just retry. When a binding breaks, that's the signal to re-derive the how from the still-valid what — not to replay the broken gesture harder.
Next in the series: Confidence Is Not Consent — once the agent owns the how, how much of it do you let it run unsupervised? Related: MCP as Discovery Protocol, Negotiated Integrations, and Headless AI: Every App Is a Head.