Mechanical sympathy

Something that’s been floating around in my head lately is the idea that I don’t know any truly good engineers who are also not good at at product design.
Product design can roughly be designed as the contract between the creator and the user, where the contract is designed by a set of affordances, or actions that the product allows the user to take. This is all cribbed from Don Norman and The Beauty of Everyday Things.
For example, an affordance of a chair is that it allows you to sit. An affordance of most social media feeds is that they allow you to pull to refresh. A search bar, in theory, affords you the ability to look for results. Recently this has changed because we’ve been vibe spaced.
What makes good engineers good at product design is the same thing that makes them good at engineering. They feel for the boundaries of what the code and the product allows them to do and stop at those boundaries.
Another name for being able to understand and plan for affordances, either through good product intuition, or experience, or both, in the real world is mechanical sympathy.
Mechanical sympathy was initially coined in referring to Formula 1 racecar handling, and in software, has been adapted to mean “writing the kind of code that makes sense with the underlying stack so we can get good performance from our systems.”
Mechanical sympathy for both developers and end-users means understanding when asyncio is and is not helpful. It means using the right language, the right build system, the right font. It means using the least amount of tooling possible. Allowing for local development. It means reading code inside out rather than top to bottom. Using uv. Removing code where not necessary. Respecting boundaries.
I was watching this really great talk by Mario on how he built an agentic coding harness, and was reminded that agentic coding tools, just like many cloud services, don’t have mechanical sympathy.
Things that have happened to me over the past few weeks, off the top of my head, across various projects, languages, and providers:
A test failed, producing a
500error. I asked the agent to fix the test. The first time, it tried to fix the code instead of the test. The second time, it rewrote the test to assert a500instead of a200.Even with a spec file, agents (and most chatbots, still) will use Python’s
ListandDict, which have been deprecated for years.Agents won’t use vectorized numerical operations unless prompted.
When writing a test, the agent fails numerous times, and instead of suggesting that the code should be refactored to be simpler and easier to test, just keeps trying, endlessly, to work agains the grain of the code flow and fix the failing thing.
In new Python projects, uv is skipped entirely as a first-class citizen across many providers as a suggestion of how to package projects.
Here’s more stuff:
Maybe agents.md / gastown / mcp / ralph / subagents / using a different provider that was cooked last week but is cracked this week, and switching from the provider that was cracked last week but is cooked this week, solve this. It’s possible that by the time I publish the post, or three microseconds from now, a new model or harness that will come out that renders these cases obsolete.
But the larger point is that sympathy, and good product design, is made up of hundreds and thousands of moments like this, where missing one is not critical, but, when looked at in its entirely, result in an app that feels “hard” to use and develop.
Mechanical sympathy, just like real sympathy, comes from an enormous context window learned over a human lifetime. People, over time, learn to get good at mechanical sympathy. Coding agents can’t. It’s (maybe) possible they will someday, until then we need to provide the intuition.