Vibing, Harness and OODA loop

Mike's Notes

Wise words from Oskar Dudycz. Subscribe to Architecture Weekly, it's awesome. 😎

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Architecture Weekly
  • Home > Handbook > 

Last Updated

30/04/2026

Vibing, Harness and OODA loop

By: Oskar Dudycz
Architecture Weekly: 27/04/2026

Software Architect & Consultant / Helping fellow humans build Event-Driven systems. /Blogger at https://event-driven.io/ . OSS contributor https://github.com/oskardudycz /Proud owner of Amiga 500.

On why Vibing and Harness are not new and why feedback loops are important.

Hey, have a look at what I made during the weekend. I had some time, grabbed a beer, turned on the computer and tried to code this feature. If I could do so much during the weekend, how much could you and your team do with it in 2 weeks?

It’s almost a 1:1 quote of what I heard from the startup founder I worked with over 10 years ago. I’m sure that you’ve heard similar phrases from people you worked with. We all know the annoying type of person who doesn’t code anymore but thinks, “I still got it!”. Then they threw a piece of stuff at you to “just fine-tune it a bit and do final touches”. Then they’re the first ones to ask “Why so long?“.

Nowadays, the Internet is full of such people. They shout about what they did with Claude or how much progress LLM tools have made. Some even predict the end of coding. I already wrote that this is wrong perspective. I won’t repeat that, but I want to say that…

Vibing isn’t new and isn’t always an issue.

I’m saying that LLM tools are an appraisal for ignorance. The more ignorant we are of the topic we’re working with, the better we see the outcomes. And that, by itself, is not always bad, as there’s power in ignorance if we focus on getting it done with the simplest tools we have.

Still, this can be terrible if we fall in love too much with what we’ve vibed.

To understand why that “weekend beer” energy is both a superpower and a liability, we need to look at the OODA Loop.


Disclaimer, it’s not a competition for Ralph Wiggum Loop. It’s much older and generic.

Military strategist John Boyd developed the OODA loop (Observe, Orient, Decide, Act) for fighter pilots. In a dogfight, the pilot who cycles through these four stages the fastest and most accurately survives.

In software, the “dogfight” is the gap between your intent and the production-ready feature.

OODA loop is built from four steps:

  1. Observe - This is the intake of raw, unfiltered information. In our world, this means looking at the state of the system.
  2. Orient - This is the most critical and difficult stage. It’s where you filter your observations through your experience, culture, and technical knowledge.
  3. Decide - Based on your orientation, you formulate a hypothesis.
  4. Act - You execute.

Getting back to my favourite founder and LLM-based tools.

The reason founder could build a PoC in a weekend while the team needed more than two weeks is that he bypassed the Observe and Orient phases. He went straight from a vague idea to Act.

If we skip or brush past the observation step, it feels like lightning speed. If the fancy UI grid is there and it does something we wanted, we move on. We’ve outsourced Orientation to our own ego. It’s too easy to assume that because we wrote it, it works.

Observation is the intake of raw data. In a professional environment, our eyes aren’t enough. We need a Harness. If we don’t have automations, tests, integration tests, and pristine traces, we aren’t observing the system; we’re just looking at it. If the inputs are messy, our observation is clouded.

But real engineering, the kind that takes those “two weeks”, is about closing the loop properly. That’s also where we need different perspectives and knowledge sharing.

Orientation is where you process those observations. This is the part where LLMs make us feel smarter than we are. If we don’t understand how a database handles concurrent connections, our “orientation” of a generated script will be shallow. We’ll see code that “looks” right, decide it’s fine, and act by deploying it.

The “I still got it” crowd loves the Decide and Act phases because that’s where the visible progress happens. LLM tools have made these phases nearly instantaneous. We can decide to build a feature and have the code for it in ten seconds.

The problem is that the faster we Act, the faster we need to Observe. If our “Act” phase takes seconds but our “Observe” phase requires a manual weekend of clicking around and drinking beer, our OODA loop is broken. We’re just generating a pile of stuff that we haven’t actually verified.

That’s why the team usually needs more than an imaginary “two weeks”. They are not “fine-tuning” the single-brilliant-dude masterpiece. They are building the infrastructure required to make the OODA loop sustainable.

And to make that possible, they need to run the full loop: Observe, Orient, Decide, Act. And do it multiple times. That takes time, but it’s required to assess the direction, automate what needs to be automated, and ensure they can iterate further and run this loop sustainably. That’s critical for delivering the outcome at the expected pace.

Of course, there’s a danger here, overfocusing on the Orient and Decide can lead to overengineering, building stuff we don’t need. That’s where ignorance can be blissful, especially when we connect it with humility. Being humble about what we don’t know and trying things the easiest way, then learning and making enhancements. Still, humility fails under deadline pressure. The harness doesn’t.

Let me give you…

The example

I’m adding proper Observability and Open Telemetry to Emmett right now. I spent some time working on it and instrumented the first component: Command Handling.

Of course, I had tests to prove it works, but I don’t trust them enough, and I wanted to try it on a real sample, since you never know until you run it. Even the best test suite won’t tell you all.

So I decided to plug it into the sample. See if it works, how ergonomic the API is and how it fits conventions in this area.

