Gall's Law

"A complex system that works is invariably found to have evolved from a simple system that worked." — John Gall, 1975

A complex system that works is invariably found to have evolved from a simple system that worked. The converse bites harder: a complex system designed from scratch never works on the first try and can't be patched into working — you must fall back to a simple system that runs and let it grow.

Why it's non-trivial: ① It is the fundamental victory of evolution over design. Complexity can't be assembled in one shot; it can only be grown step by step — and every step must preserve the invariant "the system still runs." This is isomorphic to biological evolution: no complex organ was designed in one go; the eye accreted from a light-sensitive patch, and every intermediate form had to be useful. ② It is of a piece with complex adaptive systems — complexity is an emergent product, not a blueprint product. The most dangerous thing about a perfect architecture diagram is precisely that it skips the "simple intermediate state that runs." ③ Direct corollary: the big-bang rewrite almost always fails, because it asks a complex system that has never run to run all at once.

In practice: translate every urge to "build the complex system directly" into "first find the minimal version that runs, then let it grow." Diagnostic question: after this step, does the system still run? The moment a step makes it stop running, you're violating Gall's Law.

Classic example

The Internet wasn't designed — it evolved from a few-node, simple-but-working ARPANET, staying connected and usable at every step until it grew into a global network. Unix and the World Wide Web are the same: a simple, working kernel grown incrementally. None of them was "fully designed before launch."

Scenario · BigCat

① Engineering: when building an AI-agent system, don't start by designing a ten-agent orchestration framework — get a single agent working end-to-end on a real task first, then add the second. Working first, complex later. ② Home: want a "perfect schedule system" for the family? Run a single shared list for two weeks; once it works, add automation. ③ Anti-pattern alarm: when you catch yourself carefully designing complex structure "for future use," it probably can't even pass step one. Workingness is the one thing you can't buy on credit.


English Prompt
I'm about to build/rewrite [system/product/process]. Current plan: [describe]. Apply Gall's Law: 1. Am I trying to stand up a complex system that has never worked, all at once? 2. What is the minimal version that runs end-to-end — which parts can I strip while it still works? 3. Design a 3–4 step growth path where every step keeps the system running, naming the single new complexity added at each step.

Postel's Robustness Principle

"Be conservative in what you send, be liberal in what you accept." — Jon Postel, RFC 760, 1980

Be conservative in what you send, be liberal in what you accept. This principle let the early Internet stitch countless different implementations into one interoperable whole: you emit strictly to spec yet tolerate the other side's small flaws, so collaboration doesn't collapse over a single formatting deviation.

Why it's non-trivial: ① Its blessing and its curse are the same thing. Liberal acceptance brings interoperability, but it also lets the spec slowly rot — every implementation accepts slightly different input until, over time, nobody can say what the "correct" format even is, and bugs become de-facto standards (precisely the seedbed for the next card, Hyrum's Law). ② So modern protocol design (HTTP/2, QUIC, many new data formats) swings toward strictness: better to reject early than let ambiguity settle into unrecoverable technical debt. Tolerance borrows "today's compatibility" against "tomorrow's chaos." ③ The balance depends on the system's shape: open ecosystems (maximize interop) lean liberal; closed cores meant to evolve for years lean strict.

In practice: design every interface in two halves — accept the dirty data of the real world as liberally as you can at the inbound edge, but emit strictly to spec at the outbound edge (downstream depends on your output). The key discipline: accepting liberally is not the same as propagating liberally — normalize dirty input into a clean internal representation right at the boundary; never let tolerance seep into the core.

Inbound: accept liberally "dirty but reasonable" inputs normalize strict output Outbound: emit strictly downstream depends stably
Normalize liberal input into strict output at the boundary — keep tolerance out of the core
Classic example

Early browsers' liberal parsing of malformed HTML (quirks mode) let the web explode — anyone's hand-written bad markup still rendered. The cost was decades of rendering inconsistency, and HTML5 having to spend enormous effort precisely standardizing "fault-tolerant parsing" after the fact. Tolerance ignited the ecosystem and left a spec debt behind.

Scenario · BigCat

① The interface to an LLM: accept liberally at the inbound edge (users' natural language is endlessly varied — take it in and normalize it), but the output schema you hand downstream must be strict (fixed JSON fields, fixed types), or downstream descends into chaos. ② Communication: be liberal about how others express themselves (don't nitpick wording), but precise in your own outward commitments — the engineering principle, isomorphic in collaboration. ③ Beware the dark side: the more you "accept anything," the less the other side self-corrects, until you're forced to forever support everyone's bad habits.


English Prompt
I'm designing [interface/API/collaboration process]. Apply the Robustness Principle on both sides: 1. At the inbound edge, which "dirty but reasonable" real-world inputs should I accept liberally? Give a normalization strategy. 2. At the outbound edge, is my output strict enough for downstream to depend on stably? 3. Identify one over-tolerance risk — which bug will my current leniency eventually cement into an unchangeable de-facto standard?

Hyrum's Law

"With a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody." — Hyrum Wright

