Software-in-the-Middle: When Software Becomes the Customer

When I started developing software, the operating system handed off control to an application, the application did its thing, and when done, handed back control to the operating system. These early systems were largely single-application and linear. Control flow was simple and direct. The operating system had very little involvement with the running application.

The rise of graphical user interfaces brought the next major paradigm mainstream. Instead of the operating system handing off control, it actually owned control, sending it temporarily to the application. The application and operating system worked in cooperation. For instance, if you wanted to print, the application would call the operating system and tell it what to print and the operating system would handle it from there. This is called event-driven programming.

This was the standard for desktop and mobile operating systems. When web development became popular, client-server also became popular and hit massive scale. In this model, the application would do some work, need some information, and request it from the server. The server would do its thing and send back a response with whatever was requested. Meanwhile, generally nothing was stopping the application from doing other things while it was waiting.

In each of these models, a human did something and the software reacted to it. The user would click a button and the application and operating system or application and server would do something on their behalf. This is human-in-the-middle. A human is involved in the process.

This is mostly how things worked until the early 2000s. When I started my first company, I remember investors saying selling to software developers was one of the worst markets you could ever choose. This was a group of people who could write stuff on their own and all acted as individual purchasers. Very hard! And many companies died trying.

In the late 2000s and early 2010s, this started to shift. Part of this was the rise of microservices where we could break control of the application into many different servers with each doing one specific task. And another was the rise of mobile and modern, very complex operating systems. As a developer, there was too much to know and it became feasible and desirable to outsource some capabilities to other software applications and services. We had mashups, where we’d use, say, Google Maps in our own applications.

In the 2010s a few new companies were founded to build services explicitly for other software. Stripe, payment processing, and Twilio, messaging and communication, were two such companies. Both are still staples in software development circles. Why build your own payment processing software with all the compliance and reporting requirements when we can pay 2.9% and $0.30 per transaction, and work with a company that focused on making it brain-dead simple to integrate?

This is the start of the inflection point. For the first time we have software built primarily for other software to call, with no humans involved. That’s software-in-the-middle. That shift is about to accelerate dramatically with AI, LLMs, and agents.


For decades, software was built primarily for humans. We clicked buttons. We filled out forms. We triggered workflows.

Stripe and Twilio changed that. They weren’t built for humans to use directly. They were built for software to call. Clean APIs, clear contracts, and simple integrations.

That pattern is about to dominate.

AI, LLMs, and agents don’t just assist humans; they consume APIs, call services, and orchestrate workflows. The next generation of software development won’t just build applications for users; it will build systems designed to be used by other systems.

Software enabling software. Software-in-the-middle.

Bad LLM Code, 4,000 Lines or Less

I didn’t get bad code because an LLM can’t write software. I got bad code because I gave it too much freedom.

I was working on a new feature for TrueMath this past week. I’d already written it before, but it lived behind an admin wall, and I wanted to elevate it for customers. The change came with some structural overhead, but at its core this was mostly a lift-and-shift exercise.

So I started a session with my trusty LLM, fed it a bunch of code, and said, “Here’s the basic structure, here’s what I did before—write this in the new style.”

The original code was about 2,000 lines. The LLM wrote 4,000 lines and still wasn’t done. And the code was spaghetti. There is no way I could have supported that long-term.

After a few days, I stopped writing code, started a new chat with the same LLM, and changed my instructions. This time I explicitly told it to lift and shift within the new structure and to write as little new code as possible.

It nailed the core in a single response. The rest took me a couple of days to finish up. The result? Clean, easy to update—and about 2,200 lines of code. Perfect.

One of the beautiful things about using an LLM to write software is that there’s a clear way to check whether what it did was correct. We run code through a compiler. We inspect outputs. We write automated tests and run them again and again.

Most things we ask LLMs to do don’t work like that.

We ask it to do research, and unless we double-check everything, we have no idea whether it made parts of it up. We ask it to draft legal documents without knowing whether it missed important laws in our jurisdiction.

We ask it to do math.

Did it use the right formulas?
Did it use the right data?
Did it make assumptions it never told us about?