To do it, I decided to use Grafana stack and set it up with Docker Compose. So, stable, boring stack. Not going to lie, I vibed the config. Not that there are no docs, but I intentionally wanted to see the typical config people use.

If someone says LLM-based tools are great at proof of concepts, they don’t run the stuff they vibed. If I made the observation based on the initial config, then an oriented decision would be that it won’t work. Of course, then I did the typical back-and-forth, with the LLM tool doing some Linux command Voodoo to make it work. Once. Then, if you try to repeat it, you won’t know how to do it without doing Voodoo again.

Again, that’s not much different from the other stuff we do. I’m sure that you had multiple cases, when someone didn’t use Continuous Deployment tools, but clicked through Azure, AWS, GCP portal, deployed the stack, and then there was no trace on how to set it up again (e.g. to have a different environment for testing or demos for customers).

So, we need a harness, we need a leash to keep our process on track.

How to do the harness? My advice is to start simple. We may ask LLMs to give us shell scripts, and we may ask them to run them multiple times. We also need experience and knowledge of what we want to achieve and the tools we use. It’s fine not to remember all the YAML config to set up the Grafana stack, but it’s not fine not to understand why you even use it, how it relates, and how to set it up.

Still, our first loop can close on the first working solution, even a manually vibed one. But that’s not even a PoC. We need to automate them.

I asked LLM to take notes on what issues it had, and it solved them. Then, based on that, I asked to research how to code it in TypeScript. And to use tools I know, used in past, validating if there are no new more modern ones. For instance, I was a big fan of Gulp.js and Bullseye in the past, but they’re mostly dead. I wanted to have something in the same spirit, using native, maintained tooling.

I ended up with the following tools:

  • execa for running shell scripts,
  • native fetch for calling http endpoints,
  • native Node.js test tools for checking if the stack works as expected.

Then I asked it to create the script to automate the shell Voodoo they did to make Grafana stack and Docker Compose work.

Essentially, it should:

  1. Run Docker Compose script starting up services (Grafana, Prometheus, Loki, Tempo, PostgreSQL, etc.).
  2. Wait for them to check when they’re ready (it usually takes some time).
  3. Start the application and make a request.
  4. Check if the predefined dashboard with Emmett metrics appears, and shows expected traces and metrics.

Initial diagnostic tools looked like that

async function fetchWithDiag(label: string, url: string, init?: RequestInit) {
  const res = await fetch(url, init);
  if (!res.ok) {
    const body = await res.text().catch(() => '(could not read body)');
    console.error(`\n  ✗ ${label} → HTTP ${res.status}\n  body: ${body}\n`);
  }
  return res;
}
async function diagnoseCollector() {
  const text = await fetch(URLS.otelCollectorMetrics)
    .then((r) => r.text())
    .catch(() => 'unreachable');
  const emmett = text
    .split('\n')
    .filter((l) => l.startsWith('emmett_') && !l.startsWith('#'))
    .slice(0, 5);
  console.log(
    emmett.length
      ? `\n  collector /metrics (emmett lines):\n  ${emmett.join('\n  ')}`
      : '\n  collector /metrics: no emmett_* lines found',
  );
}
async function diagnosePrometheus() {
  const json = await fetch(
    `${URLS.prometheus}/api/v1/label/__name__/values`,
  )
    .then((r) => r.json() as Promise<{ data: string[] }>)
    .catch(() => ({ data: [] as string[] }));
  const emmett = json.data.filter((n) => n.startsWith('emmett_'));
  console.log(
    emmett.length
      ? `\n  Prometheus emmett_* metrics: ${emmett.join(', ')}`
      : '\n  Prometheus: no emmett_* metrics found yet',
  );
}
async function diagnoseLoki() {
  const labels = await fetch(`${URLS.loki}/loki/api/v1/labels`)
    .then((r) => r.json() as Promise<{ data?: string[] }>)
    .catch(() => ({ data: [] as string[] }));
  console.log(`\n  Loki labels: ${(labels.data ?? []).join(', ') || '(none)'}`);
}
async function diagnoseDockerLogs(service: string, lines = 10) {
  const { stdout } = await execa('docker', [
    ...COMPOSE,
    'logs',
    '--tail',
    String(lines),
    service,
  ]).catch(() => ({ stdout: '(could not get logs)' }));
  console.log(`\n  docker logs ${service} (last ${lines}):\n  ${stdout.split('\n').join('\n  ')}`);
}

Are they pretty? No. Can they be improved? Yes. Do they have to be improved at this specific moment? No.

The setup uses test infrastructure

