The Entropy Collapse Thesis — Why One Tool, Why Clojure, Why LLMs as Compiler
Most engineers optimize for capability per tool. Pick the best tool for each job, they say. SQL for persistence, Python for scripting, Go for performance, React for UI, Terraform for infra, YAML for config. Ten tools. Ten cognitive models. Ten sets of footguns.
This is the standard view. It works. People ship software this way every day.
I think it’s wrong.
The Entropy Problem
Every tool you add introduces irreducible cognitive overhead:
Syntax switching cost — your brain context-switches between Go’s type system, Python’s indentation rules, Clojure’s parens, YAML’s whitespace traps, HCL’s strange block syntax. Each switch consumes focus.
Composability leaks — data crossing a language boundary must be serialized, validated, and reshaped. A Python dict is not a Go struct. A Terraform resource is not a Kubernetes resource. Every boundary is a translation layer that can lie, drift, or silently corrupt.
Dependency graph rot — each language brings its own package manager, its own version resolution algorithm, its own lockfile format, its own resolution failure modes. N+1 ecosystems to monitor for CVEs.
Pipeline friction — CI/CD must install N runtimes, cache N dependency directories, configure N linters, run N test runners, and report N coverage formats.
The result is that your cognitive budget is drained before you solve the actual problem. You spend 60% of your capacity managing the toolchain and 40% building the product.
This is entropy. Every tool is a perturbation of the system. Over time, the system drifts toward chaos.
Collapsing entropy means removing layers. Not adding better ones.
The One Tool Constraint
What if you committed to exactly one general-purpose programming model and made it work for everything?
Not one language per layer. One language. Data layer, application layer, scripting, automation, web frontend, CLI, infra configuration, CI/CD pipelines, one-off analytics. Same paradigm, same syntax, same composability model, from the database to the browser to the shell prompt.
The constraint forces hard trade-offs:
Compute-bound hot paths (game engines, real-time audio, GPU kernels) are off-limits. You write glue, not kernels. This is acceptable because 99% of what you build is glue.
Memory-constrained targets (embedded, WASM, Raspberry Pi peripherals) are off-limits unless runtime overhead is tolerable. Acceptable because cloud compute costs are approaching zero.
Static correctness proofs (formal verification, dependent types) are off-limits. Acceptable because generative testing + REPL-driven coverage catches the same bugs with less effort.
Everything else — the full surface area of modern application development — collapses into one pipeline. One deprecation policy to track. One idiom across the stack. One way to compose, test, and deploy.
This is the entropy-minimizing attractor. Not the easiest path. Not the most performant. The one that leaves you with the most remaining cognitive budget for the actual problem.
Why Clojure
If the goal is maximum surface area from one tool, Clojure is the strongest candidate I’ve found. The specific properties that matter:
Data Orientation
Everything in Clojure is data — code, config, state, messages, queries, responses, tests. There is no special category of “code” that behaves differently from “data.” A function definition is a data structure. A macro is a function that transforms data structures at compile time. A test is a data structure that asserts properties about other data structures.
This collapses the meta-layer into the application layer. You don’t need a separate build system to generate code. You don’t need a template engine to transform text. You don’t need a DSL — you already have one, because your language is just data, and data can be composed at runtime.
Immutability by Default
No variable can be mutated. No object’s state can change after construction. Every function takes a value and returns a new value. The old value remains unchanged and reusable elsewhere.
This is not a performance optimization. It’s a trust optimization. When you read a line of Clojure code, you know exactly what it produces. There is no hidden state that a concurrent thread, a parent class, or a callback handler might have modified. The DAG of data flow is explicit and inspectable at every point.
This maps directly to how I think systems should work. In a DAG, a child node should never be able to reach back and modify its parent. Mutation is that backward edge. Immutability removes it.
No Inheritance
Object-oriented languages model relationships through inheritance: a Dog extends Animal adds bark(). The problem is that inheritance is not a DAG. A child class can override a parent method, altering the behavior of the entire parent abstraction from below. Polymorphism makes this worse — the same method call can produce different behavior depending on the runtime type of the receiver, which the caller cannot determine statically.
Clojure has no inheritance. Composition replaces inheritance: have a value, pass it through a function, get a new value. There is no parent to override. There is no child to surprise you. The flow is unidirectional.
The Host Superpower
Clojure runs on the JVM, JavaScript runtimes, and .NET/CLR. This means it inherits the largest library ecosystems in existence — Java’s Maven Central and JS’s npm — plus their production-runtime properties (JVM: battle-tested GC, monitoring, profiling, 20 years of production hardening; JS: ubiquitous browser runtime; .NET: Windows-ecosystem reach).
Additionally, Babashka (a native-compiled Clojure with fast startup) enables Clojure scripting for CI/CD, CLI tools, and automation. Single binary, no runtime dependency, sub-second startup. This closes the final gap — Clojure now competes with Python and bash for scripting tasks.
REPL as Dialogue
The interactive development environment (REPL) is not a debugger. It’s a real-time dialogue with the running system. You can connect to a production server, inspect its state, redefine a function, test the change, and disconnect — all while the server continues serving traffic. You can build a data pipeline incrementally, inspecting each transformation step before composing the next one. You can connect to a remote API with incomplete documentation and, in <30 seconds, issue a real request and inspect the response live.
This collapses the edit-compile-run-fail-debug loop into a single continuous feedback cycle.
LLMs Close the Last Gap
I don’t type code. I describe what I want, inspect the result, and refine the specification. The LLM handles syntax, boilerplate, and mechanical translation. I handle architecture, trade-off analysis, and verification.
This works because Clojure’s syntax is minimal and regular. There is no context-sensitive grammar, no operator overloading, no implicit coercion, no special case depending on how a function is called. The LLM can produce idiomatic Clojure with high reliability because the language has few surface-level surprises.
The combination gives me:
| Layer | Who Owns It |
|---|---|
| What to build | Me |
| System architecture | Me |
| Data model design | Me |
| Security/compliance analysis | Me |
| Syntax / boilerplate | LLM |
| Tests (generative) | Me (spec) + LLM (implementation) |
| Deployment / CI | Babashka (I configure the skeleton, LLM fills details) |
The result is that I ship more systems in less time than I could by typing code myself, without sacrificing architectural control. The bottleneck becomes clarity of specification, not typing speed.
What This Means in Practice
After adopting this model, here is what I shipped in 22 days:
- 90 technical blog posts across 40+ categories
- 18 public repositories
- A supply chain security scanner for Arch Linux (aur-audit), developed in response to a real-world incident
- A serverless royalty settlement engine connecting HTMX, GCP Cloud Functions, Neon Postgres, Tazapay, and BSV (lagu-lagu)
- A Clojure toolkit for the original Bitcoin protocol (bsv-clj)
- A local-first multi-agent desktop GUI (headhunter-agent)
- A Singapore PDPA compliance toolkit for AI agents (pdpa-sg-clj)
- An IPv6-based identity + micropayment protocol for autonomous agents (ipso-agent)
- 192 commits to the main branch of nurazhar.com
None of this required me to type syntax. All of it required me to think about architecture, data flow, security, and composition.
The Trade-offs Are Real
This approach has limits. I should be explicit about them:
You cannot work effectively in teams that expect handwritten unit tests and pull requests the conventional way. The LLM generates the code; you review and verify. If the team’s process is “developer must write every line independently,” this model breaks.
You cannot target performance-constrained environments. No game engines, no real-time control systems, no embedded devices.
You cannot pass coding assessments that require writing algorithms from scratch under time pressure. Whiteboard interviews and LeetCode are structurally incompatible with this workflow.
You must be comfortable owning systems you did not type. Some engineers experience anxiety about code they did not write. If the LLM produced it, can you debug it? Can you explain it in an incident review? The answer is yes — but only if your skill is system-level understanding, not syntactic recall.
These are real constraints. They define which roles and environments are viable. For the domain I operate in — enterprise security, Bitcoin protocol, compliance automation, systems with strong formal boundaries — they are acceptable.
Conclusion
Collapse entropy to zero is a direction, not a destination. You will never eliminate all tools, all dependencies, all cognitive friction. But you can push toward a state where the remaining entropy is a conscious choice, not the accumulated debris of N years of tool-of-the-week decisions.
For me, the attractor state is:
- One programming paradigm: functional-first, data-oriented, immutable
- One language family: Clojure across JVM, JS, and Babashka
- One compiler interface: natural language → LLM → Clojure → running system
- One deploy model: Babashka single-binary for infrastructure, JVM/JS for production services
This collapses the surface area of “developer environment” to a single Babashka binary and a terminal. Everything else — databases, APIs, CI/CD, cloud services — is infrastructure, not tooling. The cognitive budget that was spent on managing toolchains is redeployed to solving the actual problem.
One tool to rule them all is not a fantasy. It’s a constraint that forces every other choice toward simplicity. The only question is whether you’re willing to accept the trade-offs.