Software gives us compilers, tests, and repeatability. Other domains—research, law, and especially math—don’t. Until they do, we’re still guessing. Just faster.

The New Software Development Stack: Humans + LLMs

How Software Development Really Works

I’ve been developing software for 40 years. I’m mostly self-taught and because of that (and my personality), I’ve spent a lot of time thinking about the process of developing software, not just the code.

While most of my software development has been in small companies I founded or personal side projects, my day job is as an engineering manager at a large multinational. This has given me a different vantage point, observing two completely different engineers: the senior engineer and the junior engineer.

The fundamental difference isn’t years of experience. It’s which parts of the process the engineer is expected to own. A junior engineer is expected to implement a solution and test that their solution works. A senior engineer is expected to understand the problem, break it down into possible solutions, choose the best one, implement and verify:

  1. What is the problem we are trying to solve?
  2. What are the options for solving the problem?
  3. Which of these options is the best choice for this situation?
  4. Implement
  5. Did I fix the problem without breaking other stuff?

Senior engineers are responsible for all five stages; junior engineers only steps 4 and 5.

How We Used to Learn

When I started writing software, there were really only two ways to learn how to write code: read a book (magazine, etc.) or ask someone who knows (including take a class). With the advent of the Internet both of these options expanded exponentially. “Read a book” became “search the Internet.” Stack Overflow became the de facto method for figuring out how to do something you haven’t done before, or reminding you how to do something you’ve done in the past.

Asking someone you know also expanded exponentially. I could read blog posts by other developers, ask on social media, take an online course, and of course our ability to connect with other developers expanded as well. At big companies, “ask someone who knows” is known as pair programming. Two (or more) developers sit in front of a screen and talk about the code in front of them.

Both of these are inefficient. While searching the Internet is easy to do, it is rare to find the exact solution to your exact problem. Most of the time we have to transform someone else’s solution to solve our problem.

Asking someone who knows is also inefficient. Finding someone who knows and with enough experience with your problem and willing to give you time is one thing. Getting that help at the exact time you need it is a whole other problem. In addition, “asking stupid questions” is something most people don’t want to do with another human for fear of being judged.

What LLMs Change

LLMs resolve many of these inefficiencies. They can answer questions about your specific code, and they can act like a pair programmer with deep knowledge of your language, framework, and tools. LLMs have the patience of Job, answering every question, discussing endlessly, and, yes, even being polite for the stupid queries. No shame necessary!

LLMs don’t just change how we write code. They change how we think about solving problems.

Hello World!

A lot of engineers recognize the potential power but don’t know how to start. Some try things but have a hard time fitting it into their development processes. This is really hard to do, actually. We each build certain processes, certain ways of working, and the act of inserting an LLM isn’t so much inserting as rototilling.

So we need practical ways of putting a toe in the water. I’m going to propose five.

Help Me Do This

This is the simplest. It requires only the most modest change in process. Instead of asking the Internet how to do something, ask the LLM. To do this effectively you need to take the time to fully describe what exactly you are trying to do. This is a different muscle. Instead of reading something on Stack Overflow and attempting to determine how that turns into a solution to your problem, you instead need to fully describe your problem. The reward, though, is it gives you a tailor-made solution. This targets stage 4: implement.

Write Tests

The reward for writing tests is incredible. I can change code with impunity and know that my tests will make sure I don’t screw up other code, assuming I wrote the tests well.

LLMs have gotten exceptionally good at writing tests. Even if you are someone who enjoys writing tests, use the LLM to make sure you have fully covered your code. Feed it your code and your test suite and ask it to tell you what tests you need for complete coverage. This targets stage 5: making sure you don’t break other code as you make changes.

Review My Code

LLMs have been trained on the world’s public (and sometimes non-public) sources. Every line of open source code, every line of documentation, every example ever created for every programming language. Furthermore, an LLM is always available and, as I pointed out above, never judgmental. What a perfect pair programmer! Next time you write some code, when you think you are done, feed it to the LLM and ask it what it thinks, what it would improve or change, ask it if you missed anything obvious (even if you know you didn’t). This targets stages 4 and 5: implementation and did I fix the problem.

Investigate the Best Solution