const CLEANUP = process.env['CLEANUP'] === '1' || process.env['CLEANUP'] === 'true';
const CLEANUP_AFTER = process.env['CLEANUP_AFTER'] === '1' || process.env['CLEANUP_AFTER'] === 'true';
const NO_START = process.env['NO_START'] === '1' || process.env['NO_START'] === 'true';
// ─── configuration ───────────────────────────────────────────────────────────
const COMPOSE = ['compose', '-f', 'docker-compose.yml', '--profile', 'observability'];
const URLS = {
  app: 'http://localhost:3000',
  prometheus: 'http://localhost:9090',
  tempo: 'http://localhost:3200',
  loki: 'http://localhost:3100',
  grafana: 'http://localhost:3001',
  otelCollectorMetrics: 'http://localhost:8889/metrics',
};
// Fresh client per run — avoids stale cart state from previous runs.
const SERVICE_NAME = 'expressjs-with-postgresql';
const CLIENT_ID = randomUUID();
const CART_ENDPOINT = `${URLS.app}/clients/${CLIENT_ID}/shopping-carts/current/product-items`;
const CONFIRM_ENDPOINT = `${URLS.app}/clients/${CLIENT_ID}/shopping-carts/current/confirm`;
// Matches the .http file — unitPrice is resolved server-side.
const ADD_PRODUCT_BODY = JSON.stringify({ productId: randomUUID(), quantity: 10 });

before(async () => {
  console.log(`\n▶ client ID for this run: ${CLIENT_ID}\n`);
  if (NO_START) {
    console.log('▶ --no-start: skipping docker compose and app startup');
    return;
  }
  if (CLEANUP) {
    console.log('▶ --cleanup: killing port 3000 and tearing down stack (down -v)…');
    await execa('bash', ['-c', 'fuser -k 3000/tcp 2>/dev/null || true']).catch(() => {});
    await new Promise((r) => setTimeout(r, 500));
    await execa('docker', [...COMPOSE, 'down', '-v', '--remove-orphans'], {
      stdio: 'inherit',
    });
  }
  const stackReady = await fetch(`${URLS.prometheus}/-/ready`)
    .then((r) => r.ok)
    .catch(() => false);
  if (stackReady) {
    console.log('▶ observability stack already up — skipping docker compose up');
  } else {
    console.log('▶ starting observability stack…');
    await execa('docker', [...COMPOSE, 'up', '-d'], { stdio: 'inherit' });
  }
  console.log('▶ waiting for backends…');
  await waitFor(() => checkUrl('Prometheus', `${URLS.prometheus}/-/ready`), {
    timeout: 90_000, label: 'Prometheus',
  });
  await waitFor(() => checkUrl('Grafana', `${URLS.grafana}/api/health`), {
    timeout: 90_000, label: 'Grafana',
  });
  await waitFor(() => checkUrl('Tempo', `${URLS.tempo}/ready`), {
    timeout: 90_000, label: 'Tempo',
  });
  await waitFor(() => checkUrl('Loki', `${URLS.loki}/ready`), {
    timeout: 90_000, label: 'Loki',
  });
  // /health returns { status: 'ok', service: 'expressjs-with-postgresql' } —
  // checking service name lets us distinguish our app from other processes on :3000.
  const checkOurApp = () =>
    checkUrl('app /health', `${URLS.app}/health`, async (res) => {
      const json = (await res.json().catch(() => ({}))) as { service?: string };
      if (json.service !== SERVICE_NAME) {
        console.log(
          `    app /health: service="${json.service ?? '(missing)'}", expected="${SERVICE_NAME}"`,
        );
        return false;
      }
      return true;
    });
  const appIsOurs = stackReady && (await checkOurApp());
  if (appIsOurs) {
    console.log('▶ app already running and healthy — skipping npm start');
  } else {
    const portTaken = await fetch(URLS.app).then(() => true).catch(() => false);
    if (portTaken) {
      // Port is occupied but not by our app — stale process or unrelated service.
      console.error(
        '\n  ✗ Port 3000 is occupied by a process that is not this app.\n' +
          '  It may be a stale version of this app (connected to a wiped database)\n' +
          '  or a completely different service.\n' +
          '  Fix: run  npm run verify:observability:cleanup  to kill it and restart,\n' +
          '  or manually free port 3000.\n',
      );
      process.exit(1);
    }
    console.log('▶ starting app…');
    app = execa('npm', ['start'], { stdio: 'inherit' });
    await waitFor(checkOurApp, { timeout: 60_000, label: 'app /health' });
  }
  console.log('▶ setup complete\n');
});

As you see, nothing fancy, the cleanup is even simpler

after(async () => {
  if (app) {
    console.log('\n▶ stopping app…');
    app.kill('SIGTERM');
    await app.catch(() => {});
    console.log('▶ app stopped');
  }
  if (CLEANUP_AFTER) {
    console.log('▶ tearing down stack (down -v)…');
    await execa('docker', [...COMPOSE, 'down', '-v', '--remove-orphans'], {
      stdio: 'inherit',
    });
    console.log('▶ stack torn down');
  } else {
    console.log('▶ stack is still running');
    console.log('▶ to clean up: npm run verify:observability:cleanup');
  }
});

Having that we can run tests:

test('successful command returns x-trace-id header', async () => {
  const res = await fetchWithDiag('POST add product', CART_ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: ADD_PRODUCT_BODY,
  });
  assert.equal(res.status, 204, `Expected 204 — body logged above`);
  const header = res.headers.get('x-trace-id');
  if (!header) {
    console.error(
      '  ✗ x-trace-id missing — verify the wrapper app in src/index.ts ' +
        'adds it via @opentelemetry/api before mounting the emmett app',
    );
  }
  assert.ok(header, 'x-trace-id header missing');
  assert.match(header, /^[0-9a-f]{32}$/, `"${header}" is not a 32-hex trace ID`);
  traceId = header;
  console.log(`  trace ID: ${traceId}`);
});
test('OTel collector exposes Emmett metrics on port 8889', async () => {
  // Send a few more requests so metrics are definitely recorded.
  for (let i = 0; i < 5; i++) {
    await fetch(CART_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: ADD_PRODUCT_BODY,
    });
  }
  try {
    await waitFor(
      async () => {
        let text: string;
        try {
          const res = await fetch(URLS.otelCollectorMetrics);
          text = await res.text();
        } catch {
          console.log('    collector :8889: connection refused');
          return false;
        }
        const emmettLines = text.split('\n').filter((l) => l.startsWith('emmett_') && !l.startsWith('#'));
        if (emmettLines.length === 0) {
          const allFamilies = [...new Set(text.split('\n').filter((l) => !l.startsWith('#') && l).map((l) => l.split('{')[0]))].slice(0, 5);
          console.log(`    collector :8889: no emmett_* metrics yet. Present: ${allFamilies.join(', ') || '(none)'}`);
          return false;
        }
        return true;
      },
      { timeout: 90_000, interval: 5_000, label: 'emmett metrics on collector :8889' },
    );
  } catch (err) {
    await diagnoseCollector();
    await diagnoseDockerLogs('otel-collector');
    throw err;
  }
});

I put it into a single file that can be run as a regular Node.js script.

It already showed me (and Claude) that what they initially did wasn’t working if you try to run it multiple times. It also showed that doing a full cleanup and rebuild, and making it reproducible, needs more work.

Is it done? Not yet; it takes too much time and resources to run it continuously throughout the pipeline. The code is a bit messy, so it needs to be organised. It’s segmented into blocks, includes basic automation and tests, and has already gone through some failures to get it done.

Could I do it better? Sure, and I will improve it, but that’s not the point. I wanted to show you my findings during weekend vibing (without beer tho), the real, not polished iteration, before I run the next one.

The main idea behind OODA loops is not to be perfect, but to iterate quickly, gather feedback as soon as possible, learn from it, develop another theory, and verify it through action.

It’s not about vibing, but it’s also not about analysis paralysis.

I hope you’re now better equipped to think about when vibing — with beer or without, with LLMs or without — actually helps, and when it doesn’t.

Vibe coding is just high-frequency steering. It only works if you have a Harness: a mechanical way to observe and orient, so you don’t steer the whole project into a wall.

Act takes seconds now. Observe takes as long as it always did. Without a harness, you’re not going faster; you’re just making more stuff you haven’t checked.

Harness is not magic, a new discipline, or the next buzzword; I hope I showed you that a bit in this article on what it may look like.

So iterate fast, but wisely remembering to do the full loop. It’s great that LLMs can help us make Acting faster, but we should not skip other steps. We should aim for a fast feedback loop to iterate in the right direction and achieve continuous improvement, to deliver proper value.

Just like Vibing isn’t new, we shouldn’t abandon old engineering practices. We should also not replace collaboration with solitary self-high fives.

Check also:

  • Emmett Pull Request with mentioned changes
  • Interactive Rubber Ducking with GenAI
  • The End of Coding? Wrong Question
  • A few tricks on how to set up related Docker images with docker-compose
  • Docker Compose Profiles, one the most useful and underrated features

Cheers!

Oskar

The Algorithm: SpaceX’s Five-Step Process For Better Engineering

Mike's Notes

I like solving impossible problems; everything else is boring.

I will apply this five-step method to Pipi and to Ajabbi's future internal culture. The method could be applied to discovering a cure for breast cancer, lowering housing costs, and building safer ships.

According to Google Search: AI Mode (Gemini).

The "Way": The Five-Step Algorithm

At SpaceX, engineers are trained in a specific "Algorithm" to tackle impossible technical hurdles. The process must be followed in this strict order to avoid the "expert's trap" of perfecting things that shouldn't exist:

    1. Question every requirement: Every requirement must have a specific person’s name attached—not just a department. Even those from "the smart guys" (including Musk himself) must be challenged to make them "less dumb".
    2. Delete any part or process you can: Engineers are encouraged to delete until they are forced to add 10% back; otherwise, they aren't deleting enough.
    3. Simplify and optimise: Only optimise after questioning and deleting. A common mistake is optimising a process that should have been eliminated entirely.
    4. Accelerate cycle time: Increase speed only after the first three steps are complete to ensure you aren't just "digging your own grave" faster.
    5. Automate: Automation is the final step, used only once the design is refined and proven.

The Culture: "Responsible Engineers" (REs)

SpaceX replaces traditional siloed management with a flat structure centred on Responsible Engineers.

    • Total Ownership: An RE owns their component from requirements and design through to manufacturing and flight.
    • Systems Thinking: Every engineer must understand the whole vehicle, not just their subsystem, to ensure local optimisations don't hurt the global mission.
    • Information Velocity: Direct communication is required; anyone can talk to anyone to solve a problem, bypassing hierarchical "chain of command" protocols. 

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

28/04/2026

The Algorithm: SpaceX’s Five-Step Process For Better Engineering

By: Garrett Reim, Guy Norris, and Irene Klotz
Aviation Week: 23/08/2024

