I can tell you, almost to the month, when building software started to feel narrow.
The work was still real. Auth still had to work. Data models still had to be honest. Users still needed a loop they could finish. But the shape of the products I was shipping had settled. Ingest something structured. Store it. Show it. Let someone click a button that ran a rule I had already written.
I got faster at that. TypeScript, Next.js, managed auth, background jobs. Those made me faster at a job I already understood. They did not change the job.
The last couple of years did. Not because I can scaffold a form faster. Because I can now put a judgment inside a product, at a specific step, and have the rest of the system remain ordinary software.
The line we used to stop at
Software has always been excellent at the parts of a problem we could specify completely.
If you could write the rule, you could automate it. Duplicate invoices. Overdue reminders. Role-based access. A cash projection from a ledger. Same input, same output, every time.
The rest of the work sat on the other side of a line we did not talk about much. Does this transaction look weird? What actually matters in this document? What changed in this campaign? Turn this specification into something a developer can use.
Those questions require interpretation. For a long time, interpretation meant a person. You could write heuristics, hire analysts, or leave the feature out. What you could not do, cheaply enough to ship, was put a useful judgment step inside the product itself.
A useful middle
There is now a place for that step. A model can look at an ambiguous case and return a structured judgment. Not a complete product. A judgment. Then the system continues: validate the output, put a human on the review queue if one belongs there, write the result into application state.
The model is not replacing the human. It is not becoming the system. It occupies one bounded uncertainty boundary.
I have already argued that the model should be the smallest part of the system. I still believe that. The excitement is that we finally have something worth putting in that one uncertain slot. The part of the problem that was never a good WHERE clause.
What that looks like when you actually ship it
I felt this most clearly while building FinCrew.
A finance product has always been good at calculation. Ingest transactions, compute burn, project runway. Duplicate payments and weekend spend are rules. You write predicates. You do not need Claude for "this vendor appeared twice on the same day."
The interesting remainder is the transaction that does not trip a rule and still looks off. That is a "does this look suspicious?" question. It is a bad database query. So the fraud pipeline is rules first. Deterministic signals fire on their own. Only the ambiguous remainder goes to Claude for a structured score and a short explanation. If the model is unavailable, the checks keep working. A review queue still exists. A human still decides.
ORACLE, the CFO assistant, is the same boundary from the other direction. It can only reach FinCrew's own services. Every metric carries a source. Numbers the system does not track stay unavailable rather than estimated. The model translates a question into tool calls against application state. Forecasting still does the arithmetic. What changed is that a what-if can sit next to a fraud alert in the same system.
DocPilot is a translation problem. An OpenAPI spec is already structured. Deterministic software can parse it. What it cannot do, on its own, is turn that spec into documentation, an FAQ, SDKs, and a mock server a developer can actually use.
The naive version dumps the spec into a prompt and asks for docs. That version invents endpoints. The version that shipped treats the spec, or a repository after classification and extraction, as source of truth. The model is one stage in a generation pipeline. It does not discover the API. It explains an API the system already extracted.
Peergrowth Pulse already had the hard parts of a growth product before anyone typed a sentence into a copilot. Live campaign data. Lead sync. Attribution that admits when Meta and Instant Forms disagree. CRM state that survives the next reconciliation. Those are conventional systems. They are also the reason the AI feature is worth having.
"Why did CPL increase?" is a terrible question to ask a model that only has a prompt. It is a reasonable question to ask a model that can call the same analytics primitives the dashboard already uses. The user is not pasting campaign data into a chat box. The product already knows. The model interprets what changed. It does not invent the numbers. Chat earns its keep on "what should I investigate?" Syncing leads does not.
I built Flowett around a related instinct. If model-based decisions are going to live inside products, they should be visible as steps in a workflow, not hidden inside a blob of prompt. A visual editor for composing AI pipelines, represented as a DAG, makes the architecture readable. Some nodes are deterministic. Some nodes are model calls. You can see where uncertainty sits. Once you can put judgment in a node, the interesting design work becomes how those nodes compose.
The work did not disappear
Traditional software engineering did not become obsolete. The boring parts matter more, because they are what keep a probabilistic step from becoming the whole system.
Auth is still auth. A unique constraint is still a unique constraint. Background ingest still has to be idempotent. You now have to decide, on purpose, which parts of a system are deterministic and which parts are allowed to be probabilistic. That decision used to be implicit: if you could not write the rule, you did not automate it. Now you can automate the ambiguous part, which means you are responsible for the boundary.
You have to give the model useful context. You have to validate outputs, because valid English is not a contract. You have to handle failure without pretending a downed model is a downed product. FinCrew's fraud rules keep running. Pulse's dashboard still shows numbers. You have to connect reasoning to real application state, through tools, schemas, and jobs, so a judgment becomes an alert or a document instead of a paragraph that dies in the UI.
These are design problems. They are also the reason the work feels like engineering again. The abstraction boundary moved. Some of the problem can stay ambiguous, on purpose, and still be part of the product.
Why this is fun again
I am not excited because I can generate a CRUD app faster. I could already generate a CRUD app.
I am excited because suspicious spend, a campaign that moved, a spec that wants to become docs, and a question a finance team would otherwise take to a spreadsheet can now sit inside a product as a bounded step. The product still has to be software. The step still has to be small. But the product can participate in the interpretation instead of stopping at the calculation.
The set of problems I can honestly treat as software has gotten bigger. That's what makes building software interesting again.
Related: The LLM Should Be the Dumbest Part of Your System · I Don't Want Your AI Copilot · Why Most AI Products Don't Need Agents · Schema-First LLM Systems · FinCrew · DocPilot · Peergrowth Pulse