With a sufficient number of users of an interface, what you "promised" in the contract no longer matters — every observable behavior of your system, documented or not, will be depended on by somebody. The wording of an error message, the iteration order of a hash, a response that got 10 ms faster — anything observable becomes an implicit interface in someone's code.

Why it's non-trivial: ① It is the death of the encapsulation myth. In theory a private implementation is free to change; in reality, with enough observers, any behavior becomes a dependency — there is no truly leak-proof abstraction. ② It is isomorphic to the observer effect: once a behavior is observed, it's pinned. The implementation freedom you think you own shrinks inversely with your user count. ③ At depth it is the emergence of an "implicit contract" — the collective behavior of thousands of dependents spontaneously forms a real contract far larger than your docs, and one you can't recall. ④ Corollary: to preserve freedom to change, either actively hide behavior (inject randomness, deliberately scramble ordering so nobody can depend on it) or lock semantics early, while users are few.

In practice: before shipping an interface, ask — which behaviors I never promised but that are observable are quietly becoming contract? For dimensions you truly want to keep free, inject controlled jitter: "if you depend on this, it will change on you."

Classic example

The Linux kernel's iron rule, "never break userspace" — because any system-call behavior is depended on by some program, even when it was originally a bug. Windows long preserving known defects for old-software compatibility is the same. This law was distilled by Google's engineers from countless incidents of "changed an unpromised detail and broke downstream."

Scenario · BigCat

① Platforms/libraries: once your internal library is used by dozens of teams, you can't even touch log formats or error-message text — each could be someone's parsing anchor; to keep room to evolve, set the rules while users are still few. ② AI prompt engineering: a prompt template depended on by many downstream flows can break a pipeline silently if you change one word or reorder the output — the "observable behaviors" of an LLM's output are all implicit contract. ③ Parenting: implicit promises you set unintentionally (a story every night, the park every weekend) get treated by a child as an inviolable contract — an observable regularity is a promise, even one you never said aloud.


English Prompt
I maintain [interface/library/prompt template/system] and plan to change it: [describe change]. Use Hyrum's Law to find landmines: 1. List 5 behaviors I never promised but that are observable and may already be depended on (output ordering, error text, timing, defaults). 2. Which of these does my change touch, and how would downstream break silently? 3. Recommend a strategy: which behaviors to actively randomize now to prevent dependence, and which to formally promote into the contract and stabilize.

Wirth's Law

"Software is getting slower more rapidly than hardware is becoming faster." — Niklaus Wirth, 1995

Software gets slower faster than hardware gets faster. Moore's dividend doesn't turn into a faster experience — it's eaten by layers of abstraction, bloated dependencies, and the "the machine is fast enough anyway" mindset (the industry quips, "Andy giveth, and Bill taketh away").

Why it's non-trivial: ① At root this is an incentive/economics problem, not a technical one: the hardware dividend is spent by developers on development speed (more abstraction layers, more third-party dependencies, easier frameworks) rather than returned to users. Performance is actively consumed; it never improves on its own. ② It is isomorphic to entropy — software naturally trends toward bloat, and the "entropy" of complexity only rises unless you continuously inject counter-energy (performance budgets, periodic slimming). ③ It echoes Parkinson's Law: available resources get filled by the workload; give it more memory and CPU and the software bloats to consume them. ④ So "performance discipline" must be an active, institutionalized swim against the current, not a hope that hardware will bail you out.

In practice: set a "performance budget" on critical paths (e.g., cold start < X s, first paint < Y ms) and guard it as a test that can fail — the budget is the counter-energy you inject against entropy. Every time you add an abstraction layer or a dependency, ask first: is the development convenience worth the hardware dividend it eats.

time / hardware generations → hardware gets faster actual software experience eaten dividend
The hardware dividend keeps widening, the experience can't keep up — the gap is the performance consumed
Classic example

Launch an Electron-based chat or editor app today, and its memory and startup time would have been enough to run an entire 1990s operating system with room to spare. Average web-page weight bloats year over year; no matter how fast the hardware, it can't claw back those seconds of first paint — compute rose by orders of magnitude, the experience barely moved.

Scenario · BigCat

① The AI-era Wirth's Law: the compute dividend of stronger models is being eaten by ever-longer agent call chains, layer upon layer of RAG retrieval, and redundant re-prompting — the model got an order of magnitude faster, end-to-end experience barely did. ② Personal workflow: the stronger the tools, the more bloated the process you pile up (ten plugins, five layers of automation), and the net efficiency gain is canceled by the process's own overhead. ③ Counter-discipline: set a "latency/step budget" for your own AI workflow too, and make every added step prove its returned value exceeds the speed it eats. Speed doesn't come back on its own — you have to reclaim it.


English Prompt
My [app/service/AI workflow] is degrading/bloating: [describe current state and symptoms]. Apply Wirth's Law: 1. List the top 3 sources eating the performance dividend (abstraction layers, dependencies, redundant calls, complexity creep), ranked by cost. 2. Propose a quantifiable performance budget for the critical path (concrete metrics + thresholds) that can be guarded as a failing test. 3. Give 2 anti-entropy moves: which steps to cut/merge for the biggest immediate speed-back without sacrificing core value.