Based in the Seattle area, Garrett Reim covers the space sector and advanced technologies that are shaping the future of aerospace and defense, including space startups, advanced air mobility and artificial intelligence.

Guy Norris is a Senior Editor for Aviation Week, covering technology and propulsion. He is based in Colorado Springs.

Irene Klotz is Senior Space Editor for Aviation Week, based in Cape Canaveral. Before joining Aviation Week in 2017, Irene spent 25 years as a wire service reporter covering human and robotic spaceflight, commercial space, astronomy, science and technology for Reuters and United Press International.

SpaceX uses 3D printers and a process of relentless refinement to streamline its Raptor engines. In the Raptor 3, plumbing and wiring that had been on the outside were fused into the motor’s metal structure. | Credit: SpaceX

What is behind SpaceX’s success? According to one former top employee, it is something called “The Algorithm.”

Tim Berry, head of manufacturing and quality at blended-wing-body startup JetZero, spent a decade at SpaceX leading the Falcon 9 and Falcon Heavy family of rockets upper-stage production team. Berry also led the Dragon Crew and Cargo integration team and was head of additive manufacturing.

How the Raptor 3 rocket engine was streamlined

“Your requirements are definitely dumb; You have to find a way to make them less dumb.”

The Algorithm was “drilled into our minds,” he said at the American Institute of Aeronautics and Astronautics Aviation Forum in Las Vegas in July. “It’s a five-step process for improving the design, making it ultimately easier to manufacture and finding ways to optimize along the way as well.”

Step 1 is: “Challenge the requirements,” Berry said. “Or as our benefactor used to say, ‘Your requirements are definitely dumb; you have to find a way to make them less dumb.’”

Step 2 involves deleting a part or a process step. “Really looking at the full value chain of what you’re working on and eliminating any unnecessary process steps while also finding opportunities to delete parts ultimately yields an overall optimization, whether it’s a reduction in labor or cycle time or anything like that,” he said.

Step 3: “Find additional opportunities to simplify the design or optimize it,” Berry said. “Make it easier to manufacture or eke a few more points of performance out of it.”

Step 4 is all about speed. “Find even more ways to go faster,” Berry explained. “You add more stations; you ramp up the manufacturing.”

And then, finally—Step 5—you automate. “Most people start with Step 5, and they automate a process that never should have existed in the first place,” said Berry. “It’s really important that you work the steps in order.”

After completing Step 5, rinse and repeat. “You’re never satisfied,” Berry said. “You’re constantly going back and finding opportunities to challenge your requirements, deleting more parts, simplifying, optimizing, going faster, and then finally, opportunities to automate, but only once you’ve really boiled down to the baseline process.”

SpaceX CEO Elon Musk is keen on reducing engineering to its basics via first principles thinking. Aristotle invented the first principles method some 2,400 years ago in his Metaphysics, describing it as trying to understand “the first basis from which a thing is known.”

“The normal way that we conduct our lives is we reason by analogy,” Musk explained in a 2012 interview. “We’re doing this because it’s like something else that was done, or it’s like what other people are doing. It’s mentally easier to reason by analogy rather than from first principles. First principles is a physics way of looking at the world, and what that really means is you kind of boil things down to the most fundamental truths.”

Musk often talks about competing SpaceX’s hardware against the laws of nature rather than other products on the market. That philosophy has driven SpaceX employees to simplify the Raptor rocket engine from something that looked “like a Christmas tree with how much stuff is on it” to a more spartan look, Berry said.

In August, SpaceX revealed the drastically streamlined Raptor 3 engine (see photo) and test-fired it. The company ditched a heat shield on the latest iteration of the methane-fueled engine by taking plumbing and wiring that was previously hanging on the outside and fusing it into the motor’s metal structure. To do so, SpaceX heavily relied on 3D printing, Musk wrote on the social media site X.

The sea-level variant of the Raptor 3 weighs 3,362 lb., compared with the 3,594-lb. Raptor 2, while generating 280 tons of force, compared with the current rocket’s 230 tons of force. The total weight of the Raptor 3 plus vehicle commodities and hardware is 3,792 lb. compared with the Raptor 2’s 6,338 lb.

“The amount of work required to simplify the Raptor engine, internalize secondary flow paths and add regenerative cooling for exposed components was staggering,” Musk said. “Getting close to the limit of known physics,” he added in another post.

Laws of Software Engineering

Mike's Notes

Dr Milan Milanovic has written a 300-page book about the laws of software engineering. It looks great. There is also a website, a newsletter and a printable poster. It is a very useful collection of 56 laws for future reference.

Missing Laws

  • Glass's Law: For every 25% increase in the complexity of the problem space, there is a 100% (fourfold) increase in the complexity of the solution space.
  • Session's Law: 
    • The Law of Exponential Complexity: As you add more components to a system, the complexity increases exponentially, not linearly.
    • Equivalence Relations & Partitioning: Sessions argues that the only way to manage massive IT complexity is through partitioning—breaking a large system into smaller, independent "Snowman" units that do not share state or data directly.
    • Mathematical Predictability: He uses these laws to calculate the probability of IT project failure based on the number of interconnected components.

Heuristics

While this name suggests rigid scientific "laws," it is more accurately described as a series of heuristic, or mental shortcuts or empirical observations that help teams understand.