Senior engineers often perform the first three stages almost subconsciously. After a while the process is just baked into what they do. One of the issues, though, is sometimes senior engineers don’t stop long enough to listen and ask questions, ensuring they fully understand the problem before deciding on a solution. Junior engineers don’t generally perform these stages, but there is no faster way to become a senior engineer than to start. 

In this stage, instead of proclaiming your detailed solution to the problem, start with the problem itself. Explain to the LLM the problem you are trying to solve. Tell it not to write code; instead, ask it to discuss the best solution. Don’t share with it what you think the solution should be, but instead use your knowledge of the space to push at the edges of what it thinks is correct, redirecting it, expanding the scope, testing the edges of your own thinking in the process. For juniors, ask it what potential approaches we should take and why it picked the one it did. If the problem isn’t clear, ask it what else we might need to know in order to even decide on a path ahead. This targets stages 1, 2 and 3.

The Whole Enchilada

The real unlock happens when we start using LLMs for all five stages. While I wouldn’t start here — get comfortable with LLMs at your own pace and the level to which you are willing to let it participate — allowing an LLM to direct all five stages unlocks a speed and leverage that wasn’t possible before.

Start with Investigation as defined above. Align on a solution together, then align on an implementation plan. Work on each step of the plan together. As it changes code review what it is doing. I personally like to have it write in its own chat, leaving me to move it into my code base. That allows me to align it with my expectations.

I often go back and forth with it a few times, getting it to refine its approach. I test as I go manually. At each step I also work with it to write unit tests. And as I complete all implementation steps, I have it write integration tests, many of which I specifically define and direct it to write.

I’m still responsible for all the code. It’s my job to make sure that what outcome I wanted is what I actually got, and to ensure my test suite is complete. Same as it always was!

Metamorphosis

I’ve now built complex projects I never would have attempted on my own. I’ve been able to develop complex SQL queries when before I could really only accomplish simple ones. I’ve learned a ton about SOC compliance and row-level security for securing Postgres databases. I’ve built groundbreaking mathematical engines that drive a new way of doing math in the AI age. And I’ve done all this by myself. In the old days there were plenty of ideas I had and discarded because I did not have the skillset to build them myself.

Now? It’s incredibly liberating to realize that the only thing holding me back is my imagination.

How Apple’s App Store policies have contributed to the surveillance advertising industry

Image from The Verge

Apple’s goal is to sell more hardware, in particular sell more iPhones. That’s where the bulk of Apple’s revenue comes from even today. In order to sell more hardware, Apple wants to commoditize its complements and the biggest complement is apps. The irony is that this decision to destroy the value of software has led directly to their need to crack down on surveillance advertising. The law of unintended consequences at its finest.

How did we get here? This story starts all the way back in the mid-90s when Microsoft launched Windows 95 and Adobe, Pagemaker and Quark decided to support Windows.¹ For years before, Apple had paid huge co-marketing fees to keep these three mammoths of the software publishing business on Mac OS. But Apple’s market share was tiny and the digital publishing business, along with education, were the only two markets still using Macintosh computers. Apple was dying and everyone knew it.

Microsoft came along with Windows 95 and offered a lifeboat to these businesses. Port to Windows and you will be saved. They jumped at the chance.

Apple was the jilted lover and seems to have decided that if it ever survived that mess it would never put itself in that situation again, beholden to software companies. Instead, it would ensure that no one software company could ever get big enough and powerful enough to hurt Apple again.

25 years later and Apple of course is the largest company in the world, and has leveraged its dominant position in the technology industry to ensure no software company could ever challenge them again.

So Apple has been more than happy to see the impact of the App Store, and has been more than a willing participant in devaluing mobile software. Prices in the Palm OS and Windows Mobile days? Shareware titles were $20-30, commercial titles were $50 and more. My own products ranged in price from $10-$160 with our best products at $60. Average price was $37 net with most sales going through our website where we collected contact information and promoted upgrades.

In the iOS days? Prices started at $10 and quickly dropped from there. Within a year average prices were in the $1 range (before Apple took 30%) and most apps were free. The same apps I sold for $60+ on Palm and Windows Mobile were selling for $3.50 net on iOS with all sales going through Apple, no customer data, and no upgrades.

