The Human Doesn't Leave. We Stop Reading Every Line.
Geoffrey Huntley shared a deliberately obfuscated Haskell function in his newsletter. Single-letter names. Recursion everywhere. My first reaction: no idea.
The 13 lines I couldn't read
f :: String -> String
f = g . h
where
h [] = []
h (x:xs) = h xs ++ [x]
g x =
let (y, z) = p x
in if y then z else g z
p [] = (True, [])
p [x] = (True, [x])
p (x:y:xs)
| x > y = let (a, b) = p (x:xs) in (False, y:b)
| otherwise = let (a, b) = p (y:xs) in (a, x:b)So I tried his experiment. I asked Claude to explain it like it was talking to my kid, with TypeScript as a reference.
The experiment
It came back with a row of kids holding letter cards, swapping places until they were lined up A to Z. Plus clean TypeScript I could actually run.
It was bubble sort the whole time.
// f: reverse the letters, then sort them
function f(s: string): string {
const letters = s.split("");
return g(h(letters)).join("");
}
// h: turn the line around (reverse)
function h(list: string[]): string[] {
if (list.length === 0) return [];
const [x, ...xs] = list;
return [...h(xs), x]; // h xs ++ [x]
}
// p: walk down the line once, swapping neighbors that are out of order.
// Returns [nobodySwapped, newLine]
function p(list: string[]): [boolean, string[]] {
if (list.length <= 1) return [true, list]; // empty or one kid: already fine
const [x, y, ...xs] = list;
if (x > y) {
// Out of order: y steps in front, x keeps walking down the line
const [, rest] = p([x, ...xs]);
return [false, [y, ...rest]]; // someone swapped!
} else {
// In order: x stays, keep checking from y onward
const [nobodySwapped, rest] = p([y, ...xs]);
return [nobodySwapped, [x, ...rest]];
}
}
// g: keep walking the line until a whole walk has no swaps
function g(list: string[]): string[] {
const [nobodySwapped, newLine] = p(list);
return nobodySwapped ? newLine : g(newLine);
}
console.log(f("banana")); // "aaabnn"
console.log(f("hello")); // "ehllo"The translation isn't the point
The Haskell-to-TypeScript trick is a demo, not the thesis. Translation between languages isn't new. The question underneath it is.
For forty years, nearly every design decision in computing assumed a human was reading, writing, or operating the system. Consoles exist because mainframes had human operators. Languages evolve at the speed humans can learn. As Huntley points out, LINQ took five to eight years to catch on, and Python 2 to 3 took about fifteen. Companies keep separate Ruby, .NET, and Java teams because people specialize. Library ecosystems mattered because humans needed them.
Take the human out of the reading seat, and every one of those choices is open again. Readable code is just the most familiar example of a much bigger pattern.
Verification replaces readability
Readable code was always a stand-in for “can we trust this?” When an agent writes the code, trust comes from machine checks: strict types, compilers, and simulators that catch the agent's mistakes on every loop. Huntley calls this back pressure.
That's why porting works “when the end result is easy to verify,” as Huntley puts it. It's also why he can run cheaper models: pick a language that does the verification for you, and “you need less intelligence to stay on the rails.”
The human moves from reading every line to defining what must be true.
This is also why I can get away with cheaper models. Pick a language that does the verification for you, and you need less intelligence to stay on the rails.
What guardrails actually look like
This is where I have something concrete to add. I run ctrl+shft, an agent engine that plans, implements, and opens pull requests on its own. Types are one form of guardrail. The ones that matter most in my pipeline are the two places a human still has to act.
The pipeline is autonomous between two human gates and deliberately stops at them. A human applies the start label to launch the work. A human applies the verdict label to ship it. There is no auto-merge by design. The agent can plan, implement, and open a PR on its own — but it cannot merge its own work.
Every fix PR must state its regression-guard verdict: added a test, covered by an existing test, or not warranted with a reason. A check in CI enforces this with zero permissions.
A convention is only real when a machine enforces it.
A hook blocks committing infrastructure changes directly to main. Infra changes must flow feature → dev → main, or the dev branch drifts and workflows break. The hook lets ambiguous cases through but always blocks the known-bad one.
Code-health findings carry path allowlists — the exact globs the implementer may modify, enforced in CI. The agent can only touch what it's been handed. And if the automation token is missing, the first chaining hop fails closed: the issue lands in blocked. There is no silent failure.
The pattern across all of these: guardrails aren't about restricting the AI. They're about making the boundaries explicit and machine-enforced. Types do this at the language level. Process gates do it at the system level. Both give the agent feedback on every loop.
The human doesn't leave
The skill that matters is shifting from “can you read this?” to “can you ask the right question and check the answer?”
The human doesn't leave. We stop reading every line and start deciding what must be true. That's a different job, and it's the one worth getting good at.
Which practice on your team exists only because a person had to read the code?