Complex Adaptive System (CAS)

I knew about some of these heuristics and designed Pipi as a CAS to break many of them. 😎 Laws of Software Engineering gives me a clear target to smash.

Website

On the Laws of Software Engineering website, there is a link from each law to a page with sections covering;

  • Takeaways
  • Overview
  • Examples
  • Origin
  • Further Reading

I copied the 56 laws here from the website, subscribed to the newsletter, and printed off the poster. I must get the book. Very cool.

Thank you, Dr Milan Milanovic. 😎

From Amazon

"Conway's Law. Brooks's Law. Goodhart's Law. Hyrum's Law. If you've been writing software long enough, you've recognized these rules even before you knew their names. If you had an opportunity to see a failed rewrite, or a time that is getting bigger but slower, you hit into these rules.

These patterns have been showing up in software projects for over fifty years. Experienced engineers know them, but they learned them the hard way. These lessons were never in one place, where you could read them. They lived in academic papers from the 1960s, but also in some blog posts that get shared once and forgotten. I also found them in discussions and code reviews.

This book puts all of them in one place. 63+ laws and principles, each with its own chapter, with forewords by Dr. Rebecca Parsons (CTO Emerita at Thoughtworks) and Addy Osmani (Engineering Director at Google Cloud AI).

Each chapter covers where the law came from, how it actually works, what it looks like in the real world, and how it connects to other laws in the book. That last part matters. Some of these principles reinforce each other, and others directly conflict. The "Related Laws" sections throughout the book show you where those connections are. This is important because in practice, engineering is about tradeoffs, not rules.

The book is organized into seven standalone parts:

    • Part I: Architecture & Complexity. 8 laws, including Gall's Law, CAP Theorem, and Hyrum's Law.
    • Part II: People, Teams & Organizations. 10 laws, including Conway's Law, Brooks's Law, and the Peter Principle.
    • Part III: Time, Estimation & Planning. 6 laws, including Hofstadter's Law and Goodhart's Law.
    • Part IV: Quality, Maintenance & Evolution. 10 laws including Technical Debt, Lehman's Laws, and Testing Pyramid.
    • Part V: Scale, Performance & Growth. 4 laws, including Amdahl's Law and Metcalfe's Law.
    • Part VI: Coding & Design Principles. 6 laws including SOLID, DRY, and YAGNI.
    • Part VII: Decision-Making & Biases. 13 laws, including Occam's Razor, Dunning-Kruger Effect, and Pareto Principle.

Many chapters also cover companion laws within them: George Box's Law, The Spotify Model, Two Pizza Rule, The Dead Sea Effect, The Cobra Effect, Impostor Syndrome, and more. There's more in here than the table of contents suggests.

You don't need to read it front to back. Each chapter stands alone. Building a distributed system? Start with Part I. Team problems? Part II. Estimation problems? Part III. Codebase issues? Part IV. Or just start at page one. That works too.

Half the book covers people, organizations, and how we make decisions, not just technology. Remember that this is not a coding tutorial and it won't teach you a programming language or framework.

If you've been in the industry for a while, you'll probably recognize some of these laws. Here you will learn about other laws and how they are connected and affect each other. You will also learn to understand it in depth, so it becomes useful for your current or next projects. And if you're earlier in your career, this book gives you the vocabulary your senior colleagues already have." - Amazon

Resources

References

  • Laws of Software Engineering by Dr Milan Milanovic (2026).

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Amazing CTO
  • Home > Ajabbi Research > Library > Subscriptions > Techworld with Milan
  • Home > Handbook > 

Last Updated

27/04/2026

Laws of Software Engineering

By: Dr Milan Milanovic
Laws of Software Engineering: 27/04/2026

I’m a software engineer and CTO with over 20 years of experience, from startups to large enterprises, including big tech as a contractor. I hold a Ph.D. in Computer Science, have authored over 20 scientific publications with 440+ citations, and am recognized as a Microsoft MVP for Developer Technologies.

With each new role, from developer to architect to CTO, I encountered the same patterns. The same struggles. The same failures. The same hard-won insights that engineers keep rediscovering, often at great cost.

I started collecting these laws and principles for myself, then for my teams. When I started my newsletter, I shared them with the world. Today, my writing on software, architecture, and leadership reaches over 400,000 engineers.

This book represents everything I wish I’d had when I started. I hope it spares you some of the pain I went through.

A collection of principles and patterns that shape software systems, teams, and decisions.

Part I: Architecture & Complexity (Architecture)

1. Gall's Law

A complex system that works is invariably found to have evolved from a simple system that worked.

2. The Law of Leaky Abstractions

All non-trivial abstractions, to some degree, are leaky. 

+ George Box's Law

3. Tesler's Law (Conservation of Complexity)

Every application has an inherent amount of irreducible complexity that can only be shifted, not eliminated.

4. CAP Theorem

A distributed system can guarantee only two of: consistency, availability, and partition tolerance.

5. Hyrum's Law

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

6. Second-System Effect

Small, successful systems tend to be followed by overengineered, bloated replacements.

7. Fallacies of Distributed Computing

A set of eight false assumptions that new distributed system designers often make.

8. Law of Unintended Consequences