This helped sell lots of iPhones. All this cheap software made Apple’s hardware all the more valuable (commoditizing its complements). And Apple did little to help raise the price. No upgrade pricing, no trial versions, lax App Store policies. If your product failed, oh well for you as there were 100s of others willing to take your place.

The problem is all this cheap software required a business plan to make real money. If developers couldn’t make a living actually selling software, then they would make money selling you. They tracked your location, sharing information with the highest bidder. Because the income to stay in business had to come from somewhere and it wasn’t coming from selling software. Welcome to the surveillance advertising business.

(This is where I want to point out that I never did this and thus work full-time for someone else. Not every company did this and I’m in awe of those who made it through, survived this insanity, and still were able to build indie businesses.)

Even if these developers charged an up-front or subscription price, there was no mechanism to advertise and find out if someone purchased. While Apple could easily provide this capability, they don’t. So developers tracked consumers using the same mechanisms utilized in surveillance advertising. How will developers know if their advertising works now that tracking has been eliminated?

Now Apple cracks down on the surveillance advertising industry, blaming Facebook and others for the intrusion. Is Facebook to blame? Of course. But Apple is no innocent bystander in this insanity to track everything you do and everywhere you go in the name of selling you more crap.

Is Apple wrong to crack down on all this tracking? Of course not. It’s an abhorrent practice that should go away. But let’s not pretend that Apple’s App Store approach and policies hasn’t led us to this exact spot. The two go hand in hand.


¹ I know this story because a former Apple executive told it to me many many years ago.

Math doesn’t change

“Math doesn’t change,” the customer tells me as to why he doesn’t want to pay a subscription.

Yes, he is absolutely right. Math does not change. But iOS does. Constantly. And in order to keep that math working the app that that math works within does have to change. Otherwise it stops working.

We developed PowerOne Finance and PowerOne Scientific at the dawn of the iOS App Store. (Actually PowerOne pre-dates the App Store by more than a decade but that’s a different story.) At one point versions of PowerOne occupied 3 spots in the top 1000 apps with one of them occupying a slot in the top 100. As the App Store gold rush cooled, we couldn’t justify the investment in maintaining this old code and a one-time price so we started working on a new version we hoped would replace it and hoped we had a substantial number of customers who would move to the new version, propelling us up the charts, and driving enough subscribers to keep working full-time.

A few years ago we released PowerOne 6 and gave a great deal to existing customers to move over and, well, they didn’t.

At this point, the writing was on the wall. After 23 years of working on Infinity Softworks full-time, it was time to work for someone else. PowerOne was making enough money to keep it going, barely, but not enough to work even part-time.

Now I do a handful of releases per year, maintain the web servers, and am happy to work with a small group of customers who really enjoy the app. PowerOne Finance and PowerOne Scientific have continued to work all these years for anyone that already had a copy.

This week, however, Apple released iOS 14.5. For me, PowerOne Finance and PowerOne Scientific still worked. For some customers, though, it does not. A few customers reached out to me and asked what to do. I suggested they download PowerOne 6, explaining it was free with advanced features available for a subscription price of $19.99 per year.

Very politely, the customer responded that they don’t see a reason to pay a subscription because the “math doesn’t change.”

And he’s right. The math doesn’t change. But the platform on which the software runs is constantly changing. The server to sync templates across devices costs time and money. The earth in which that unchanging math lies is constantly shifting.

I’m really not upset. I’m sad but not upset.

I’m sad because the mobile landscape became hostile to running a small company with a niche product. I’m sad that I couldn’t figure out how to keep Infinity Softworks going as a full-time job after 23 years. I’m sad because I thought I had more customers that cared deeply for the product and would stick with me. And even that sadness isn’t the same as it was two years ago when I decided it was time to get a job. Then I was devastated. Now it is more of a lament.

While I’m not upset with this customer, it does strike me as ironic that the same person complaining that PowerOne Finance and PowerOne Scientific no longer works did so at the same time while lamenting a subscription price for software.

Because “the math doesn’t change.”