Whenever you change a complex system, expect surprise.

9. Zawinski's Law

Every program attempts to expand until it can read mail.

Part II: People, Teams & Organizations (Teams)

10. Conway's Law

Organizations design systems that mirror their own communication structure. 

+ The Spotify Model

11. Brooks's Law

Adding manpower to a late software project makes it later. 

+ Little's Law

12. Dunbar's Number

There is a cognitive limit of about 150 stable relationships one person can maintain.

13. The Ringelmann Effect

Individual productivity decreases as group size increases. 

+ The Two-Pizza Rule

14. Price's Law

The square root of the total number of participants does 50% of the work.

15. Putt's Law

Those who understand technology don't manage it, and those who manage it don't understand it.

16. Peter Principle

In a hierarchy, every employee tends to rise to their level of incompetence.

17. Bus Factor

The minimum number of team members whose loss would put the project in serious trouble. 

+ The Dead Sea Effect

18. Dilbert Principle

Companies tend to promote incompetent employees to management to limit the damage they can do.

Part III: Time, Estimation & Planning (Planning)

19. Hofstadter's Law

It always takes longer than you expect, even when you take into account Hofstadter's Law. 

20. Parkinson's Law

Work expands to fill the time available for its completion.

21. The Ninety-Ninety Rule

The first 90% of the code accounts for the first 90% of development time; the remaining 10% accounts for the other 90%.

22. Goodhart's Law

When a measure becomes a target, it ceases to be a good measure. 

+ The Cobra Effect

23. Gilb's Law

Anything you need to quantify can be measured in some way better than not measuring it. 

24. Premature Optimization (Knuth's Optimization Principle)

Premature optimization is the root of all evil.

Part IV: Quality, Maintenance & Evolution (Quality)

25. Murphy's Law / Sod's Law

Anything that can go wrong will go wrong.

26. Postel's Law

Be conservative in what you do, be liberal in what you accept from others.

27. Broken Windows Theory

Don't leave broken windows (bad designs, wrong decisions, or poor code) unrepaired. 

28. The Boy Scout Rule

Leave the code better than you found it.

29. Technical Debt

Technical Debt is everything that slows us down when developing software.

30. Linus's Law

Given enough eyeballs, all bugs are shallow.

31. Kernighan's Law

Debugging is twice as hard as writing the code in the first place.

32. Testing Pyramid

A project should have many fast unit tests, fewer integration tests, and only a small number of UI tests. 

+ The Beyoncé Rule

33. Pesticide Paradox

Repeatedly running the same tests becomes less effective over time.

34. Lehman's Laws of Software Evolution

Software that reflects the real world must evolve, and that evolution has predictable limits.

35. Sturgeon's Law

90% of everything is crap.

Part V: Scale, Performance & Growth (Scale)

36. Amdahl's Law

The speedup from parallelization is limited by the fraction of work that cannot be parallelized.

37. Gustafson's Law

It is possible to achieve significant speedup in parallel processing by increasing the problem size.

38. Metcalfe's Law

The value of a network is proportional to the square of the number of users. 

+ Sarnoff's & Reed's Laws

Part VI: Coding & Design Principles (Design)

39. DRY (Don't Repeat Yourself)

Every piece of knowledge must have a single, unambiguous, authoritative representation.

40. KISS (Keep It Simple, Stupid)

Designs and systems should be as simple as possible.

41. YAGNI (You Aren't Gonna Need It)

Don't add functionality until it is necessary. 

42. SOLID Principles

Five main guidelines that enhance software design, making code more maintainable and scalable. 

43. Law of Demeter

An object should only interact with its immediate friends, not strangers.

44. Principle of Least Astonishment

Software and interfaces should behave in a way that least surprises users and other developers.

Part VII: Decision-Making & Biases (Decisions)

45. Dunning-Kruger Effect

The less you know about something, the more confident you tend to be. 

+ Impostor Syndrome

46. Hanlon's Razor

Never attribute to malice that which is adequately explained by stupidity or carelessness.

47. Occam's Razor

The simplest explanation is often the most accurate one.

48. Sunk Cost Fallacy

Sticking with a choice because you've invested time or energy in it, even when walking away helps you.

49. The Map Is Not the Territory

Our representations of reality are not the same as reality itself.

50. Confirmation Bias

A tendency to favor information that supports our existing beliefs or ideas.

51. The Hype Cycle & Amara's Law

We tend to overestimate the effect of a technology in the short run and underestimate the impact in the long run.

52. The Lindy Effect

The longer something has been in use, the more likely it is to continue being used.

53. First Principles Thinking

Breaking a complex problem into its most basic blocks and then building up from there.

54. Inversion

Solving a problem by considering the opposite outcome and working backward from it.

55. Pareto Principle (80/20 Rule)

80% of the problems result from 20% of the causes.

56. Cunningham's Law

The best way to get the correct answer on the Internet is not to ask a question, it's to post the wrong answer.

20 Engines

Mike's Notes

While I finish off testing the Pipi System Engine (sys), here is the plan for the next stage.

Update 27/04/2026

Today, I have also been setting up some new twin 27" monitors to help with coding. I need larger 16pt Arial or Noto Sans font sizes these days.😎 Hopefully, better visuals will mean I will not get so tired.

Update 03/05/2026

The list has been pruned to 18 engines now, not 20.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

07/05/2026

20 18 Engines

By: Mike Peters
On a Sandy Beach: 26/04/2026

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Project

The project is to import the 18 engines necessary for Pipi to run in deterministic mode, and self-manage with a minimal set of internal features, no adaptation or self-evolution. That will come later, when many more engines are imported, contained inside other engines, and probabilistic behaviour begins.

Variables

All of these engines have worked for years (some with origins dating back 26 years) and are mature and stable, but have been migrated from a laptop to a data centre, where the host environment differs. I forgot to sort that out first. 😎 The Nest Engine (nst) was rapidly invented to solve that problem, but the variables generated by the nest are also causing name clashes.

Process

Each engine needs the same minor tweak as it is imported. I'm also carefully checking all spelling in each engine. Completing descriptions for future self-documentation.

Logs

Each engine is then left running while I watch the logs. Once the log engine is imported, logs can be visualised in Mission Control using third-party open-source tools. This will speed things up a lot. Eventually, the logs will feed feedback loops as this beast scales.

System Engine (sys)

The System Engine (sys), on its own, is like the empty skin of an elephant, to be stuffed and used in a museum exhibit. It looks like an elephant, but it is not alive.

The System Engine has some intrinsic properties on its own. It is designed to contain all other engines. As the engines are added and interact, the System Engine will come to life.

It's the whole that is greater (George Ellis) or lesser (Terrence Deacon) than the sum of the parts.

18 Engine import list

In this order.

  1. System Engine (sys)
  2. Nest Engine (nst)
  3. Namespace Engine (nsp)
  4. Render Engine (rnd)
  5. Template Engine (tem)
  6. Variables Engine (var)
  7. Log Engine (log)
  8. Data Engine (dta)
  9. Configuration Engine (cnf)
  10. Versioning Engine (ver)
  11. Code Engine (cde)
  12. Conductor Engine (cnd)
  13. Directory Engine (dir)
  14. Node Engine (nde)
  15. CMS Engine (cms)
  16. Core Engine (cor)
  17. Factory Engine (fac)
  18. Page Engine (pge)

Pipi is the IDE

I built Pipi using Pipi, so I'm stuck in chicken-and-egg land at the moment. The more engines are imported, the easier this will get. I have a rough idea of the import order, and luckily, I can use Synethesia to run simulations, which are usually very fast and accurate. After all, it's how I build everything.

Pipi Editions

Each engine is like a different kind of Lego brick, and each Pipi is built out of hundreds of these bricks. The 4 Editions are built with the same bricks, combined in different ways. The Instances of any Edition are built exactly the same, but their databases with the same data models will store different histories, weights, parameters, etc., as they adapt and evolve.

DevOps Engine (dvp)

The DevOps log below is currently maintained manually, but will be generated automatically once the DevOps Engine (dvp) is back up and running. These logs will be automatically published on the Ajabbi Developer website using an interactive format with more detail.


DevOps log (edit)

A record of work done.

NZ DateTime Action Engine Status
2026-05-07 20:01 Edit 18 engines - short and long descriptions. Complete





Test System Engine (sys)





Create Nest Engine (nst) - Code for Linux vs Windows path delimiters.

Test Nest Engine (nst)





Test Namespace Engine (nsp)





Test System Engine (sys)





Import Render Engine (rnd)

Edit Render Engine (rnd) - variables.

Edit Render Engine (rnd) - spelling.

Test Render Engine (rnd)





Test Render Engine (rnd) - run Nest Engine (nst) templates to create a nest.





Test System Engine (sys)





Import Template Engine (tem)

Edit Template Engine (tem) - variables.

Edit Template Engine (tem) - spelling.

Test Template Engine (tem)





Import Template Engine (tem) - import temporary nest templates





Test System Engine (sys)





Test Render Engine (nst) + Template Engine (tem) + Nest Engine (nst)





Test System Engine (sys)





Import Variables Engine (var)

Edit Variables Engine (var) - variables.

Edit Variables Engine (var) - spelling.

Test Variables Engine (var)





Test System Engine (sys)





Create Variables Engine (var) - quick & dirty CRUD editor.





Create Temporary web form UI for each engine

Test Temporary web form UI for each engine





Import Log Engine (log)

Edit Log Engine (log) - variables.

Edit Log Engine (log) - spelling.

Test Log Engine (log) - CRUD log file formats

Render Log Engine (log) - logs





Import Graphing library for logs

Create Embed visualisations on Mission Control web pages.





Test System Engine (sys)





Import Data Engine (dta)

Edit Data Engine (dta) - variables.

Edit Data Engine (dta) - spelling.

Test Data Engine (dta)





Test System Engine (sys)





Import Configuration Engine (cnf)

Edit Configuration Engine (cnf) - variables.

Edit Configuration Engine (cnf) - spelling.

Test Configuration Engine (cnf)





Test System Engine (sys)





Import Versioning Engine (ver)

Edit Versioning Engine (ver) - variables.

Edit Versioning Engine (ver) - spelling.

Test Versioning Engine (ver) - automatic versioning.

Render Versioning Engine (ver) - version logs





Test System Engine (sys)