<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Remo H. Jansen</title>
    <description>The latest articles on DEV Community by Remo H. Jansen (@remojansen).</description>
    <link>https://dev.to/remojansen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F24875%2F8708b5ec-0a58-4900-9c87-6f5ab2540dd7.png</url>
      <title>DEV Community: Remo H. Jansen</title>
      <link>https://dev.to/remojansen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/remojansen"/>
    <language>en</language>
    <item>
      <title>Career safety in the Age of AI Layoffs</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Sat, 05 Sep 2026 06:39:57 +0000</pubDate>
      <link>https://dev.to/remojansen/career-safety-in-the-age-of-ai-layoffs-4ich</link>
      <guid>https://dev.to/remojansen/career-safety-in-the-age-of-ai-layoffs-4ich</guid>
      <description>&lt;p&gt;There is a strange contradiction happening in software engineering right now. A lot of developers are worried that AI is going to make them obsolete. &lt;/p&gt;

&lt;p&gt;At the same time, the people building the most capable AI coding tools are demonstrating something that should probably make us rethink what being a software engineer actually means. I don't think the future is one where nobody understands software anymore. I think it is one where writing the software becomes dramatically cheaper. &lt;/p&gt;

&lt;p&gt;And if that happens, the thing that makes an engineer valuable has to move. That is what I mean by career safety. Career safety isn't about making yourself impossible to replace. It is about making your value portable.&lt;/p&gt;

&lt;h2&gt;
  
  
  We've always resisted giving up the code
&lt;/h2&gt;

&lt;p&gt;Developers have a long history of being suspicious of abstractions that take work away from us. We went from machine code to assembly, from assembly to higher-level languages, from manually managing memory to garbage collection, from building everything ourselves to libraries and frameworks, and from text editors to IDEs. &lt;/p&gt;

&lt;p&gt;We even had entire categories of tools, such as CASE tools, designed to automate parts of software development. And every time, there was resistance. Because programmers don't just use code. We build our identities around it. John Carmack captured this unusually well when he wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Coding” was never the source of value, and people shouldn’t get overly attached to it. — John Carmack&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He followed that with the more important point:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Problem solving is the core skill. — John Carmack&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a difficult idea for developers to internalize because coding is tangible. You can point at the repository. You can point at the pull request. You can count the commits. You can say, "I wrote this." &lt;/p&gt;

&lt;p&gt;But the business doesn't ultimately pay you for the number of lines you wrote. It pays you for what those lines accomplish.&lt;/p&gt;

&lt;h2&gt;
  
  
  The business never really bought the code
&lt;/h2&gt;

&lt;p&gt;A company doesn't wake up in the morning thinking: "We need 14,000 more lines of TypeScript." It thinks: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"We need to reduce the cost of this process."&lt;/li&gt;
&lt;li&gt;"We need to launch this product."&lt;/li&gt;
&lt;li&gt;"We need to increase conversion."&lt;/li&gt;
&lt;li&gt;"We need to satisfy this regulatory requirement."&lt;/li&gt;
&lt;li&gt;"We need to reduce operational risk."&lt;/li&gt;
&lt;li&gt;"We need to make our customers happier." &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is the mechanism. The outcome is the value. That distinction matters enormously when the cost of producing code starts approaching zero. &lt;/p&gt;

&lt;p&gt;And I think this is where the current AI discussion often goes wrong. The argument becomes: "AI can't write perfect code." Maybe. "AI still makes mistakes." Absolutely. "I had to spend an hour fixing what the AI generated." I've done that too. &lt;/p&gt;

&lt;p&gt;But those observations don't answer the important question. How quickly is the capability improving? The code AI generated a year ago was often laughably bad. Today's systems are substantially better. &lt;/p&gt;

&lt;p&gt;And the direction of travel matters more than whether today's models can replace every engineer. You don't need AI to become perfect for the economics of software development to change. You only need the cost of producing implementation to keep falling. &lt;/p&gt;

&lt;p&gt;Linus Torvalds recently made essentially this pragmatic argument about AI in the Linux kernel:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“AI is a tool, just like other tools we use. And it’s clearly a useful one.”— Linus Torvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He went further, saying that decisions in the kernel should be based on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“technical merit. Not fear of new tools.”— Linus Torvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction is incredibly important. The question isn't whether AI is morally good or bad. The question is: Does the tool help us produce better technology? &lt;/p&gt;

&lt;p&gt;If the answer increasingly becomes yes, then refusing to use it isn't protecting the profession. It is simply refusing a tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding code isn't the same as typing it
&lt;/h2&gt;

&lt;p&gt;This is where I think we need to separate two things that have become conflated. Understanding software and manually producing software are not the same skill. &lt;/p&gt;

&lt;p&gt;A compiler already transforms the code we write into something else. Libraries allow us to avoid implementing primitives ourselves. Frameworks give us enormous amounts of functionality without us writing the underlying infrastructure. Garbage collectors manage memory we previously had to manage ourselves. IDEs autocomplete, refactor and navigate code for us. &lt;/p&gt;

&lt;p&gt;Nobody seriously argues that using these tools means we aren't software engineers. AI is another step in the same direction. The difference is that this step is much larger. &lt;/p&gt;

&lt;p&gt;Instead of automating one small part of implementation, AI can potentially automate substantial portions of the implementation itself. That doesn't make understanding less important. It makes understanding more important. &lt;/p&gt;

&lt;p&gt;If I ask an AI system to build a payment workflow, I need to understand payments. If I ask it to design an insurance pricing system, I need to understand insurance. If I ask it to modify a distributed system, I need to understand distributed systems. &lt;/p&gt;

&lt;p&gt;The machine can increasingly help with the implementation. It cannot magically give me the context that determines whether the implementation is the right thing to build. And that leads to what I think will become one of the biggest career advantages of the next decade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain expertise becomes a moat
&lt;/h2&gt;

&lt;p&gt;Imagine two engineers. The first is: Senior Full Stack TypeScript Engineer. The second is: London Specialty Insurance Product Engineer. &lt;/p&gt;

&lt;p&gt;The first might be excellent at React, Node.js, TypeScript, databases and cloud infrastructure. The second understands specialty insurance. They understand brokers. They understand underwriting. They understand policies. They understand claims. They understand regulatory constraints. They understand how the business actually makes money. They understand which workflows are painful. They understand which problems are worth solving. &lt;/p&gt;

&lt;p&gt;If AI makes software implementation dramatically cheaper, which of those profiles becomes more differentiated? Probably the second. &lt;/p&gt;

&lt;p&gt;Because technology knowledge is increasingly becoming something AI can augment. Deep knowledge of a particular business domain is much harder to manufacture on demand. &lt;/p&gt;

&lt;p&gt;This doesn't mean you should abandon technical expertise. Quite the opposite. It means you should combine technical expertise with domain expertise. &lt;/p&gt;

&lt;p&gt;If you're working in finance, learn finance. If you're working in healthcare, learn healthcare. If you're working in insurance, learn insurance. Read the books. Understand the regulations. Talk to customers. Understand the economics. Learn how the industry actually works. &lt;/p&gt;

&lt;p&gt;I increasingly think engineers should be reading as many books about their industry as they read about technology.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engineer becomes a product person
&lt;/h2&gt;

&lt;p&gt;This also changes the relationship between engineering and product management. For a long time, organizations could afford a relatively clean separation. Product people decide what to build. Engineers decide how to build it. &lt;/p&gt;

&lt;p&gt;But what happens when the cost of implementation falls dramatically? The bottleneck moves. The scarce resource becomes knowing what should be built. &lt;/p&gt;

&lt;p&gt;That makes product judgment increasingly valuable for engineers. Can you understand a customer's problem? Can you translate it into a useful product? Can you understand the business case? Can you identify the constraints? Can you decide what not to build? Can you describe the desired outcome precisely enough that a machine can implement it? &lt;/p&gt;

&lt;p&gt;That last question is particularly important. Because the next generation of software engineering may look less like: "Give me a ticket and I'll write the code." And more like: "Give me a business problem and I'll turn it into a system of coordinated machines that can safely solve it."&lt;/p&gt;

&lt;h3&gt;
  
  
  The compression of the middle
&lt;/h3&gt;

&lt;p&gt;There is another consequence of AI that is worth considering: it may not only reduce the amount of implementation work required, but also compress some of the intermediate organizational layers around engineering.&lt;/p&gt;

&lt;p&gt;As AI makes it easier to gather information, analyze requirements, synthesize feedback, produce specifications, and coordinate work, roles such as analysts, Scrum Masters, and some forms of project or product coordination may become leaner. This doesn't mean these functions disappear, but fewer people may be needed to translate information between customers, product teams, and engineers.&lt;/p&gt;

&lt;p&gt;That makes communication an increasingly important engineering skill.&lt;/p&gt;

&lt;p&gt;An engineer who can speak directly with users, understand their problems, gather and synthesize feedback, identify patterns across different customers, and translate those patterns into general-purpose solutions could become considerably more valuable. Instead of simply implementing a requirement handed down through several layers of an organization, that engineer can participate directly in discovering the requirement.&lt;/p&gt;

&lt;p&gt;This also changes what it means to be good at product management. Product thinking isn't simply about writing requirements or managing a backlog; it involves understanding people and their problems, communicating effectively, distinguishing individual requests from broader patterns, and deciding which problems are worth solving.&lt;/p&gt;

&lt;p&gt;In a leaner organization, the engineer who can bridge the gap between &lt;strong&gt;user problem → product insight → technical solution&lt;/strong&gt; may have significantly more leverage than an engineer who only receives a specification and implements it.&lt;/p&gt;

&lt;p&gt;The interesting consequence is that AI may therefore make communication skills &lt;em&gt;more&lt;/em&gt; important for engineers, not less. As the number of intermediaries decreases, the ability to communicate directly with the people experiencing the problem becomes increasingly valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next engineering skill is operating agents
&lt;/h2&gt;

&lt;p&gt;I don't think the most interesting future is one where an engineer has one AI assistant open in their IDE. I think it is one where an engineer can operate a fleet of agents. &lt;/p&gt;

&lt;p&gt;One agent investigates the existing architecture. Another implements a feature. Another writes tests. Another reviews the implementation. Another performs security analysis. Another evaluates performance. Another updates documentation. Another runs experiments. And potentially dozens or hundreds of these processes can operate in parallel. &lt;/p&gt;

&lt;p&gt;At that point, the scarce skill isn't typing. It's orchestration. &lt;/p&gt;

&lt;p&gt;You need to know how to decompose a problem. How to specify work. How to establish boundaries. How to give agents the right context. How to structure systems so that work can happen safely in parallel. How to verify the results. How to detect when an agent has gone off course. How to roll back changes. How to evaluate whether the system actually achieved the intended outcome. &lt;/p&gt;

&lt;p&gt;In other words, the engineer moves up another level of abstraction. From writing code to causing valuable software to be produced. &lt;/p&gt;

&lt;p&gt;And I think this is where Linus's pragmatism becomes especially relevant. His position isn't "let the machines do whatever they want." It's essentially the opposite. Use the tool if it is useful. Judge the result on technical merit. Don't confuse opposition to the tool with engineering quality. &lt;/p&gt;

&lt;p&gt;The standard doesn't disappear because AI produced the code. If anything, the standard has to become more explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification becomes more important than generation
&lt;/h2&gt;

&lt;p&gt;There is an uncomfortable implication here. If one engineer can produce ten times as much implementation with AI, we cannot simply review ten times as much code manually. The old process breaks. &lt;/p&gt;

&lt;p&gt;Today we often have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human → code → human review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tomorrow we may have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human → specification → many agents → automated verification → human judgment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That requires a different engineering discipline. We need better architectures. Better tests. Better specifications. Better observability. Better evaluation. Better security controls. Better deployment boundaries. Better rollback mechanisms. Better ways of proving that the generated system does what we intended. &lt;/p&gt;

&lt;p&gt;This is why I don't think AI makes engineering less important. It makes engineering discipline more important. &lt;/p&gt;

&lt;p&gt;The amount of generated code may explode. Our ability to trust that code cannot depend on reading every line. We need systems that make correctness easier to verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what actually makes a career safe?
&lt;/h2&gt;

&lt;p&gt;This is the question I care about most. Because if coding becomes increasingly commoditized, simply becoming a better coder may not be enough. The answer, I think, is to build career capital in layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technical depth&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You still need to understand how software works. You need architecture. You need systems thinking. You need to understand databases, networks, distributed systems, security, reliability and the fundamentals of computing. AI doesn't remove the need for this knowledge. It gives you more leverage if you have it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Domain expertise&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Become unusually knowledgeable about something that matters to a business. Don't just be "a TypeScript developer." Become the engineer who understands a particular industry, market or class of problems deeply.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Product judgment&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Learn to identify valuable problems. Understand customers. Understand economics. Understand trade-offs. Understand why something should be built, not just how.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Specification&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Learn to turn ambiguous goals into precise systems and constraints. This may become one of the defining engineering skills of the AI era. The better you can specify a problem, the more leverage you can get from machines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verification&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Learn how to determine whether a system is actually correct. Testing. Evaluation. Observability. Security. Performance. Failure analysis. Rollback. These skills become more valuable as the amount of machine-generated output increases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reputation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Build proof that exists outside your current employer. Write. Build open source. Contribute. Speak. Teach. Publish. Create things people can see. Your employer can remove your job. They cannot remove the body of work you've built in public. That's what makes career capital portable.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Become replaceable at the task level
&lt;/h2&gt;

&lt;p&gt;There is a strange career lesson hidden in all of this. I don't think you should try to make yourself irreplaceable. You should try to make yourself replaceable at the task level. &lt;/p&gt;

&lt;p&gt;If an AI can write the CRUD endpoint you used to spend half a day implementing, great. Let it. If an agent can write the tests, great. Let it. If a tool can refactor 50 files in seconds, great. Let it. &lt;/p&gt;

&lt;p&gt;Your goal shouldn't be to protect those tasks. Your goal should be to move upward. &lt;/p&gt;

&lt;p&gt;From implementation to architecture. From architecture to product. From product to domain. From individual tasks to systems of work. From writing code to deciding what code should exist in the first place. &lt;/p&gt;

&lt;p&gt;The safest engineer isn't necessarily the one who can write the most code. It is the engineer who can create the most value when the cost of writing code approaches zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding was never the destination
&lt;/h2&gt;

&lt;p&gt;This is why I keep coming back to Carmack's observation. Coding was never the source of value. It was the mechanism through which we created value. &lt;/p&gt;

&lt;p&gt;For decades, writing software was expensive enough that the ability to write it well was itself a significant competitive advantage. AI changes that equation. And that is scary. But it is also liberating. &lt;/p&gt;

&lt;p&gt;Because it forces us to ask what we should have been asking all along: What are we actually here to do? &lt;/p&gt;

&lt;p&gt;We're here to solve problems. We're here to create useful products. We're here to improve businesses. We're here to make systems more reliable. We're here to reduce costs. We're here to create new possibilities. &lt;/p&gt;

&lt;p&gt;Code is one of the tools that allows us to do those things. It is not the thing itself. &lt;/p&gt;

&lt;p&gt;Linus's attitude toward AI is useful here because it cuts through a lot of the noise: Use the tool. Judge it on technical merit. Keep the engineering standards. And if the tool becomes genuinely useful, don't confuse refusing to use it with preserving engineering. &lt;/p&gt;

&lt;p&gt;The future engineer may write less code than today's engineer. They may spend more time understanding a business. More time talking to customers. More time designing systems. More time specifying problems. More time verifying results. More time coordinating machines. And potentially produce vastly more valuable software as a result. &lt;/p&gt;

&lt;p&gt;That's the career shift I'm preparing for. Not how do I make sure AI never replaces what I do? But: How do I become the person who can use AI to create more value than I could ever create by myself? &lt;/p&gt;

&lt;p&gt;Because career safety doesn't come from protecting the work that machines are learning to do. It comes from developing the judgment, expertise and reputation to remain valuable when the work itself changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update 1: Technical depth is still relevant
&lt;/h2&gt;

&lt;p&gt;Based on a comment by &lt;a class="mentioned-user" href="https://dev.to/routinekit"&gt;@routinekit&lt;/a&gt;, I think it is important to clarify one point: we can’t completely forget about technical expertise.&lt;/p&gt;

&lt;p&gt;I still believe that the implementation details — specific tools, frameworks, syntax, and even some programming languages — will become less important as AI becomes increasingly capable of handling them. But that doesn't mean technical knowledge becomes irrelevant. In some ways, the opposite is true.&lt;/p&gt;

&lt;p&gt;The fundamentals become more important. Understanding protocols, computer architecture, operating systems, networks, databases, distributed systems, security, and how software behaves at a deeper level gives you the context needed to judge whether an AI-generated solution is actually good.&lt;/p&gt;

&lt;p&gt;And there is another kind of knowledge that is much harder to automate: the intuition you develop after years of being burned by bugs in production.&lt;/p&gt;

&lt;p&gt;Knowing that a particular architecture &lt;em&gt;looks&lt;/em&gt; fine is one thing. Having an instinct that something is going to fail under load, that a seemingly harmless change could introduce a race condition, or that a system is going to become impossible to operate six months from now is something you develop through experience.&lt;/p&gt;

&lt;p&gt;AI can help us implement and investigate these problems, but someone still needs to recognize when something doesn't look right and know what questions to ask.&lt;/p&gt;

&lt;p&gt;So I don't think the future is about choosing between technical expertise and domain expertise. I think the most valuable engineers will combine both, deep technical fundamentals, strong domain knowledge, and the experience to use AI effectively while knowing when not to trust it.&lt;/p&gt;

&lt;p&gt;The syntax may change. The tools will certainly change. But understanding how computers and systems actually work. Having the scars to prove you've learned from production remains valuable.&lt;/p&gt;

</description>
      <category>career</category>
      <category>discuss</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Calling All Cloud Providers: Let’s Build a Sustainable Funding Model for Open Source</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Mon, 31 Aug 2026 13:47:12 +0000</pubDate>
      <link>https://dev.to/remojansen/calling-all-cloud-providers-lets-build-a-sustainable-funding-model-for-open-source-45p7</link>
      <guid>https://dev.to/remojansen/calling-all-cloud-providers-lets-build-a-sustainable-funding-model-for-open-source-45p7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Calling All Cloud Providers: Let’s Build a Sustainable Funding Model for Open Source&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open source powers an enormous part of the modern internet. Apache HTTP Server, Linux, Kubernetes, PostgreSQL, Python, NGINX, and thousands of other projects form the foundation of the applications and infrastructure that run on cloud platforms.&lt;/p&gt;

&lt;p&gt;Yet a clear economic disconnect remains: cloud providers generate billions from workloads built on open source, while many of the projects creating that value struggle to fund maintenance and development.&lt;/p&gt;

&lt;p&gt;There is a straightforward mechanism that could help close the gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if cloud providers shared a small percentage of cloud consumption attributable to open-source projects?
&lt;/h3&gt;

&lt;p&gt;Not as a new fee, surcharge, or software license, and without charging the customer anything extra. An open-source project could simply add a button in its GitHub repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Deploy to AWS — Support this project ]
[ Deploy to Azure — Support this project ]
[ Deploy to Google Cloud — Support this project ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The button deploys the project into the user’s own cloud account at the provider’s normal prices. The user pays exactly what they would otherwise pay. The cloud provider receives its normal revenue. The only difference is an attribution identifier that records:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This workload originated from this open-source project.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The provider could then voluntarily return, for example, &lt;strong&gt;1% of eligible cloud consumption&lt;/strong&gt; to the project.&lt;/p&gt;

&lt;p&gt;From the user’s perspective nothing changes: they keep their own cloud account, pay normal prices, and need not understand the attribution details. The important part is transparency—the project can clearly tell users that choosing a particular option may help support it through ordinary cloud usage.&lt;/p&gt;

&lt;h4&gt;
  
  
  An example
&lt;/h4&gt;

&lt;p&gt;A user clicks &lt;strong&gt;Deploy to Azure to support this project&lt;/strong&gt; and later spends $10,000/month on Azure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Customer’s Azure bill&lt;/td&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure keeps&lt;/td&gt;
&lt;td&gt;$9,900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSS project receives&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additional customer cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The project sells nothing. The user makes no donation. Azure charges nothing extra. The project simply receives a small share of the consumption it helped drive. If that deployment represents incremental revenue Azure might otherwise have lost, the provider has effectively spent $100 to acquire or retain $10,000 of monthly consumption—an attractive customer-acquisition cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  This is different from a cloud marketplace
&lt;/h3&gt;

&lt;p&gt;AWS, Azure, and Google Cloud Marketplaces already let companies sell software. That model is useful, but it does not address free and open-source projects that have no commercial license, subscription, paid listing, SaaS product, or software fee.&lt;/p&gt;

&lt;p&gt;Apache HTTP Server is free. The customer simply needs somewhere to run it. If they choose Azure, Azure earns the infrastructure revenue. Why shouldn’t a tiny portion of that revenue flow back to the project that helped make the workload possible? This is not about turning open source into a paid product. It is about recognizing that open-source projects can influence where and how cloud infrastructure is consumed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Think of it as an OSS affiliate program
&lt;/h3&gt;

&lt;p&gt;Affiliate marketing is a familiar pattern: a site refers a customer to Amazon; the customer buys something; Amazon shares a percentage of the revenue. The affiliate did not manufacture the product—it simply helped generate the transaction.&lt;/p&gt;

&lt;p&gt;Cloud revenue sharing for open source could work the same way, except the referral is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“My open-source project helped generate or influence a cloud workload.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of a one-time payment, the project could receive a small percentage of resulting consumption for a defined period. The exact percentage and duration would be set by the provider. &lt;strong&gt;1% is only an illustrative example of a sufficiently small share.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud providers already have pieces of the technology
&lt;/h3&gt;

&lt;p&gt;This is not purely hypothetical. Microsoft Azure’s Customer Usage Attribution (CUA), for example, associates Azure usage with partner solutions and deployments at the time of deployment. That capability is very close to the core technical requirement: knowing which project influenced a particular cloud deployment.&lt;/p&gt;

&lt;p&gt;The missing piece is the economic incentive. Attribution already helps providers understand the origin of consumption. Why not use it to create a sustainability mechanism for open source?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why would a cloud provider do this?
&lt;/h3&gt;

&lt;p&gt;Giving away 1% of revenue looks like a cost at first glance. The better framing is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would spending $10 increase the probability of generating or retaining $1,000 of cloud consumption?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is yes, the program is an interesting customer-acquisition and retention tool. Open source already possesses a vast distribution network—GitHub, documentation, tutorials, Stack Overflow, package repositories, Docker images, Kubernetes manifests, Helm charts, Terraform modules, blog posts, conferences, and developer communities. Cloud providers compete to become the place where that software runs. Giving the software itself an economic incentive to make cloud deployment easier is a natural extension of that competition.&lt;/p&gt;

&lt;h3&gt;
  
  
  The flywheel
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp3j8brqr1konj3unlwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmp3j8brqr1konj3unlwc.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maintainers currently rely on donations, GitHub Sponsors, corporate sponsorship, consulting, support contracts, foundations, grants, employment, dual licensing, or venture funding. Those channels remain valuable. Cloud revenue sharing would simply add another path that links real-world usage directly to sustainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  This could change the economics of open source
&lt;/h3&gt;

&lt;p&gt;One of the largest problems in open source is that usage and funding are poorly correlated. A project can have millions of users and still struggle financially. Cloud revenue sharing would create a tighter relationship:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;usage → value → sustainability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;instead of&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;usage → popularity → hope someone sponsors us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It would not replace existing models; it would complement them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attribution should favor the projects that need it most
&lt;/h3&gt;

&lt;p&gt;The hardest practical question is attribution. When a user deploys an application that sits on top of Kubernetes, Ubuntu, PostgreSQL, Terraform, and a dozen other layers, who should receive credit?&lt;/p&gt;

&lt;p&gt;In practice, the core infrastructure projects are often relatively well-funded. Foundations, corporate sponsors, and large user bases already support many of them. The projects that most often see zero support are the higher-level applications, libraries, tools, and domain-specific projects that actually put the “Deploy to Cloud” button in front of the user.&lt;/p&gt;

&lt;p&gt;That observation points to a simple and intentional design choice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attribute the revenue share to the project that provided the explicit Deploy action.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx1rn4xo1ap8fzpwb7aeq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx1rn4xo1ap8fzpwb7aeq.png" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The system does not try to split value across every layer of the stack. It does not attempt to decide whether Ubuntu, PostgreSQL, or Terraform “deserve” a cut. It only records a clear, measurable relationship: this deployment was initiated through &lt;em&gt;this&lt;/em&gt; project’s channel.&lt;/p&gt;

&lt;p&gt;That design has three advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It directs funding toward the projects that are actively choosing to participate and that most often lack other funding.&lt;/li&gt;
&lt;li&gt;It is technically straightforward—no complex dependency graph or value-allocation algorithm is required.&lt;/li&gt;
&lt;li&gt;It remains transparent to the user: “If you choose this deployment option, your normal cloud usage may help support &lt;em&gt;this&lt;/em&gt; project.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More sophisticated multi-project attribution could be explored later if the community wants it. But the first version should deliberately prioritize the project that put the button in front of the user. That is usually the project that needs the support.&lt;/p&gt;

&lt;h3&gt;
  
  
  It does not have to be 1%
&lt;/h3&gt;

&lt;p&gt;1% is merely an example. The share could be 0.1%, 0.5%, 2%, or tiered. The essential principle is that it remains small relative to the consumption generated or influenced by the project.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Monthly cloud consumption&lt;/th&gt;
&lt;th&gt;1% OSS share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$1,000&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$100,000&lt;/td&gt;
&lt;td&gt;$1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$1 million&lt;/td&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$10 million&lt;/td&gt;
&lt;td&gt;$100,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a successful project this can become meaningful funding. For the provider the percentage is tiny compared with the associated consumption. The precise economics can be tested experimentally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Existing vs. incremental consumption
&lt;/h3&gt;

&lt;p&gt;A distinction between attributed and incremental consumption is useful. If a customer already spending $10,000/month on Azure simply clicks the support button, the provider has not necessarily acquired new revenue. Different share rates (or time-limited payments) can therefore apply to new customers, new workloads, cloud switches, or true incremental consumption versus existing usage. A time-limited window—for example the first 12 months after deployment—would further position the program as a customer-acquisition and developer-ecosystem initiative rather than a permanent royalty.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open-source neutrality matters
&lt;/h3&gt;

&lt;p&gt;If a project offered “Deploy to us and we get 1%” buttons for AWS, Azure, and Google Cloud, it would have financial relationships with multiple providers. That can be healthy provided the project remains neutral. Provider neutrality should therefore be a core design principle: all participating clouds support the same public attribution standard and offer comparable participation mechanisms. The project can then present equal options without endorsing any single provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate attribution from payment
&lt;/h3&gt;

&lt;p&gt;It is useful to establish an open attribution standard independently of any financial program—something along the lines of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OSS Project ID + Repository ID + Deployment ID + Cloud Provider + Timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Providers receive the metadata. Commercial agreements then decide whether and how much money is paid. This separation makes experimentation easier. Stakeholders need not agree immediately on a universal percentage; they can first agree on how to identify and measure open-source deployment influence. Once the data exists, different economic models can be tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if open source became a cloud distribution channel?
&lt;/h3&gt;

&lt;p&gt;Today cloud providers compete for developers. Open-source projects already have those developers. If major projects could say “Deploy this on Azure / AWS / Google Cloud to support our project,” the provider would no longer be merely hosting open source—it would be economically partnering with it. The project would have a direct incentive to make cloud deployment easier, more reliable, and more discoverable. The result is a powerful alignment: providers want workloads, developers want easy deployment, and projects want sustainable funding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s try it
&lt;/h3&gt;

&lt;p&gt;I am not asking providers to make cloud computing cheaper, donate billions, or raise customer prices. I am proposing something much smaller:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give open-source projects a way to participate in the cloud economics they help create.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start with a limited experiment. Select a handful of major projects, give them a deployment attribution mechanism, allow transparent “Deploy to … to support this project” buttons, measure the resulting consumption, and return a small percentage of eligible usage. Then evaluate: Did the program drive incremental consumption? Influence cloud choice? Improve adoption or retention? Provide meaningful funding? If it works, expand it. If it does not, stop. Relative to the size of the cloud market, that is a modest experiment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bigger opportunity
&lt;/h3&gt;

&lt;p&gt;The real opportunity may be larger than any particular percentage. Open-source projects form a major part of the developer distribution network. They influence what developers install, what technologies companies adopt, what infrastructure teams deploy, and ultimately where workloads run. Yet the economic value of that influence is largely invisible.&lt;/p&gt;

&lt;p&gt;Cloud providers measure customers, workloads, and consumption. Open-source communities measure downloads, stars, contributors, and deployments. What is missing is a bridge between those two worlds. A standardized attribution mechanism—&lt;strong&gt;open-source project → deployment → cloud consumption&lt;/strong&gt;—would create that bridge. Once the connection exists, many economic models become possible: 1%, 0.1%, fixed bounties for new customers, shares of incremental consumption, time-limited referral payments, and more. The exact model can evolve. The essential step is creating the connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  A call to action
&lt;/h3&gt;

&lt;p&gt;If you work at &lt;strong&gt;AWS, Azure, or Google Cloud&lt;/strong&gt; on developer relations, partnerships, cloud economics, Marketplace, open source, or growth: I would like to hear your thoughts.&lt;/p&gt;

&lt;p&gt;If you maintain an open-source project: Would you add a “Deploy to Cloud to support this project” button if it could generate recurring funding?&lt;/p&gt;

&lt;p&gt;If you are a cloud customer: Would knowing that your normal usage could help fund an open-source project influence which cloud you choose?&lt;/p&gt;

&lt;p&gt;Attribution may prove complicated. 1% may be too high or 0.1% may be sufficient. The economics may work only for new customers. Revenue sharing may not turn out to be the best model. But the underlying question is worth asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can we turn a small fraction of cloud consumption into a sustainable funding mechanism for the open-source software that makes that consumption possible?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cloud providers already have the customers. Open source already has the developers. The technology to attribute deployments is increasingly available. What is missing is the incentive model. Let’s build it.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>opensource</category>
      <category>discuss</category>
      <category>community</category>
    </item>
    <item>
      <title>I’m Looking for the Right Problem, Not the Right Job Title — Could It Be You?</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Sat, 29 Aug 2026 23:37:13 +0000</pubDate>
      <link>https://dev.to/remojansen/im-looking-for-the-right-problem-not-the-right-job-title-could-it-be-you-1baa</link>
      <guid>https://dev.to/remojansen/im-looking-for-the-right-problem-not-the-right-job-title-could-it-be-you-1baa</guid>
      <description>&lt;p&gt;This is a slightly unusual post.&lt;/p&gt;

&lt;p&gt;After almost &lt;strong&gt;20 years of writing software&lt;/strong&gt;, creating and leading an open-source project, writing multiple books, spending several years as a &lt;strong&gt;Microsoft MVP&lt;/strong&gt;, and generally spending far too much of my free time thinking about developer tools, I've reached a point where I think I know what I want to do next.&lt;/p&gt;

&lt;p&gt;And I'm not entirely sure what the job title is.&lt;/p&gt;

&lt;p&gt;So instead of starting with a job title, I'm going to describe the kind of work I'd love to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm looking for a role where I can combine technical depth, API design, developer experience, product thinking and technical leadership.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If that sounds like something your team needs, I'd love to hear from you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two decades of building software
&lt;/h2&gt;

&lt;p&gt;I've been coding for almost 20 years now.&lt;/p&gt;

&lt;p&gt;Over that time I've worked across quite a broad part of the software development landscape, but I've increasingly gravitated towards one particular problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do we make software easier and more enjoyable for other developers to build?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've written multiple books, spent several years as a Microsoft MVP, spoken and written about technology, and built open-source software used by developers around the world.&lt;/p&gt;

&lt;p&gt;You can find some of my books &lt;a href="https://www.amazon.co.uk/stores/author/B015LX8AZM" rel="noopener noreferrer"&gt;on Amazon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking back, these experiences all taught me different parts of the same thing.&lt;/p&gt;

&lt;p&gt;Writing books taught me how to explain complex technical ideas.&lt;/p&gt;

&lt;p&gt;Being a Microsoft MVP taught me how much I enjoy sharing knowledge and participating in a technical community.&lt;/p&gt;

&lt;p&gt;And building open-source software taught me something I didn't expect: how much I enjoy designing and evolving developer tools as products and ecosystems.&lt;/p&gt;

&lt;p&gt;The project that has shaped me the most is &lt;strong&gt;Inversify&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Inversify
&lt;/h1&gt;

&lt;p&gt;I created Inversify and have developed it from the ground up.&lt;/p&gt;

&lt;p&gt;It started as an experiment around dependency injection in TypeScript and Node.js, but over the years it became a much bigger education in both technology and developer experience.&lt;/p&gt;

&lt;p&gt;Working on Inversify meant going very deep into things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript's type system&lt;/li&gt;
&lt;li&gt;decorators&lt;/li&gt;
&lt;li&gt;runtime metadata&lt;/li&gt;
&lt;li&gt;reflection&lt;/li&gt;
&lt;li&gt;compiler integration&lt;/li&gt;
&lt;li&gt;JavaScript and Node.js runtime behaviour&lt;/li&gt;
&lt;li&gt;module systems&lt;/li&gt;
&lt;li&gt;API design&lt;/li&gt;
&lt;li&gt;backwards compatibility&lt;/li&gt;
&lt;li&gt;versioning&lt;/li&gt;
&lt;li&gt;performance&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;developer experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But that isn't actually the part of the experience I value most.&lt;/p&gt;

&lt;p&gt;The other side of building Inversify was learning how to &lt;strong&gt;run and evolve an open-source developer ecosystem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When thousands of developers are using an API, designing the API is only part of the job.&lt;/p&gt;

&lt;p&gt;You have to learn how to listen to users without allowing the loudest user to define the roadmap.&lt;/p&gt;

&lt;p&gt;You have to distinguish:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I want this feature"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"There is actually a deeper problem with the abstraction."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You have to make pragmatic decisions when there isn't a perfect technical solution.&lt;/p&gt;

&lt;p&gt;You have to work with contributors.&lt;/p&gt;

&lt;p&gt;You have to communicate breaking changes.&lt;/p&gt;

&lt;p&gt;You have to think about migration paths.&lt;/p&gt;

&lt;p&gt;You have to maintain compatibility.&lt;/p&gt;

&lt;p&gt;You have to decide what &lt;em&gt;not&lt;/em&gt; to build.&lt;/p&gt;

&lt;p&gt;And you have to maintain trust with a community.&lt;/p&gt;

&lt;p&gt;In hindsight, Inversify became an accidental education in &lt;strong&gt;developer-tooling product and program management&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Experimenting with developer experience
&lt;/h1&gt;

&lt;p&gt;One of the things I've enjoyed most throughout this journey has been experimenting with new ways of making developers' lives easier.&lt;/p&gt;

&lt;p&gt;For example, &lt;strong&gt;inversify-express-utils&lt;/strong&gt; was one of my early experiments with decorator-based routing and controller APIs for Node.js.&lt;/p&gt;

&lt;p&gt;The basic idea was to let developers express an HTTP API using TypeScript classes and decorators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;httpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the time, decorator-driven routing and controller APIs were still relatively novel in the Node.js/TypeScript ecosystem.&lt;/p&gt;

&lt;p&gt;Today, this style of API is completely mainstream.&lt;/p&gt;

&lt;p&gt;That experience was incredibly interesting to me, not because of the particular implementation, but because of the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify a developer problem.&lt;/li&gt;
&lt;li&gt;Imagine a better API.&lt;/li&gt;
&lt;li&gt;Build a prototype.&lt;/li&gt;
&lt;li&gt;Put it in front of real developers.&lt;/li&gt;
&lt;li&gt;Listen to what they do with it.&lt;/li&gt;
&lt;li&gt;Discover where the abstraction works and where it doesn't.&lt;/li&gt;
&lt;li&gt;Iterate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've gone through versions of that cycle repeatedly over the years.&lt;/p&gt;

&lt;p&gt;And I've realised that &lt;strong&gt;I really enjoy it.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  From Inversify to RFLCT
&lt;/h1&gt;

&lt;p&gt;More recently, I've started thinking about something much closer to the underlying language and tooling.&lt;/p&gt;

&lt;p&gt;Inversify has historically relied heavily on decorators and runtime metadata.&lt;/p&gt;

&lt;p&gt;After working with that model for years, I started wondering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What if framework metadata could be expressed through TypeScript's type system instead?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question led me to start experimenting with &lt;strong&gt;RFLCT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is still evolving, but imagine something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rflct&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Shape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;sides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Polygon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the compiler generates metadata automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@acme/shapes@1|src/geo.ts|Shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Polygon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:paramtypes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:properties&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:paramtypes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:symbols&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:symbols&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@acme/shapes@1|src/geo.ts|Shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@acme/shapes@1|src/geo.ts|Polygon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part isn't really the syntax.&lt;/p&gt;

&lt;p&gt;It's the architectural question behind it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can a universal tooling layer understand relationships expressed through TypeScript's type system and turn them into runtime metadata, while leaving the meaning of that metadata entirely to the consuming framework?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Inversify could define what &lt;code&gt;postConstruct&lt;/code&gt;, &lt;code&gt;preDestroy&lt;/code&gt;, &lt;code&gt;injectFromBase&lt;/code&gt;, &lt;code&gt;injectFromHierarchy&lt;/code&gt;, etc. mean.&lt;/p&gt;

&lt;p&gt;Another framework could define completely different metadata.&lt;/p&gt;

&lt;p&gt;RFLCT doesn't need to understand either framework.&lt;/p&gt;

&lt;p&gt;This is the kind of problem I've increasingly found myself drawn towards:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the boundary between programming languages, frameworks, APIs, tooling and developer experience.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  What I think I'm actually good at
&lt;/h1&gt;

&lt;p&gt;I've spent most of my career writing software, and I'm very comfortable doing it.&lt;/p&gt;

&lt;p&gt;But looking back at everything I've worked on, I think my highest-leverage skill is increasingly somewhere else.&lt;/p&gt;

&lt;p&gt;I really enjoy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understanding difficult developer problems&lt;/li&gt;
&lt;li&gt;designing APIs&lt;/li&gt;
&lt;li&gt;designing abstractions&lt;/li&gt;
&lt;li&gt;thinking about programming-language constraints&lt;/li&gt;
&lt;li&gt;exploring new developer experiences&lt;/li&gt;
&lt;li&gt;talking to users and contributors&lt;/li&gt;
&lt;li&gt;collecting and interpreting feedback&lt;/li&gt;
&lt;li&gt;turning conflicting feedback into a coherent direction&lt;/li&gt;
&lt;li&gt;deciding what should and shouldn't be built&lt;/li&gt;
&lt;li&gt;prototyping ideas&lt;/li&gt;
&lt;li&gt;thinking about long-term API evolution&lt;/li&gt;
&lt;li&gt;connecting technical possibilities with actual developer needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still enjoy implementation.&lt;/p&gt;

&lt;p&gt;But increasingly, I think &lt;strong&gt;I can create more value by deciding what we should build and why, and by designing the abstraction that makes it possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don't want to become a non-technical product manager who throws requirements over the wall to engineers.&lt;/p&gt;

&lt;p&gt;Quite the opposite.&lt;/p&gt;

&lt;p&gt;I want to remain deeply technical.&lt;/p&gt;

&lt;p&gt;I want to be able to understand the compiler implications of an API, prototype something when necessary, discuss architecture with engineers, understand the constraints, and get into the code when that's the fastest way to answer a question.&lt;/p&gt;

&lt;p&gt;But I would like my &lt;strong&gt;primary leverage to come increasingly from technical design, product thinking and direction rather than from implementing every feature myself.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  So what am I looking for?
&lt;/h1&gt;

&lt;p&gt;I'm honestly not completely sure what the job title should be.&lt;/p&gt;

&lt;p&gt;It might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technical Product Manager&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Experience Product Manager&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Platform Lead&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lead / Principal Engineer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Tools Engineer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Productivity Engineer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical Program Manager&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Ecosystem Lead&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;or something I haven't thought of yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The title matters less to me than the actual work.&lt;/p&gt;

&lt;p&gt;I'm looking for a role where I can sit at the intersection of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;developers × APIs × technology × product × developer experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and help figure out what the next thing should look like.&lt;/p&gt;

&lt;p&gt;I'm particularly interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript / JavaScript&lt;/li&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;developer tooling&lt;/li&gt;
&lt;li&gt;compilers and language tooling&lt;/li&gt;
&lt;li&gt;developer experience&lt;/li&gt;
&lt;li&gt;APIs and SDKs&lt;/li&gt;
&lt;li&gt;frameworks&lt;/li&gt;
&lt;li&gt;developer platforms&lt;/li&gt;
&lt;li&gt;programming-language-adjacent infrastructure&lt;/li&gt;
&lt;li&gt;open-source ecosystems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love to work somewhere where &lt;strong&gt;developer tooling itself is the product&lt;/strong&gt;, rather than something that happens to be built internally.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I'd love to do
&lt;/h1&gt;

&lt;p&gt;My ideal day would probably involve some combination of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Developers are struggling with X. Why?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;followed by:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What would the API look like if we solved this properly?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;followed by:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can we actually make TypeScript, the compiler or the runtime do that?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;followed by:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let's prototype it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let's put it in front of developers and see what happens."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I love that loop.&lt;/p&gt;

&lt;p&gt;I'd love to do it with a team of people who are much smarter than me in their respective areas, where I can contribute the combination of &lt;strong&gt;technical depth, API design, developer empathy and years of experience operating an open-source ecosystem.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  What I bring
&lt;/h1&gt;

&lt;p&gt;I think my background gives me an unusual combination of experiences.&lt;/p&gt;

&lt;p&gt;I've spent more than 20 years writing software.&lt;/p&gt;

&lt;p&gt;I've created and built a major open-source TypeScript project from the ground up.&lt;/p&gt;

&lt;p&gt;I've spent years working directly with developers and contributors.&lt;/p&gt;

&lt;p&gt;I've designed APIs that have been used in real-world applications.&lt;/p&gt;

&lt;p&gt;I've experimented with new developer-experience patterns.&lt;/p&gt;

&lt;p&gt;I've written multiple technical books.&lt;/p&gt;

&lt;p&gt;I've been a Microsoft MVP.&lt;/p&gt;

&lt;p&gt;And along the way, I've ended up going very deep into areas I originally had no particular intention of becoming an expert in:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;decorators, metadata, reflection, type systems, compiler integration and the relationship between compile-time types and runtime JavaScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't set out to become a TypeScript language/tooling expert.&lt;/p&gt;

&lt;p&gt;I was trying to build useful developer tools.&lt;/p&gt;

&lt;p&gt;The technical depth came from having to solve the problems those tools created.&lt;/p&gt;

&lt;p&gt;And I think that's an important part of what I can offer.&lt;/p&gt;




&lt;h1&gt;
  
  
  I'm looking for the right problem, not just the right title
&lt;/h1&gt;

&lt;p&gt;I could update my CV and start applying to jobs.&lt;/p&gt;

&lt;p&gt;But I'm not sure that's the best way to find this particular kind of role.&lt;/p&gt;

&lt;p&gt;I'm not looking for just any job.&lt;/p&gt;

&lt;p&gt;I'm looking for a team where this rather unusual combination of experience is useful.&lt;/p&gt;

&lt;p&gt;I've spent years building a developer tool from scratch, evolving its APIs, working with its community, dealing with real-world developer feedback and becoming deeply familiar with the technical machinery underneath TypeScript and Node.js.&lt;/p&gt;

&lt;p&gt;I suspect there are teams out there where that experience would be extremely useful.&lt;/p&gt;

&lt;p&gt;I just don't necessarily know where all of them are.&lt;/p&gt;

&lt;p&gt;So I'm hoping that someone reading this will think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Wait, this sounds like something our team needs."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe that's at a large company working on developer infrastructure.&lt;/p&gt;

&lt;p&gt;Maybe it's a team working on TypeScript, JavaScript, Node.js, VS Code, developer productivity or developer platforms.&lt;/p&gt;

&lt;p&gt;Maybe it's a company I've never heard of.&lt;/p&gt;

&lt;p&gt;Maybe it's a role that doesn't exist yet.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where?
&lt;/h1&gt;

&lt;p&gt;I'm based in the &lt;strong&gt;Irish countryside&lt;/strong&gt;, so I can only consider &lt;strong&gt;fully remote roles&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm absolutely fine with occasional travel for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;team gatherings&lt;/li&gt;
&lt;li&gt;conferences&lt;/li&gt;
&lt;li&gt;planning sessions&lt;/li&gt;
&lt;li&gt;customer or developer meetings&lt;/li&gt;
&lt;li&gt;important face-to-face events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm just not looking for a role that requires regular commuting to an office.&lt;/p&gt;

&lt;p&gt;Remote-first is therefore an important constraint for me.&lt;/p&gt;




&lt;h1&gt;
  
  
  If this sounds like you
&lt;/h1&gt;

&lt;p&gt;If you work on developer tooling, TypeScript, Node.js, compilers, developer productivity, APIs, SDKs, frameworks or developer platforms, &lt;strong&gt;I'd genuinely love to hear from you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Especially if you work somewhere that values people who can move between:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;technical architecture ↔ API design ↔ developer experience ↔ product direction ↔ open-source/community&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you know of a team that might be a good fit, please reach out!&lt;/p&gt;

</description>
      <category>career</category>
      <category>typescript</category>
      <category>whoishiring</category>
      <category>devrel</category>
    </item>
    <item>
      <title>AI Code Is No Longer Slop</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Sat, 29 Aug 2026 07:39:55 +0000</pubDate>
      <link>https://dev.to/remojansen/ai-code-is-no-longer-slop-41hg</link>
      <guid>https://dev.to/remojansen/ai-code-is-no-longer-slop-41hg</guid>
      <description>&lt;p&gt;In December 2025 I wrote that Claude Opus 4.5 changed everything. I had been using AI coding tools since the beginning. They were useful, but they were also unreliable. The working pattern was defensive: stage every change, review every prompt, stay on feature branches, lean hard on CI, and be ready to roll back.&lt;/p&gt;

&lt;p&gt;Then I spent serious time with Opus 4.5.&lt;/p&gt;

&lt;p&gt;What shifted was not that the model suddenly produced perfect software. It still didn’t. What shifted was the economics of my own time. I could describe &lt;em&gt;why&lt;/em&gt; I wanted something instead of spelling out every implementation detail. The agent could plan, write, test, and iterate. I spent more of my day reviewing results, thinking about architecture, and deciding what should be built next.&lt;/p&gt;

&lt;p&gt;I wrote at the time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Suddenly, when you are running 6 AI agents in parallel, it is like freaking horizontally scaling yourself.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was my personal inflection point — the moment AI-assisted development stopped feeling like a productivity experiment and started feeling like a fundamental change in how software could be built.&lt;/p&gt;

&lt;p&gt;It was still only my experience. I could have been unusually tolerant of AI-generated code. Then July 2026 happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Linux moment
&lt;/h2&gt;

&lt;p&gt;On July 14, 2026, Linus Torvalds replied to a discussion on the Linux kernel mailing list about LLM-based tooling for maintainers. The thread included familiar anti-LLM concerns and the question of whether such tools should simply be avoided.&lt;/p&gt;

&lt;p&gt;Torvalds was direct:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Linux is not one of those anti-AI projects”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“AI is a tool, just like other tools we use. And it’s clearly a useful one.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a random developer saying AI is handy. This is the person who created and still shepherds the Linux kernel.&lt;/p&gt;

&lt;p&gt;That is why this matters more than my December experience. My moment was personal. This is a project-level signal from one of the most consequential figures in open-source software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read the original
&lt;/h3&gt;

&lt;p&gt;I’m not going to paraphrase the email and pretend the surrounding context is irrelevant. Read the full message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Subject: Re: Linking Patchwork with Sashiko?&lt;br&gt;
Date: Tue, 14 Jul 2026 20:06:14 -0700 [thread overview]&lt;br&gt;
Message-ID:  (raw)&lt;br&gt;
In-Reply-To: &lt;a href="mailto:4928C919-7999-4E76-ADCB-F8643FED105B@linux.dev"&gt;4928C919-7999-4E76-ADCB-F8643FED105B@linux.dev&lt;/a&gt;&lt;br&gt;
On Tue, 14 Jul 2026 at 19:01, Roman Gushchin &lt;a href="mailto:roman.gushchin@linux.dev"&gt;roman.gushchin@linux.dev&lt;/a&gt; wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I think it makes the point of sashiko - helping maintainers - unachievable. If the point to not use&lt;br&gt;
LLMs in general, let’s discuss this, not how to make each use case more complex.&lt;/p&gt;

&lt;p&gt;It seems like [1]  expresses a very anti-LLM position in general&lt;br&gt;
Yes.&lt;br&gt;
And no, that's not the position of the Linux kernel.&lt;br&gt;
I realize that some people really dislike AI, but this is an area&lt;br&gt;
where I'm willing to absolutely put my foot down as the top-level&lt;br&gt;
maintainer.&lt;br&gt;
Linux is not one of those anti-AI projects, and if somebody has issues&lt;br&gt;
with that, they can do the open-source thing and fork it.&lt;br&gt;
Or just walk away.&lt;br&gt;
AI is a tool, just like other tools we use.  And it's clearly a useful one.&lt;br&gt;
It may not have been that "clearly" even just a year ago, but it's no&lt;br&gt;
longer in question today.&lt;br&gt;
There are other questions around AI (like what the economy of it will&lt;br&gt;
actually look like in the end), but "is it useful" is no longer one of&lt;br&gt;
those questions. Anybody who doubts that clearly hasn't actually used&lt;br&gt;
it.&lt;br&gt;
Yes, it can also be a somewhat painful tool, both for maintainer&lt;br&gt;
workloads and just from a "it keeps finding embarrassing bugs"&lt;br&gt;
standpoint.&lt;br&gt;
But the solution is not to put your head in the sand and sing "La La&lt;br&gt;
La, I can't hear you" at the top of your voice like some people seem&lt;br&gt;
to do.&lt;br&gt;
The solution is to make sure those LLM tools &lt;em&gt;help&lt;/em&gt; maintainers&lt;br&gt;
instead of just causing them pain. There's no question on that side.&lt;br&gt;
We're not forcing anybody to use it, but I will very loudly ignore&lt;br&gt;
people who try to argue against other people from using it.&lt;br&gt;
And no, AI isn't perfect. But Christ, anybody who points to the&lt;br&gt;
problems at AI had better be looking in the mirror and pointing at&lt;br&gt;
themselves at the same time.&lt;br&gt;
Because it's not like natural intelligence is always all that great either.&lt;br&gt;
The kernel project has been and will continue to be about the technology.&lt;br&gt;
Sure, the social angle of working on open source is important and&lt;br&gt;
often a very motivating part of the project, but in the end that's a&lt;br&gt;
side benefit, not the &lt;em&gt;point&lt;/em&gt; of the project.&lt;br&gt;
This is &lt;em&gt;NOT&lt;/em&gt; some kind of "social warrior" project, never has been,&lt;br&gt;
and never will be.&lt;br&gt;
In the kernel community we do open source because it results in better&lt;br&gt;
technology, not because of religious reasons.&lt;br&gt;
And so we make decisions primarily based on technical merit. Not fear&lt;br&gt;
of new tools.&lt;br&gt;
              Linus&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/blockquote&gt;

&lt;p&gt;What struck me most is that Torvalds does &lt;strong&gt;not&lt;/strong&gt; claim AI is perfect. He talks about the problems it creates — embarrassing bugs, extra workload for maintainers, the risk of noise. His conclusion is not “AI is dangerous, therefore ban it.” It is closer to “AI is useful, therefore figure out how to make it work.”&lt;/p&gt;

&lt;p&gt;That is a meaningful distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI still produces bad code
&lt;/h2&gt;

&lt;p&gt;The conversation around “AI slop” has gone sideways.&lt;/p&gt;

&lt;p&gt;AI can still produce code that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unnecessarily complicated&lt;/li&gt;
&lt;li&gt;subtly wrong&lt;/li&gt;
&lt;li&gt;insecure&lt;/li&gt;
&lt;li&gt;poorly structured&lt;/li&gt;
&lt;li&gt;hard to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have seen all of those. I still review every change. I still rely on tests and CI. I still throw away AI-generated work when it is not good enough.&lt;/p&gt;

&lt;p&gt;None of that contradicts the claim that AI has become an extremely useful engineering tool. In some ways it is &lt;em&gt;why&lt;/em&gt; the tool is useful: we have gotten better at using it &lt;em&gt;despite&lt;/em&gt; its failure modes.&lt;/p&gt;

&lt;p&gt;The important fact is not that AI never makes mistakes. The important fact is that the cost of generating, evaluating, and discarding candidates has fallen far enough that the overall process is now productive.&lt;/p&gt;

&lt;h2&gt;
  
  
  “AI-generated” is not a quality metric
&lt;/h2&gt;

&lt;p&gt;Imagine opening a pull request and saying: “This was written by AI, so it’s slop.”&lt;/p&gt;

&lt;p&gt;That is not a technical review.&lt;/p&gt;

&lt;p&gt;A technical review asks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the implementation correct?&lt;/li&gt;
&lt;li&gt;Does it meet the requirements?&lt;/li&gt;
&lt;li&gt;Is the architecture appropriate?&lt;/li&gt;
&lt;li&gt;Is it tested?&lt;/li&gt;
&lt;li&gt;Is it secure?&lt;/li&gt;
&lt;li&gt;Is it maintainable?&lt;/li&gt;
&lt;li&gt;Does it add unnecessary complexity?&lt;/li&gt;
&lt;li&gt;Does it fit the existing system?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“Was an LLM involved?” can be useful context. It is not a quality judgment.&lt;/p&gt;

&lt;p&gt;Humans have produced terrible software for decades. We do not call it “human slop.” We call it bad code, review it, fix it, or delete it. The same standard should apply to AI-generated code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real change is the cost of another attempt
&lt;/h2&gt;

&lt;p&gt;The biggest shift is not that AI can write code. We have known that for years. The shift is that the cost of producing &lt;em&gt;another&lt;/em&gt; implementation has collapsed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want a different approach? Ask the agent.
&lt;/li&gt;
&lt;li&gt;Want to prototype an API? Ask the agent.
&lt;/li&gt;
&lt;li&gt;Want to migrate off a deprecated framework? Ask the agent.
&lt;/li&gt;
&lt;li&gt;Want to explore an unfamiliar codebase? Ask the agent.
&lt;/li&gt;
&lt;li&gt;Want tests for an edge case? Ask the agent.
&lt;/li&gt;
&lt;li&gt;Want to throw the whole thing away and try again? Do it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not make engineering judgment less important. It makes it &lt;em&gt;more&lt;/em&gt; important.&lt;/p&gt;

&lt;p&gt;When implementation becomes cheap, deciding &lt;em&gt;what&lt;/em&gt; to implement becomes more valuable. Architecture, requirements, code review, testing, domain knowledge, and the ability to recognize bad solutions all rise in relative value.&lt;/p&gt;

&lt;p&gt;In December 2025 the story was capability: multiple agents, dramatically higher throughput, less time spent on repetitive implementation, more time spent on product thinking and review.&lt;/p&gt;

&lt;p&gt;In July 2026 the story became acceptance: one of the world’s most important open-source projects is not going to reject the technology on ideological grounds.&lt;/p&gt;

&lt;p&gt;Capability plus acceptance is a stronger signal than either alone.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>discuss</category>
      <category>programming</category>
    </item>
    <item>
      <title>Developers that brand AI as slop will be left behind</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Fri, 28 Aug 2026 14:33:08 +0000</pubDate>
      <link>https://dev.to/remojansen/developers-that-brand-ai-as-slop-will-be-left-behind-2ki4</link>
      <guid>https://dev.to/remojansen/developers-that-brand-ai-as-slop-will-be-left-behind-2ki4</guid>
      <description>&lt;p&gt;A few days ago I &lt;a href="https://github.com/inversify/monorepo/issues/2086" rel="noopener noreferrer"&gt;shared&lt;/a&gt; an architectural proposal for InversifyJS on Reddit. It outlined an IPC-based type server that could help Inversify move beyond the legacy TypeScript decorator metadata APIs toward something compatible with TC39 decorators and TypeScript’s next-generation compiler.&lt;/p&gt;

&lt;p&gt;The first comment was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI slop&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I ignored it and went back to work.&lt;/p&gt;

&lt;p&gt;That night I built &lt;a href="https://github.com/remojansen/rflct" rel="noopener noreferrer"&gt;rflct&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It isn’t perfect. It’s an alpha with plenty of rough edges. But the fact that an architectural idea can become a working repository—with a runtime, compiler-side implementation, types, tests, examples, and a CLI—in a couple of hours is still mind-blowing to me.&lt;/p&gt;

&lt;p&gt;I have a particular perspective on this because I built Inversify from the ground up. It’s been a roughly ten-year journey. Early feedback was often negative. Plenty of things weren’t right, and plenty of things I didn’t know yet. Today Inversify has around 500,000 daily downloads and is used by projects including Elastic and the Eclipse Foundation.&lt;/p&gt;

&lt;p&gt;One of the things Inversify did well was listening to users. The best way to get that feedback is to put something real in their hands—not when it’s perfect, not when you’re completely proud of it, and not after months of polishing. Early. Sometimes so early it isn’t pretty yet.&lt;/p&gt;

&lt;p&gt;That is where AI changes software development most.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI makes experimentation cheap
&lt;/h2&gt;

&lt;p&gt;Before AI, turning an architectural hypothesis into something executable carried a real cost: research the APIs, design the architecture, write the code, debug it, build tests, and work through the edge cases. When experimentation is expensive, developers become conservative. You pick one idea, spend days or weeks on it, and only then discover whether it works.&lt;/p&gt;

&lt;p&gt;AI collapses that cost. It is no longer primarily about typing code faster. &lt;strong&gt;It is about making the loop from “I wonder if this would work?” to “Let’s build it and find out” dramatically cheaper.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the part many people are underestimating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Inversify problem is real
&lt;/h2&gt;

&lt;p&gt;Inversify was built around TypeScript’s experimental decorator and metadata capabilities. Constructor injection could infer parameter types and deliver a pleasant developer experience. But the ecosystem has moved on. TC39 decorators do not provide the same parameter decorator mechanism, and the &lt;code&gt;design:paramtypes&lt;/code&gt; metadata Inversify historically relied on belongs to the legacy decorator world. At the same time, TypeScript’s next-generation compiler (&lt;code&gt;tsgo&lt;/code&gt;) is introducing an IPC-based extensibility model.&lt;/p&gt;

&lt;p&gt;The RFC I shared explored whether a resident type server, queried over IPC, could recover the type information needed for dependency injection at build time. It was deliberately exploratory—open questions, things that needed prototyping, things that could be wrong. That’s the point of an RFC.&lt;/p&gt;

&lt;p&gt;Someone looked at it and decided the most useful feedback was “AI slop.”&lt;/p&gt;

&lt;p&gt;So I built something concrete instead.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rflct&lt;/code&gt; is an experiment in ahead-of-time reflection metadata for TypeScript 7. Instead of relying on runtime decorator metadata, it generates the metadata at build time. I didn’t need AI to identify the problem or decide the architectural questions were worth exploring. Those existed before the AI. What changed was the cost of turning the ideas into something I could run, test, and put in front of other developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The value moved up the stack
&lt;/h2&gt;

&lt;p&gt;When writing code becomes cheaper, writing code is no longer the scarce resource. The valuable part of the job moves upward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What problem are we solving?&lt;/li&gt;
&lt;li&gt;Is this the right problem?&lt;/li&gt;
&lt;li&gt;Is this architecture reasonable?&lt;/li&gt;
&lt;li&gt;What are the constraints and assumptions?&lt;/li&gt;
&lt;li&gt;What should be tested—and what should not be built?&lt;/li&gt;
&lt;li&gt;How do we know the result is correct?&lt;/li&gt;
&lt;li&gt;What happens in production, and what are the failure modes?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An LLM can generate a function, a module, or an entire repository. Generating a lot of code does not tell you whether you should have generated any of it. That judgment is still yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  “But you didn’t write the code”
&lt;/h2&gt;

&lt;p&gt;We have spent decades building abstractions that generate code for us: compilers, IDEs, code generators, frameworks, and libraries containing millions of lines we do not personally maintain. Nobody asks whether a developer typed every instruction the CPU executes. We care whether the software works, whether it is maintainable, whether the architecture is sound, whether it solves the problem, and whether the person responsible understands what they are shipping.&lt;/p&gt;

&lt;p&gt;If you generate 20,000 lines with AI and understand none of it, you have a problem. If you use AI to explore an architecture, inspect the output, run it, test it, discard bad approaches, modify it, and ultimately understand the result, the provenance of individual keystrokes is not particularly interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  “AI slop” is not a technical critique
&lt;/h2&gt;

&lt;p&gt;AI slop is real. Developers are generating applications they do not understand. People are opening pull requests with thousands of unreviewed lines. Documentation confidently describes APIs that do not exist. People are building things because an LLM suggested them rather than because they understood the problem.&lt;/p&gt;

&lt;p&gt;None of that is controversial.&lt;/p&gt;

&lt;p&gt;There is a large difference, however, between observing that a lot of low-quality AI-generated software exists and declaring that AI-generated software &lt;em&gt;is&lt;/em&gt; slop. The first is an observation. The second is a prejudice.&lt;/p&gt;

&lt;p&gt;I have seen far worse code on GitHub than &lt;code&gt;rflct&lt;/code&gt;, and it was written entirely by humans. Unreviewed pull requests, copy-pasted Stack Overflow solutions, frameworks held together by wishful thinking, and entire applications whose authors clearly did not understand the systems they were shipping. Low quality is not a new invention of large language models. The difference is that AI makes it easier to produce more of it, faster. That is a real problem. It is not a reason to stop evaluating the actual architecture, the tests, or the trade-offs.&lt;/p&gt;

&lt;p&gt;If you think an architecture is wrong, say why. If the assumptions about TypeScript are flawed, show the evidence. If there is a race condition, a bad API, a performance problem, or a failure case, demonstrate it. That is how software engineering works.&lt;/p&gt;

&lt;p&gt;“AI slop” identifies none of those things. It tells you how the reviewer feels about the tool that was used. Feeling is not the same as review.&lt;/p&gt;

&lt;h2&gt;
  
  
  We have seen this movie before
&lt;/h2&gt;

&lt;p&gt;Software developers have a long history of dismissing new approaches before eventually adopting them: new languages, frameworks, paradigms, static typing, functional programming, managed runtimes, garbage collection, WebAssembly, containers, serverless. Each transition produces a group that correctly identifies real limitations. There is a difference, though, between understanding the limitations of a technology and refusing to engage with it because you dislike the technology.&lt;/p&gt;

&lt;p&gt;AI is going through the same process. The people who dismiss it completely are going to have a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The productivity gap will compound
&lt;/h2&gt;

&lt;p&gt;Imagine two developers.&lt;/p&gt;

&lt;p&gt;Developer A decides AI-generated code is inherently bad and refuses to use it.&lt;/p&gt;

&lt;p&gt;Developer B uses AI aggressively but reviews everything: generates prototypes, investigates unfamiliar APIs, writes tests, explores alternatives, throws away most of what is produced, keeps what works, and still understands the system.&lt;/p&gt;

&lt;p&gt;After one day, B may have explored five ideas while A has explored one. After a week the gap is larger. After a month it is larger still.&lt;/p&gt;

&lt;p&gt;The advantage compounds because the scarce resource is not typing speed. It is &lt;strong&gt;iteration speed&lt;/strong&gt;. The faster you can move from idea to evidence, the more ideas you can evaluate, and the more likely you are to find something valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI amplifies the developer
&lt;/h2&gt;

&lt;p&gt;This does not mean AI will make everyone a great developer. Quite the opposite. AI can make a bad developer dramatically more productive at producing bad software. If you do not understand architecture, AI can generate an architecture you do not understand even faster. If you cannot review code, AI simply gives you more code to fail to review.&lt;/p&gt;

&lt;p&gt;AI amplifies the developer. That is why engineering judgment becomes more important, not less.&lt;/p&gt;

&lt;p&gt;The future is not “AI writes the software and developers sit back.” It is closer to this: developers who know what they are doing can use AI to explore and execute at a speed that was not previously possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  I would rather build and be wrong
&lt;/h2&gt;

&lt;p&gt;Maybe &lt;code&gt;rflct&lt;/code&gt; is a terrible idea. Maybe the architecture needs to change completely. Maybe TypeScript 7 will evolve in a direction that makes the experiment irrelevant. That is fine.&lt;/p&gt;

&lt;p&gt;I now have something concrete that can be run, benchmarked, broken, changed, and inspected by others. That feedback loop is infinitely more useful than an argument on Reddit.&lt;/p&gt;

&lt;p&gt;AI has lowered the cost of turning ideas into software. We can try more things, fail faster, learn faster, and build things that previously were not worth the investment. Developers who spend their energy dismissing all of this as “AI slop” are going to discover that the world moved on without them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developers that brand AI as slop will be left behind.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>career</category>
    </item>
    <item>
      <title>RFLCT: Bringing Runtime Type Metadata to TypeScript 7</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Thu, 27 Aug 2026 00:11:08 +0000</pubDate>
      <link>https://dev.to/remojansen/rflct-bringing-runtime-type-metadata-to-typescript-7-3l37</link>
      <guid>https://dev.to/remojansen/rflct-bringing-runtime-type-metadata-to-typescript-7-3l37</guid>
      <description>&lt;p&gt;If you've built large-scale applications in TypeScript, chances are you've used a Dependency Injection (DI) container. As the creator of InversifyJS, I've spent years thinking deeply about inversion of control, decoupling, and how to make enterprise patterns feel natural in TypeScript. &lt;/p&gt;

&lt;p&gt;But for all those years, there has been a glaring elephant in the room: our heavy reliance on &lt;code&gt;experimentalDecorators&lt;/code&gt; and &lt;code&gt;emitDecoratorMetadata&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;These compiler flags have served us well, but they are exactly that—&lt;em&gt;experimental&lt;/em&gt;. They tie us to legacy decorator implementations, require specific compiler configurations, and often feel like a magic black box that doesn't perfectly align with modern build pipelines. I've spent a lot of time recently thinking about how we could finally drop these flags entirely while keeping the developer experience pristine.&lt;/p&gt;

&lt;p&gt;With the release of TypeScript 7, I'm thrilled to introduce the solution: &lt;strong&gt;🪞 RFLCT&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is RFLCT?
&lt;/h2&gt;

&lt;p&gt;RFLCT is an ahead-of-time (AOT) reflect metadata injector for TypeScript 7. It injects &lt;code&gt;design:symbols&lt;/code&gt; and &lt;code&gt;design:arguments&lt;/code&gt; directly at build time. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero decorators. Zero &lt;code&gt;emitDecoratorMetadata&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It integrates seamlessly with virtually any build tool (Vite, Rollup, webpack, esbuild) via &lt;a href="https://github.com/unjs/unplugin" rel="noopener noreferrer"&gt;unplugin&lt;/a&gt;, or you can use the built-in CLI using the TypeScript 7 API for standalone &lt;code&gt;tsgo&lt;/code&gt; projects.&lt;/p&gt;

&lt;p&gt;Let's look at how it actually feels to write code with RFLCT.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic: Before and After
&lt;/h2&gt;

&lt;p&gt;With RFLCT, you annotate the types you want to expose to your runtime metadata using a special &lt;code&gt;Reflect&amp;lt;T&amp;gt;&lt;/code&gt; wrapper type. &lt;/p&gt;

&lt;h3&gt;
  
  
  What you write:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rflct&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Shape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;sides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Polygon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// resolve&amp;lt;T&amp;gt;() → the runtime identity of T (Symbol for interfaces, class for classes)&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What RFLCT compiles it to:
&lt;/h3&gt;

&lt;p&gt;Notice how the interfaces are safely converted into global Symbols, and metadata is explicitly registered without a single decorator in sight.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reflect-metadata&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@acme/shapes@1|src/geo.ts|Shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Polygon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:arguments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:symbols&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;design:symbols&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@acme/shapes@1|src/geo.ts|Shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;__RFLCT_Shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@acme/shapes@1|src/geo.ts|Polygon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Polygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Broader Than Just InversifyJS
&lt;/h2&gt;

&lt;p&gt;While my primary motivation for building RFLCT was to pave the way for the next generation of InversifyJS, this underlying primitive—a reliable, decorator-free way to emit runtime type metadata—unlocks so much more. &lt;/p&gt;

&lt;p&gt;Because RFLCT standardizes how types are mapped to memory at build time, it has massive potential across the ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Custom DI Engines:&lt;/strong&gt; Build your own lightweight inversion of control containers without metadata boilerplate.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Object Mapping &amp;amp; Hydration:&lt;/strong&gt; Easily map database or API JSON results directly back into instantiated classes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;RPC Frameworks:&lt;/strong&gt; Guarantee type-safe network boundaries by validating incoming arguments against compile-time metadata.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Runtime Validation:&lt;/strong&gt; Perform deep runtime validation by reading exactly what types a constructor or method expects.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How It Works: The Three Transformations
&lt;/h2&gt;

&lt;p&gt;Under the hood, RFLCT performs three core transformations during your build step:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;design:symbols&lt;/code&gt; — The Global Type Registry
&lt;/h3&gt;

&lt;p&gt;Every class, interface, and type alias in a file is registered in a process-wide &lt;code&gt;Map&lt;/code&gt; on the global &lt;code&gt;Reflect&lt;/code&gt; object. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Interfaces &amp;amp; Types&lt;/strong&gt; become universally unique Symbols (e.g., &lt;code&gt;Symbol.for(qualifiedName)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Classes&lt;/strong&gt; map directly to their constructor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;design:arguments&lt;/code&gt; — Parameter Type Metadata
&lt;/h3&gt;

&lt;p&gt;Any parameter annotated with &lt;code&gt;Reflect&amp;lt;T&amp;gt;&lt;/code&gt; (or &lt;code&gt;Reflect&amp;lt;T, Metadata&amp;gt;&lt;/code&gt;) tells the compiler to produce a &lt;code&gt;Reflect.defineMetadata("design:arguments", [...], target, key)&lt;/code&gt; call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Constructors&lt;/strong&gt;: &lt;code&gt;target = ClassName&lt;/code&gt;, &lt;code&gt;key = undefined&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Methods&lt;/strong&gt;: &lt;code&gt;target = ClassName.prototype&lt;/code&gt;, &lt;code&gt;key = "methodName"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;resolve&amp;lt;T&amp;gt;()&lt;/code&gt; — Compile-Time Type Resolution
&lt;/h3&gt;

&lt;p&gt;Writing &lt;code&gt;resolve&amp;lt;T&amp;gt;()&lt;/code&gt; acts as a macro. It is replaced at compile time with the actual runtime identity of &lt;code&gt;T&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;resolve&amp;lt;Shape&amp;gt;()&lt;/code&gt; compiles to &lt;code&gt;Symbol.for("...Shape")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;resolve&amp;lt;Triangle&amp;gt;(Triangle)&lt;/code&gt; compiles simply to &lt;code&gt;Triangle&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Smart Symbol Qualification
&lt;/h3&gt;

&lt;p&gt;A massive headache with metadata in the past has been duplicate dependencies causing symbol collisions. Generated symbols use &lt;code&gt;Symbol.for(qualifiedName)&lt;/code&gt; structured as &lt;code&gt;packageName@majorVersion|packageRelativePath|TypeName&lt;/code&gt;. This prevents collisions, allows minor/patch versions to share symbols, and ensures that shared library types resolve to the exact same symbol reference in memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started (Alpha Preview)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;⚠️ **Note:&lt;/em&gt;* RFLCT is currently in its early &lt;code&gt;0.0.1-alpha.0&lt;/code&gt; stage and the npm module has not yet been published to the public registry.* &lt;/p&gt;

&lt;p&gt;For now, to try it out, you will need to download the source directly from GitHub and build it locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Build from Source
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-username/rflct.git
&lt;span class="nb"&gt;cd &lt;/span&gt;rflct
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
npm &lt;span class="nb"&gt;link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Project Setup
&lt;/h3&gt;

&lt;p&gt;Link the local package to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;link &lt;/span&gt;rflct
npm &lt;span class="nb"&gt;install &lt;/span&gt;reflect-metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to import &lt;code&gt;reflect-metadata&lt;/code&gt; &lt;strong&gt;once&lt;/strong&gt; at your application's entry point to polyfill the global &lt;code&gt;Reflect.defineMetadata&lt;/code&gt; APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// entry.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reflect-metadata&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configure Your Build Tool
&lt;/h3&gt;

&lt;p&gt;Because RFLCT is built on top of &lt;code&gt;unplugin&lt;/code&gt;, it drops straight into your existing pipeline. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vite:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;vitePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rflct/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;vitePlugin&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Plugins for Rollup, esbuild, and webpack are also available out of the box!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you are running a standalone &lt;code&gt;tsgo&lt;/code&gt; project without a bundler, the CLI leverages the TS 7 API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx rflct &lt;span class="nt"&gt;-p&lt;/span&gt; tsconfig.json &lt;span class="nt"&gt;-o&lt;/span&gt; dist &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Looking Forward &amp;amp; Next Steps
&lt;/h2&gt;

&lt;p&gt;Dependency Injection in TypeScript is finally growing up. By moving metadata generation to a clean, predictable build step, we can leave experimental decorators in the past and write DI code that feels native to modern TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, what's next?&lt;/strong&gt; &lt;br&gt;
The immediate next step for me is to start experimenting with integrating RFLCT directly into &lt;strong&gt;InversifyJS&lt;/strong&gt;. I'll be testing how seamlessly we can migrate existing IoC containers to this new AOT metadata approach. &lt;/p&gt;

&lt;p&gt;I can't wait to see what you build with it! Clone the repo, try it in your projects, and let me know what you think.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>node</category>
      <category>tooling</category>
    </item>
    <item>
      <title>23 TypeScript Tools for Making Software Explicit in the AI Era</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Fri, 21 Aug 2026 08:53:59 +0000</pubDate>
      <link>https://dev.to/remojansen/23-typescript-tools-for-making-software-explicit-in-the-ai-era-20hb</link>
      <guid>https://dev.to/remojansen/23-typescript-tools-for-making-software-explicit-in-the-ai-era-20hb</guid>
      <description>&lt;p&gt;In my previous articles, &lt;a href="https://dev.to/remojansen/from-rigidity-to-explicitness-how-ai-changes-the-role-of-constraints-in-software-5cp5"&gt;I argued that AI is changing the role of constraints in software development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a long time, we treated constraints as friction. Static types felt slower than dynamic code. Schemas felt restrictive compared to flexible data. Explicit workflows felt more cumbersome than letting an application decide what to do at runtime.&lt;/p&gt;

&lt;p&gt;But AI changes the economics.&lt;/p&gt;

&lt;p&gt;Writing code is becoming cheaper. Understanding what the code is supposed to do is not. And verifying that generated code actually does what we intended is becoming one of the most important parts of software development.&lt;/p&gt;

&lt;p&gt;This leads to a simple principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The more important an assumption is, the more valuable it is to make that assumption explicit.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is particularly important with TypeScript. TypeScript already makes some things explicit, but the type system cannot express everything. It cannot tell us what happens when an HTTP request fails, validate JSON received from an external service, tell us which application states are legal, describe database relationships, enforce module boundaries, or define how a distributed workflow should behave after a process crashes.&lt;/p&gt;

&lt;p&gt;Those things are often left implicit.&lt;/p&gt;

&lt;p&gt;That is exactly where AI-assisted development becomes difficult. If a constraint exists only in someone's head, a prompt, a convention, or an undocumented assumption, the AI has to infer it. And inference is exactly where we don't want critical business rules to live.&lt;/p&gt;

&lt;p&gt;The interesting thing about the TypeScript ecosystem is that there are now tools for making almost every layer of a system more explicit.&lt;/p&gt;

&lt;p&gt;Here are 23 of them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Schemas and Side Effects
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Effect
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://effect.website/" rel="noopener noreferrer"&gt;Effect&lt;/a&gt; makes effects, errors, dependencies, concurrency, resources, and schemas explicit.&lt;/p&gt;

&lt;p&gt;Without Effect, we might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a lot of implicit information here. The function performs I/O. It can fail. It returns unvalidated external data. It depends on &lt;code&gt;fetch&lt;/code&gt;. The caller has to discover all of this by reading the implementation.&lt;/p&gt;

&lt;p&gt;With Effect, those concerns become part of the program's structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;decodeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important difference is not syntax. It is information density. The code communicates what the operation does, what it can fail with, what it depends on, and how it composes with other effects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Effect turns invisible operational behavior into explicit program structure.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Zod
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt; makes runtime data validation explicit.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The programmer is implicitly assuming that the response contains an &lt;code&gt;email&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;With Zod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the assumption is explicit. The schema can be read by a human, used by the application, tested automatically, and inspected by AI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Types describe what we believe. Runtime schemas verify what we actually received.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. io-ts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gcanti.github.io/io-ts/" rel="noopener noreferrer"&gt;io-ts&lt;/a&gt; makes the boundary between unknown runtime data and typed data explicit.&lt;/p&gt;

&lt;p&gt;Without a codec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPayment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type says &lt;code&gt;payment&lt;/code&gt; is valid, but the runtime data has not actually been checked.&lt;/p&gt;

&lt;p&gt;With io-ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPayment&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there is an executable description of the boundary between unknown data and trusted data. The codec can also be tested against invalid inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Valibot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://valibot.dev/" rel="noopener noreferrer"&gt;Valibot&lt;/a&gt; makes runtime validation and schemas explicit with a lightweight API.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Assume input is valid.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Valibot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AccountInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AccountInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The requirements are no longer hidden inside the implementation. They are represented as data that humans, tests, tools, and AI can inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. TypeBox
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sinclairzx81.github.io/typebox/" rel="noopener noreferrer"&gt;TypeBox&lt;/a&gt; makes JSON Schema and TypeScript type definitions explicit and connected.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CreateOrder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TypeScript type describes the application, but external validators and JSON Schema consumers need another representation.&lt;/p&gt;

&lt;p&gt;With TypeBox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CreateOrder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema becomes a first-class artifact that can drive validation, tooling, documentation, and generation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One explicit contract is better than five independently maintained descriptions of the same contract.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  State and Behaviour
&lt;/h1&gt;

&lt;h2&gt;
  
  
  6. XState
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stately.ai/docs/xstate" rel="noopener noreferrer"&gt;XState&lt;/a&gt; makes states, events, transitions, and actors explicit.&lt;/p&gt;

&lt;p&gt;Without a state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the application grows, combinations can appear that were never intended. The actual state model exists implicitly in conditionals.&lt;/p&gt;

&lt;p&gt;With XState:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;machine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMachine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;states&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;SUBMIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;submitting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;FAILURE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;RETRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the possible states and transitions are explicit. An AI does not need to infer which transitions are legal. Tests can enumerate them, tooling can visualize them, and invalid transitions can be rejected.&lt;/p&gt;

&lt;p&gt;This is the same idea I explored in my article about AI agents and finite state machines: don't ask the AI to infer the workflow when you can give it the workflow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Data and Persistence
&lt;/h1&gt;

&lt;h2&gt;
  
  
  7. Prisma
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt; makes database models, relationships, migrations, and generated data access types explicit.&lt;/p&gt;

&lt;p&gt;Without a schema-first ORM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE organisation_id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;organisationId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relationship between users and organisations is hidden in the database.&lt;/p&gt;

&lt;p&gt;With Prisma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
  id             String       @id
  email          String
  organisationId String
  organisation   Organisation @relation(fields: [organisationId], references: [id])
}

model Organisation {
  id    String @id
  users User[]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model and relationship are explicit, and the schema can generate types and migrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Drizzle
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt; makes database schemas, SQL relationships, and query types explicit in TypeScript.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT id, name FROM users WHERE active = true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL is explicit, but the relationship between the query and its TypeScript result is not.&lt;/p&gt;

&lt;p&gt;With Drizzle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usersTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usersTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersTable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema, query, selected fields, and result type can all be connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Kysely
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kysely.dev/" rel="noopener noreferrer"&gt;Kysely&lt;/a&gt; makes SQL queries and their result types compile-time checked.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  SELECT id, email
  FROM users
  WHERE organisation_id = $1
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;organisationId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI has to infer the result shape from the SQL.&lt;/p&gt;

&lt;p&gt;With Kysely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;organisation_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;organisationId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query is constrained by the database type. Invalid tables or columns can become compile-time errors.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The best constraint is often the one that fails automatically.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Infrastructure
&lt;/h1&gt;

&lt;h2&gt;
  
  
  10. Pulumi
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.pulumi.com/" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt; makes infrastructure resources, dependencies, and configuration explicit as typed code.&lt;/p&gt;

&lt;p&gt;Without infrastructure-as-code, the architecture might be a collection of console settings, scripts, environment variables, documentation, and tribal knowledge.&lt;/p&gt;

&lt;p&gt;With Pulumi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uploads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uploads-policy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Infrastructure relationships become part of the program. The AI can inspect them, deployment tooling can inspect them, and infrastructure can be previewed before it changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. AWS CDK
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; makes cloud infrastructure and its relationships explicit as TypeScript constructs.&lt;/p&gt;

&lt;p&gt;Without CDK, the relationship between an API and Lambda might exist only in cloud configuration.&lt;/p&gt;

&lt;p&gt;With CDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_22_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relationship is now explicit, reviewable, synthesizable, and testable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Architecture
&lt;/h1&gt;

&lt;h2&gt;
  
  
  12. dependency-cruiser
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sverweij/dependency-cruiser" rel="noopener noreferrer"&gt;dependency-cruiser&lt;/a&gt; makes module dependency rules explicit and enforceable.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI
 ↓
Application
 ↓
Domain
 ↓
Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But nothing prevents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Domain
 ↓
Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With dependency-cruiser, rules such as "domain must not depend on infrastructure" become executable.&lt;/p&gt;

&lt;p&gt;An AI can read the rules, CI can enforce them, and generated imports that violate them can fail automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Architectural decisions should be executable whenever possible.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  13. Madge
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/pahen/madge" rel="noopener noreferrer"&gt;Madge&lt;/a&gt; makes module dependency graphs and circular dependencies explicit.&lt;/p&gt;

&lt;p&gt;Without tooling, a cycle such as this can be difficult to see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → B → C → D → A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Madge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;madge &lt;span class="nt"&gt;--circular&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the graph becomes inspectable and circular dependencies can be detected automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. eslint-plugin-boundaries
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/j5ik2o/eslint-plugin-boundaries" rel="noopener noreferrer"&gt;eslint-plugin-boundaries&lt;/a&gt; makes architectural layer and module boundaries explicit and enforceable.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../infrastructure/database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;might compile perfectly even when application code is not supposed to access infrastructure directly.&lt;/p&gt;

&lt;p&gt;With boundaries configured, the rule can become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application → domain
application → infrastructure ❌
domain → infrastructure ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ESLint can reject the violation. The AI does not need to remember the architectural rule because the tool enforces it.&lt;/p&gt;

&lt;h1&gt;
  
  
  APIs
&lt;/h1&gt;

&lt;h2&gt;
  
  
  15. tRPC
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt; makes client/server procedure contracts explicit and type-safe.&lt;/p&gt;

&lt;p&gt;Without a shared contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server might expect completely different field names. The contract exists implicitly across two implementations.&lt;/p&gt;

&lt;p&gt;With tRPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;publicProcedure&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The procedure and its input contract become part of the program. The client can consume that contract directly, and the compiler provides feedback when it changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  16. ts-rest
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ts-rest.com/" rel="noopener noreferrer"&gt;ts-rest&lt;/a&gt; makes HTTP endpoints, parameters, payloads, and responses explicit as shared contracts.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/orders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI has to infer what the endpoint expects, what it returns, and which status codes are possible.&lt;/p&gt;

&lt;p&gt;With a contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;router&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/orders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ErrorResponse&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP boundary becomes explicit and can drive server implementation, client usage, tests, and documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  17. oRPC
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://orpc.unnoq.com/" rel="noopener noreferrer"&gt;oRPC&lt;/a&gt; makes RPC procedures and their client/server contracts explicit and type-safe.&lt;/p&gt;

&lt;p&gt;Without a contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation determines what arguments are accepted.&lt;/p&gt;

&lt;p&gt;With an explicit procedure contract, inputs, outputs, errors, and procedure identity become machine-readable. The AI is no longer asked to figure out how the API probably works; it is asked to implement against a defined contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  18. OpenAPI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.openapis.org/" rel="noopener noreferrer"&gt;OpenAPI Initiative&lt;/a&gt; makes network API contracts explicit independently of implementation language.&lt;/p&gt;

&lt;p&gt;Without OpenAPI, an API might be described by a README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /users

Probably takes:
{
  name,
  email
}

Returns a user.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With OpenAPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/CreateUser'&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;201'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/User'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API becomes a formal artifact. Humans can read it, machines can validate it, tools can generate clients, and AI can consume it.&lt;/p&gt;

&lt;h2&gt;
  
  
  19. Orval
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://orval.dev/" rel="noopener noreferrer"&gt;Orval&lt;/a&gt; makes OpenAPI contracts executable by generating strongly typed clients.&lt;/p&gt;

&lt;p&gt;Without generation, an AI might repeatedly write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users/123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and have to infer the method, parameters, request body, response type, and error handling.&lt;/p&gt;

&lt;p&gt;With Orval, the OpenAPI specification becomes the source from which client code is generated. The AI can use generated code whose structure already reflects the contract.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Explicitness is even more powerful when it can be converted into automation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Distributed Systems
&lt;/h1&gt;

&lt;h2&gt;
  
  
  20. Proto.Actor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://proto.actor/" rel="noopener noreferrer"&gt;Proto.Actor&lt;/a&gt; makes actors, messages, supervision, identity, and distributed communication explicit.&lt;/p&gt;

&lt;p&gt;Without an actor model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens when the node disappears? Who owns the state? Should the message be retried? Who supervises the failure? Can two messages be processed concurrently?&lt;/p&gt;

&lt;p&gt;An actor model gives these concepts explicit names and structures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Actor
  ↓
Message
  ↓
State
  ↓
Supervision
  ↓
Failure handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distributed system now has a vocabulary that both humans and AI can reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  21. Dapr
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dapr.io/" rel="noopener noreferrer"&gt;Dapr&lt;/a&gt; makes distributed-system capabilities explicit through abstractions such as actors, state, pub/sub, and service invocation.&lt;/p&gt;

&lt;p&gt;Without a distributed abstraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distributed semantics are scattered throughout the application.&lt;/p&gt;

&lt;p&gt;With Dapr, concepts such as state, pub/sub, service invocation, and actors become explicit architectural capabilities. The AI can reason about intent instead of reconstructing architecture from arbitrary infrastructure calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  22. Temporal
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://temporal.io/" rel="noopener noreferrer"&gt;Temporal&lt;/a&gt; makes durable workflows, activities, retries, timers, failures, and long-running execution explicit.&lt;/p&gt;

&lt;p&gt;Without Temporal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeCard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens if the process crashes after &lt;code&gt;chargeCard()&lt;/code&gt;? Should it be retried? What if &lt;code&gt;createOrder()&lt;/code&gt; fails? Should the email be sent twice?&lt;/p&gt;

&lt;p&gt;With Temporal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeCard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;proxyActivities&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;activities&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;startToCloseTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 minute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maximumAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow, retries, timers, activities, and durability semantics become explicit.&lt;/p&gt;

&lt;p&gt;This is especially important for AI agents.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Let AI decide what requires intelligence. Let the workflow engine handle what requires reliability.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Functional Programming
&lt;/h1&gt;

&lt;h2&gt;
  
  
  23. fp-ts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gcanti.github.io/fp-ts/" rel="noopener noreferrer"&gt;fp-ts&lt;/a&gt; makes functional effects, optionality, errors, and composition explicit through types.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caller has to remember to handle the missing case.&lt;/p&gt;

&lt;p&gt;With explicit functional structures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUser&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Either&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The possibility of failure is no longer an informal convention. It is part of the function's type.&lt;/p&gt;

&lt;p&gt;If a function returns &lt;code&gt;User&lt;/code&gt;, the AI may assume it always has a user. If it returns &lt;code&gt;Either&amp;lt;ValidationError, User&amp;gt;&lt;/code&gt;, the failure path is visible and the compiler can help verify that it was handled.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Common Pattern
&lt;/h1&gt;

&lt;p&gt;These tools look very different. Effect is not Prisma. Prisma is not XState. XState is not OpenAPI. OpenAPI is not Temporal.&lt;/p&gt;

&lt;p&gt;But they are all solving a similar problem.&lt;/p&gt;

&lt;p&gt;They take something that would otherwise be implicit and give it a representation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Implicit&lt;/th&gt;
&lt;th&gt;Explicit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Errors&lt;/td&gt;
&lt;td&gt;Effect / &lt;code&gt;Either&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External data assumptions&lt;/td&gt;
&lt;td&gt;Zod / io-ts / Valibot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON Schema&lt;/td&gt;
&lt;td&gt;TypeBox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application state&lt;/td&gt;
&lt;td&gt;XState&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database relationships&lt;/td&gt;
&lt;td&gt;Prisma / Drizzle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL result types&lt;/td&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure relationships&lt;/td&gt;
&lt;td&gt;Pulumi / CDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module architecture&lt;/td&gt;
&lt;td&gt;dependency-cruiser / Madge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architectural boundaries&lt;/td&gt;
&lt;td&gt;eslint-plugin-boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API contracts&lt;/td&gt;
&lt;td&gt;tRPC / ts-rest / oRPC / OpenAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generated API clients&lt;/td&gt;
&lt;td&gt;Orval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actor semantics&lt;/td&gt;
&lt;td&gt;Proto.Actor / Dapr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durable workflows&lt;/td&gt;
&lt;td&gt;Temporal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optionality and errors&lt;/td&gt;
&lt;td&gt;fp-ts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important word here is &lt;strong&gt;representation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If a rule only exists in a developer's head, there is very little an AI can do with it. If the rule exists only in a prompt, the AI can forget it. If it exists only in documentation, it can become stale.&lt;/p&gt;

&lt;p&gt;But if the rule exists in code, a schema, a state machine, a contract, a dependency rule, or a workflow definition, it becomes part of the system.&lt;/p&gt;

&lt;p&gt;And once it is part of the system, we can automate around it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Explicitness Creates a Verification Surface
&lt;/h1&gt;

&lt;p&gt;Consider an AI-generated change.&lt;/p&gt;

&lt;p&gt;Without explicit constraints, verification might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI generated code
        ↓
Human reads it
        ↓
Human tries to understand intent
        ↓
Human guesses whether assumptions are correct
        ↓
Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a lot of interpretation involved.&lt;/p&gt;

&lt;p&gt;Now consider a system with explicit contracts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI generated code
        ↓
TypeScript compiler
        ↓
Schema validation
        ↓
API contract tests
        ↓
Architecture rules
        ↓
State-machine tests
        ↓
Database constraints
        ↓
Workflow verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI still generates code, but it generates code inside a much smaller space of possibilities.&lt;/p&gt;

&lt;p&gt;That is the real advantage.&lt;/p&gt;

&lt;p&gt;We don't need AI to become perfectly reliable. We can make the environment around AI more verifiable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Better Context Produces Better Code
&lt;/h1&gt;

&lt;p&gt;These tools don't only prevent mistakes. They improve the context available to the AI.&lt;/p&gt;

&lt;p&gt;Suppose I ask an AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add a new payment provider.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In an implicit codebase, the AI has to discover how payments work, where providers live, which errors are possible, how payment state is represented, how retries work, which modules can depend on which, which API contract is expected, and how the database represents payments.&lt;/p&gt;

&lt;p&gt;It has to reconstruct all of that.&lt;/p&gt;

&lt;p&gt;Now imagine the same request in an explicit system. The AI can inspect the payment state machine, payment schema, payment API contract, dependency rules, database schema, and workflow definition.&lt;/p&gt;

&lt;p&gt;The problem has become much smaller.&lt;/p&gt;

&lt;p&gt;The AI isn't necessarily smarter. The system is simply giving it better information.&lt;/p&gt;

&lt;p&gt;This is why I increasingly think about constraints as &lt;strong&gt;context compression&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An explicit model can communicate a large amount of intent in a small, machine-readable representation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Explicitness Also Changes Testing
&lt;/h1&gt;

&lt;p&gt;When something is implicit, testing often requires testing the implementation.&lt;/p&gt;

&lt;p&gt;When something is explicit, we can often test the model.&lt;/p&gt;

&lt;p&gt;With an FSM we can test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can Submitted → Purchased happen directly?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an API contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does POST /orders return 201 with an Order?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does invalid email data get rejected?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With dependency rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can domain import infrastructure?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should not.&lt;/p&gt;

&lt;p&gt;With a database schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can an Order reference a non-existent User?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should not.&lt;/p&gt;

&lt;p&gt;With Temporal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What happens when Activity #2 fails?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With OpenAPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does the implementation conform to the published API?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verification target moves from "Does this code look correct?" toward "Does this implementation satisfy these explicit constraints?"&lt;/p&gt;

&lt;p&gt;That is a much better problem for automation.&lt;/p&gt;

&lt;h1&gt;
  
  
  This Does Not Mean Everything Should Be Explicit
&lt;/h1&gt;

&lt;p&gt;There is an obvious danger here.&lt;/p&gt;

&lt;p&gt;If explicitness is good, it is tempting to make everything explicit.&lt;/p&gt;

&lt;p&gt;That would be a mistake.&lt;/p&gt;

&lt;p&gt;Not every function needs an effect system. Not every application needs a state machine. Not every database needs an ORM. Not every project needs five architecture enforcement tools.&lt;/p&gt;

&lt;p&gt;The goal is not maximum constraint. The goal is to make &lt;strong&gt;important assumptions explicit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A useful question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If this assumption were wrong, would the resulting bug be expensive?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is yes, it is a good candidate for explicit representation.&lt;/p&gt;

&lt;p&gt;If the answer is no, inference may be perfectly reasonable.&lt;/p&gt;

&lt;p&gt;The objective is not rigidity. It is explicitness where explicitness creates value.&lt;/p&gt;

&lt;h1&gt;
  
  
  The TypeScript Ecosystem Is Becoming an Explicitness Ecosystem
&lt;/h1&gt;

&lt;p&gt;This is what I find interesting about the current TypeScript ecosystem.&lt;/p&gt;

&lt;p&gt;It is no longer just a language with a type checker. There are tools for making almost every important boundary explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Business process
                    │
                 XState
                    │
              Application
                    │
          ┌─────────┴─────────┐
          │                   │
       Effect              fp-ts
          │                   │
          └─────────┬─────────┘
                    │
                TypeScript
                    │
       ┌────────────┼────────────┐
       │            │            │
     Zod         tRPC        OpenAPI
       │            │            │
       └────────────┼────────────┘
                    │
              Database
                    │
       Prisma / Drizzle / Kysely
                    │
              Infrastructure
                    │
             Pulumi / CDK
                    │
          Distributed Systems
                    │
      Temporal / Dapr / Actors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And around all of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependency-cruiser
Madge
eslint-plugin-boundaries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tools are doing something bigger than adding features to TypeScript.&lt;/p&gt;

&lt;p&gt;They are making assumptions observable.&lt;/p&gt;

&lt;h1&gt;
  
  
  AI Makes This More Important
&lt;/h1&gt;

&lt;p&gt;Before AI-assisted development, a developer could often compensate for implicitness through experience.&lt;/p&gt;

&lt;p&gt;A senior developer might know that you shouldn't import infrastructure from the domain, that a particular API actually returns 202 rather than 200, that an operation is not safe to retry, that a state can only transition after approval, or that a database field is nullable even though the TypeScript type says otherwise.&lt;/p&gt;

&lt;p&gt;That knowledge existed in people's heads.&lt;/p&gt;

&lt;p&gt;AI doesn't reliably have access to that knowledge. And even when we tell it, we have to trust that it will remember and apply it consistently.&lt;/p&gt;

&lt;p&gt;This creates a new architectural pressure:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Move important knowledge out of people's heads and into artifacts that machines can inspect and verify.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Schemas. Types. Contracts. State machines. Dependency rules. Database models. Workflow definitions. Infrastructure definitions.&lt;/p&gt;

&lt;p&gt;These become part of the AI's context. But more importantly, they become part of the system's verification surface.&lt;/p&gt;

&lt;h1&gt;
  
  
  From Code Generation to Code Verification
&lt;/h1&gt;

&lt;p&gt;AI is making code generation increasingly cheap. That changes what we should optimize for.&lt;/p&gt;

&lt;p&gt;We should spend less time asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How can I make writing this code faster?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and more time asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How can I make it obvious whether this code is correct?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tools in this list answer that question in different ways. Some make data explicit. Some make behaviour explicit. Some make architecture explicit. Some make infrastructure explicit. Some make distributed execution explicit. Some make APIs explicit. And some make failure explicit.&lt;/p&gt;

&lt;p&gt;They all reduce the amount of important information that exists only through inference.&lt;/p&gt;

&lt;p&gt;That is valuable for humans. But I think it is becoming even more valuable for AI.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Future Is Not More Constraints
&lt;/h1&gt;

&lt;p&gt;I don't think the future of software is going to be about adding more and more constraints.&lt;/p&gt;

&lt;p&gt;It is about putting constraints in the right places.&lt;/p&gt;

&lt;p&gt;A good system might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI
 │
 │ intelligence
 ▼
Explicit contracts
 │
 ├── Types
 ├── Schemas
 ├── APIs
 ├── State machines
 ├── Architecture rules
 ├── Database models
 └── Workflows
 │
 ▼
Automated verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI remains probabilistic. The system around it becomes increasingly deterministic.&lt;/p&gt;

&lt;p&gt;That is the architectural shift I find most interesting.&lt;/p&gt;

&lt;p&gt;We don't need to make AI deterministic. We need to stop asking AI to infer things that software can already define.&lt;/p&gt;

&lt;p&gt;The best TypeScript tools for the AI era may therefore not be the ones that help us write code faster. They may be the ones that make the &lt;strong&gt;intent behind the code impossible to misunderstand&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because when code generation is cheap, explicitness becomes leverage.&lt;/p&gt;

&lt;p&gt;And when verification is the bottleneck, every explicit constraint becomes another thing a human or a machine can check.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't make the AI remember your rules. Make your system represent them.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is how we move from AI-generated software to AI-generated software that we can actually verify.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ai</category>
      <category>agents</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The Boundary Independence Principle (BIP)</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Thu, 20 Aug 2026 08:20:05 +0000</pubDate>
      <link>https://dev.to/remojansen/the-boundary-independence-principle-bip-18me</link>
      <guid>https://dev.to/remojansen/the-boundary-independence-principle-bip-18me</guid>
      <description>&lt;p&gt;I've been writing about the SOLID principles for over a decade. I built &lt;a href="https://inversify.io/" rel="noopener noreferrer"&gt;InversifyJS&lt;/a&gt; because of them, I wrote about &lt;a href="https://dev.to/remojansen/implementing-the-onion-architecture-in-nodejs-with-typescript-and-inversifyjs-10ad"&gt;implementing them with the onion architecture&lt;/a&gt;, and just recently I argued that they are &lt;a href="https://dev.to/remojansen/the-solid-principles-are-universal-1c9m"&gt;universal design principles&lt;/a&gt; that appear far beyond object-oriented programming. Yet something has always felt missing, not from the principles themselves, but from the conversation around them.&lt;/p&gt;

&lt;p&gt;SOLID tells you how to write good components. It does not tell you how to compose those components into a system whose &lt;em&gt;shape&lt;/em&gt; can still change.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gap
&lt;/h2&gt;

&lt;p&gt;Imagine a perfectly SOLID codebase. Your &lt;code&gt;UserRepository&lt;/code&gt; depends on an abstraction. Your &lt;code&gt;EmailService&lt;/code&gt; has a single responsibility. Your &lt;code&gt;OrderProcessor&lt;/code&gt; is open for extension and closed for modification. Everything is clean.&lt;/p&gt;

&lt;p&gt;Then your CTO says: "We need to extract user management into its own microservice."&lt;/p&gt;

&lt;p&gt;You begin ripping the system apart. New projects, moved files, rewritten imports, new entry points, new dependency wiring, shared interfaces extracted into packages. Weeks of work follow. The SOLID components themselves needed almost no change. The &lt;em&gt;structure&lt;/em&gt; around them did. The boundaries of the system had been baked into import paths, project layout, entry points, and hard-coded module wiring.&lt;/p&gt;

&lt;p&gt;SOLID gave you excellent bricks. Without a principle that keeps those bricks from being cemented into a fixed shape too early, you still end up with a rigid system.&lt;/p&gt;




&lt;h2&gt;
  
  
  A convergence of patterns
&lt;/h2&gt;

&lt;p&gt;Software engineering literature is rich with architectural patterns: Hexagonal Architecture, Onion Architecture, Ports &amp;amp; Adapters, Composition Roots, Modular Monoliths, Microkernels, and plugin systems.&lt;/p&gt;

&lt;p&gt;On the surface they address different concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Composition Root&lt;/strong&gt; ,  assemble the object graph in one place at startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ports and Adapters / Hexagonal&lt;/strong&gt; ,  isolate domain logic behind explicit interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onion / Clean Architecture&lt;/strong&gt; ,  enforce inward-pointing dependencies toward business rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular Monolith&lt;/strong&gt; ,  keep a single deployable while enforcing strict module encapsulation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microkernels and plugins&lt;/strong&gt; ,  keep a minimal core unaware of the extensions that run inside it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite different origins, applying these patterns well repeatedly produces the same emergent property:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can change the physical shape of a system without altering the code that performs the actual business logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When independent patterns converge on the same property, it is worth asking whether they are expressions of a deeper principle.&lt;/p&gt;




&lt;h2&gt;
  
  
  System shape
&lt;/h2&gt;

&lt;p&gt;System shape is the physical layout, deployment targets, and runtime distribution of a system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One process or many services?&lt;/li&gt;
&lt;li&gt;In-memory calls or network messages?&lt;/li&gt;
&lt;li&gt;Monolith container, shared library, or serverless functions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shape decisions are among the most expensive an organization can make. Teams that choose microservices too early often spend months consolidating. Teams that stay in a monolith too long often spend a year extracting services. These transitions hurt because business logic frequently carries implicit assumptions about where it runs, shared memory, instantaneous responses, or a single database.&lt;/p&gt;

&lt;p&gt;The patterns above make system shape &lt;em&gt;mutable&lt;/em&gt;. They allow teams to reconfigure the physical layout without rewriting domain rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  What changes with the Composition Root
&lt;/h2&gt;

&lt;p&gt;Consider the same codebase wired exclusively through an IoC container, with all wiring living in composition roots.&lt;/p&gt;

&lt;p&gt;In a monolith:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// monolith/composition-root.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmsModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the organization decides to split services, new composition roots select different subsets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// services/user-service/composition-root.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// services/order-service/composition-root.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The components themselves do not change. Only the composition roots change. The boundaries of the system, what constitutes a deployable unit, were never encoded inside the components.&lt;/p&gt;

&lt;p&gt;I documented a concrete case study of this approach: &lt;a href="https://dev.to/remojansen/from-monolith-to-microservices-without-changing-one-line-of-code-thanks-to-the-power-of-inversion-57l6"&gt;From Monolith to Microservices without changing one line of code&lt;/a&gt;. The same source produced either a monolith or a set of microservices depending on which composition roots and CI/CD configuration were used. We even swapped CosmosDB for PostgreSQL by replacing one IoC module.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is not merely the Composition Root pattern
&lt;/h2&gt;

&lt;p&gt;The Composition Root (Mark Seemann) tells you &lt;em&gt;where&lt;/em&gt; to wire dependencies: in a single place close to the application’s entry point. Seemann correctly insists that each &lt;em&gt;deployable artifact&lt;/em&gt; should have exactly one composition root.&lt;/p&gt;

&lt;p&gt;The principle I am naming is different. It is not primarily about &lt;em&gt;where&lt;/em&gt; wiring occurs. It is about what components are allowed to know about their own boundaries.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Composition Root pattern is a &lt;em&gt;mechanism&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Boundary Independence is the &lt;em&gt;reason&lt;/em&gt; to keep components ignorant of shape so that new composition roots can redefine system shape without touching the components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In large systems the ideal of a single pure composition root often becomes hierarchical or framework-assisted wiring. The principle still holds: the decision about which components belong together must remain outside the components themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  The core mechanism: ignorance of boundaries
&lt;/h2&gt;

&lt;p&gt;The consistent mechanism across these patterns is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Components that execute core business logic must remain ignorant of the boundaries that enclose them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A domain service that processes a customer transaction should not know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether its storage dependency is an in-memory map, a local database, or a remote API;&lt;/li&gt;
&lt;li&gt;whether it is running inside a Lambda, an Express process, or a CLI;&lt;/li&gt;
&lt;li&gt;whether an event it emits is handled synchronously or placed on a distributed broker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the component has no access to infrastructure details and no way to query its execution environment, it stays passive to shape changes. Relocate it from a monolith to a serverless worker and it continues to function.&lt;/p&gt;

&lt;p&gt;This is the heart of the idea: &lt;strong&gt;components must not encode assumptions about the shape in which they happen to execute.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Boundary Deferral Spectrum
&lt;/h2&gt;

&lt;p&gt;A useful mental model is a spectrum of &lt;em&gt;how late&lt;/em&gt; boundary decisions can be deferred:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design time (worst)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Boundaries are baked into source structure, import paths, and project layout. Restructuring requires rewriting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composition time (good)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Boundaries are defined only in composition roots. Components are boundary-agnostic. Different roots produce different shapes from the same components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build time (better)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Composition roots (or equivalent configuration) read build arguments or environment variables. A single codebase yields different deployable artifacts. Restructuring becomes a change of build configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime (most deferred)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The system discovers and loads components dynamically. Plugin architectures (MEF, OSGi, VS Code extensions, browser extensions) do not know their final shape until they are running. The system’s boundaries are defined by what is present at runtime.&lt;/p&gt;

&lt;p&gt;Plugin systems are the purest practical embodiment of this spectrum. They are also among the longest-lived and most adaptable software systems we have built, IDEs, browsers, operating systems. They defer boundary decisions until the last responsible moment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Horizontal and vertical slicing
&lt;/h2&gt;

&lt;p&gt;Boundary decisions come in two flavours; both should be deferrable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vertical slices&lt;/strong&gt; concern technical layers: HTTP, domain logic, persistence. In a well-structured onion or hexagonal architecture these layers are already separated by abstractions. The composition root connects them. Swapping an entire persistence technology becomes a composition-root change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Horizontal slices&lt;/strong&gt; concern business capabilities: user management, order processing, content, authentication. In a modular monolith these appear as modules; in a microservices architecture each becomes its own deployable with its own composition root.&lt;/p&gt;

&lt;p&gt;Both kinds of boundary should be defined outside the components. Components should not know whether two repositories live in the same process or across a network, or whether an email service is an in-process call or a remote API. Those are composition-time decisions.&lt;/p&gt;

&lt;p&gt;When both vertical and horizontal boundaries are deferred, the system becomes a collection of Lego bricks. You can assemble a monolith, a set of services, or anything in between without modifying the bricks, only the instruction manual changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The same idea in functional programming
&lt;/h2&gt;

&lt;p&gt;The principle is not limited to OOP or IoC containers.&lt;/p&gt;

&lt;p&gt;In ML-family languages, module functors parameterise an entire module over its dependencies. The wiring occurs at the call site; the functor itself remains boundary-agnostic.&lt;/p&gt;

&lt;p&gt;Effect systems such as ZIO declare dependencies as a type-level environment. Different layer configurations can be assembled for tests, a monolith, or distributed deployment. The program does not change, only the layer composition does.&lt;/p&gt;

&lt;p&gt;Even simple higher-order functions achieve the same effect: a function that receives its collaborators as parameters does not know whether those collaborators are local, remote, or mocked.&lt;/p&gt;

&lt;p&gt;The mechanisms differ; the principle is identical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary Independence and Dependency Inversion
&lt;/h2&gt;

&lt;p&gt;A natural question is whether this is simply Dependency Inversion applied at larger scale.&lt;/p&gt;

&lt;p&gt;There is a close relationship, but they are not the same.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Inversion&lt;/strong&gt; makes a component independent of the &lt;em&gt;concrete implementation&lt;/em&gt; of a dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary Independence&lt;/strong&gt; makes a component independent of the &lt;em&gt;shape&lt;/em&gt; in which that dependency is fulfilled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DIP frees you from a particular database driver or HTTP client. BIP frees you from the assumption that the dependency lives in the same process, the same memory space, or the same failure domain. BIP builds on DIP and extends the reasoning from class and module boundaries to architectural and physical boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architectural trade-offs and costs
&lt;/h2&gt;

&lt;p&gt;Flexibility always has costs. Making components shape-independent introduces real challenges that must be managed deliberately.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Contract design for multiple topologies
&lt;/h3&gt;

&lt;p&gt;Interfaces must express guarantees that remain valid whether the dependency is local or remote. Assumptions about shared memory, instantaneous response, or transactional consistency across components cannot be hidden inside domain logic if the boundary may later move across a network.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Translation and mapping overhead
&lt;/h3&gt;

&lt;p&gt;Data crossing a boundary usually requires mapping between transport formats and domain models. This adds code and a modest performance cost compared with passing raw persistence entities through the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Latency and performance blindness
&lt;/h3&gt;

&lt;p&gt;When code is unaware of boundaries, developers can write sequential chatty interactions that are acceptable in-process but catastrophic across a network. Boundary Independence does not remove the need for performance thinking; it requires that the costs of each shape be made explicit at the boundary (bulk APIs, async messaging, caching strategies, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Consistency, failure, and observability
&lt;/h3&gt;

&lt;p&gt;Network boundaries introduce partial failure, different consistency models, and the need for distributed tracing, circuit breakers, and clear ownership of data. These concerns cannot be fully abstracted away. They must be addressed at the edges while the domain logic remains ignorant of them.&lt;/p&gt;

&lt;p&gt;Boundary Independence does not claim that shape is irrelevant. It claims that shape should remain a &lt;em&gt;replaceable architectural decision&lt;/em&gt;, while the semantics and costs of each shape are handled explicitly at the boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Has this been said before?
&lt;/h2&gt;

&lt;p&gt;Yes, but scattered across different communities and disciplines.&lt;/p&gt;

&lt;p&gt;The practice is not new. Strong teams have been building boundary-agnostic components and deciding system shape at composition time for years. What has been missing is a concise, named principle that sits cleanly alongside SOLID and explicitly connects the dots.&lt;/p&gt;

&lt;h3&gt;
  
  
  The existing sources
&lt;/h3&gt;

&lt;p&gt;These concepts and patterns already exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Robert C. Martin&lt;/strong&gt; (Clean Micro-service Architecture): “The Deployment Model is a Detail,” “Forced Ignorance”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mark Seemann&lt;/strong&gt; (Dependency Injection pattern): The Composition Root mechanism; exactly one composition root per deployable artifact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Lean community&lt;/strong&gt;: “Decide as Late as Possible” — defer irreversible decisions until the last responsible moment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain-Driven Design &amp;amp; Hexagonal/Onion Architecture&lt;/strong&gt;: Isolate domain logic behind abstraction boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin architectures&lt;/strong&gt; (VS Code, browsers, operating systems): Runtime deferral of component identity and shape&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular Monoliths&lt;/strong&gt; &amp;amp; &lt;strong&gt;Modular Monolith Architecture&lt;/strong&gt;: Treat internal boundaries as if they might become external later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not new ideas. This article is not inventing them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's missing: the synthesis
&lt;/h3&gt;

&lt;p&gt;However, these sources treat the idea as &lt;em&gt;separate concerns&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uncle Bob writes about deployment models and clean architecture, but still largely assumes component boundaries are fixed in project or package structure.&lt;/li&gt;
&lt;li&gt;The Composition Root pattern tells you &lt;em&gt;where&lt;/em&gt; to wire, but not that components must be deliberately designed to be boundary-agnostic.&lt;/li&gt;
&lt;li&gt;“Decide as Late as Possible” is a general heuristic, not a specific principle about system boundaries.&lt;/li&gt;
&lt;li&gt;Plugin architectures demonstrate the idea perfectly, but are often treated as a specialist domain (browser extensions, IDEs) rather than a principle that applies to all systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these sources state the full, unified claim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The boundaries of a system — which components are grouped together, what constitutes a vertical or horizontal slice, what constitutes a deployable unit — should be defined outside the components themselves (normally at composition-root level or later) and should be reconfigurable without modifying the components.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why name it?
&lt;/h3&gt;

&lt;p&gt;By naming the principle and showing its convergence across patterns, we can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make it teachable&lt;/strong&gt; — rather than learning Composition Root, Hexagonal Architecture, and modular monoliths as separate ideas, teams can learn one unifying principle and recognize it in all of them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enable deliberate design&lt;/strong&gt; — teams can ask “Are we applying BIP?” during architecture and code review, with a shared language.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connect it to SOLID&lt;/strong&gt; — BIP is the architectural answer to a question SOLID leaves open: “Once I have excellent components, how do I ensure the system's shape remains flexible?” Naming it makes that connection explicit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prevent accidental commitment&lt;/strong&gt; — when teams see that plugin architectures, successful modular monoliths, and successful microservices migrations all rely on the same underlying principle, they can avoid encoding shape decisions inside domain logic by default, not by luck.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  The principle
&lt;/h3&gt;

&lt;p&gt;I am naming the existing, convergent practice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Boundary Independence Principle (BIP)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Components should declare what they require to operate, while remaining ignorant of how, where, or when those requirements are fulfilled across system boundaries. System boundaries, both vertical (technical layers) and horizontal (business capabilities), should be defined at the composition-root level (or later), never inside the components themselves. Boundary decisions should be deferred as late as practically possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More concisely: &lt;em&gt;system boundaries are independent of the components themselves&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This extends the familiar framing — database is a detail, framework is a detail, deployment model is a detail — to its logical conclusion: the boundaries themselves are a detail too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Intellectual lineage
&lt;/h2&gt;

&lt;p&gt;This principle is built on decades of work by many practitioners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alistair Cockburn&lt;/strong&gt; — Hexagonal Architecture / Ports &amp;amp; Adapters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robert C. Martin&lt;/strong&gt; — Clean Architecture, dependency inversion, deployment as a detail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mark Seemann&lt;/strong&gt; — Dependency Injection patterns and the Composition Root&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain-Driven Design community&lt;/strong&gt; — Entity, aggregate, and bounded context boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lean software development&lt;/strong&gt; — Defer decisions until the last responsible moment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin architecture patterns&lt;/strong&gt; — Used successfully in OS kernels, browsers, IDEs, and application platforms for decades&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contribution of naming Boundary Independence is not inventing a new idea. It is making explicit what these patterns converge on, giving it a place alongside SOLID, and enabling teams to apply it deliberately rather than accidentally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical application
&lt;/h2&gt;

&lt;p&gt;Naming the principle gives teams a shared heuristic for design and review:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Architecture and code reviews&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Ask: “Does this domain logic contain assumptions about its execution boundary?” Importing infrastructure SDKs, assuming shared transactions, or hard-wiring module relationships inside business logic are boundary leaks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deferring expensive decisions&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Start with a modular monolith whose internal boundaries are already BIP-compliant. Extract services later only when metrics justify the operational cost. The extraction cost drops dramatically because the components were never entangled with the old shape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Domain logic that is oblivious to its runtime environment can be tested with lightweight in-memory adapters. This simplifies unit and many integration tests. It does &lt;em&gt;not&lt;/em&gt; eliminate the need for contract tests, end-to-end tests, or chaos experiments once real distribution appears; those remain necessary at the boundaries.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not to make every system ready to become a microservice architecture on day one. The goal is to avoid accidentally encoding shape decisions inside the components that should not have to care.&lt;/p&gt;




&lt;h2&gt;
  
  
  SOLID + BIP
&lt;/h2&gt;

&lt;p&gt;SOLID without Boundary Independence has always felt incomplete. SOLID gives you high-quality components. Without a principle that keeps boundaries deferred and external to those components, teams still cement the bricks into a shape too early. When the shape must change, and it almost always must, the cost is far higher than it needed to be.&lt;/p&gt;

&lt;p&gt;The teams that extract the most value from SOLID are the ones that, whether they name it or not, also practise Boundary Independence. Their systems behave like plugin architectures even when they are not formally plugins. Components do not know their own shape. Boundaries live in one place (or a small number of controlled places). Changing that place changes the shape of the system.&lt;/p&gt;

&lt;p&gt;SOLID gives us the bricks. BIP keeps the mortar from setting too soon.&lt;/p&gt;

&lt;p&gt;I would love to hear your thoughts.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>discuss</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Why Developer Experience (DevEx) Dies in the Backlog?</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Mon, 03 Aug 2026 01:10:16 +0000</pubDate>
      <link>https://dev.to/remojansen/please-stop-calling-it-developer-experience-27ec</link>
      <guid>https://dev.to/remojansen/please-stop-calling-it-developer-experience-27ec</guid>
      <description>&lt;p&gt;"Technical debt" might be one of the most successful terms ever coined in software engineering.&lt;/p&gt;

&lt;p&gt;Not because it solved the problem. Technical debt is still everywhere. But it fundamentally changed the conversation. Before the term existed, engineers struggled to explain why seemingly harmless shortcuts eventually become expensive. Calling it &lt;em&gt;technical debt&lt;/em&gt; gave business leaders a language they already understood. Debt accumulates interest. Debt carries risk. Debt demands attention.&lt;/p&gt;

&lt;p&gt;It was a brilliant metaphor. The metaphor didn't eliminate technical debt, but it changed how organizations think about it. Today, no serious engineering organization questions whether technical debt exists. The conversation has shifted from &lt;em&gt;Is this a real problem?&lt;/em&gt; to &lt;em&gt;What should we do about it?&lt;/em&gt; That's the power of language. Today, our industry has another equally pervasive problem.&lt;/p&gt;

&lt;p&gt;Developers spend their days fighting slow build systems, brittle internal frameworks, unreliable tooling, unnecessary processes, endless context switching and organizational friction. Every one of these things quietly reduces an organization's ability to build great software, yet we rarely talk about them as strategic business problems. We have a name for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And I believe we've chosen the wrong one. Not because the underlying idea is wrong, but because the name fails to communicate what is actually at stake.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wrong Conversation
&lt;/h2&gt;

&lt;p&gt;At first glance, &lt;em&gt;Developer Experience&lt;/em&gt; seems like a perfectly reasonable name. After all, nobody questions the importance of &lt;em&gt;Customer Experience&lt;/em&gt;. Entire organizations are built around improving it.&lt;/p&gt;

&lt;p&gt;So why hasn't &lt;em&gt;Developer Experience&lt;/em&gt; had the same impact? I think the answer is surprisingly simple. The relationship between customer experience and business performance is obvious. Happy customers buy products, remain loyal and recommend them to others. Executives instinctively understand why customer experience deserves investment. The relationship between developer experience and engineering performance is far less obvious.&lt;/p&gt;

&lt;p&gt;When executives hear &lt;em&gt;Developer Experience&lt;/em&gt;, many unconsciously categorize it as an internal quality-of-life initiative. Something that would be nice to improve once more important business priorities have been addressed.&lt;/p&gt;

&lt;p&gt;Ironically, the same executives care deeply about engineering performance. They worry when expensive engineering teams deliver slowly. They worry when quality declines. They worry when innovation stalls. They just don't naturally connect those outcomes with what we currently call &lt;em&gt;Developer Experience&lt;/em&gt;. That's the problem. The name hides the real business impact.&lt;/p&gt;

&lt;p&gt;As a result, conversations about developer productivity often drift in an unhealthy direction. Instead of asking how to build an environment where engineers consistently perform at their best, we ask questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How can we measure developers?&lt;/li&gt;
&lt;li&gt;How can we increase output?&lt;/li&gt;
&lt;li&gt;How can we ship more features?&lt;/li&gt;
&lt;li&gt;How can AI make developers faster?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions all assume performance is something we extract from people. I think that's backwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Is an Emergent Property
&lt;/h2&gt;

&lt;p&gt;Imagine an elite sports team. Nobody expects world-class athletes to win championships simply because they have better shoes. High-performance organizations invest across the entire system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coaching&lt;/li&gt;
&lt;li&gt;Training&lt;/li&gt;
&lt;li&gt;Nutrition&lt;/li&gt;
&lt;li&gt;Recovery&lt;/li&gt;
&lt;li&gt;Medical care&lt;/li&gt;
&lt;li&gt;Sports psychology&lt;/li&gt;
&lt;li&gt;Team dynamics&lt;/li&gt;
&lt;li&gt;Equipment&lt;/li&gt;
&lt;li&gt;Leadership&lt;/li&gt;
&lt;li&gt;Purpose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No one believes any single investment creates champions. Performance emerges from the interaction of the whole system. Software engineering is no different. Good tooling matters. Fast build systems matter. AI coding assistants matter.&lt;/p&gt;

&lt;p&gt;But so do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autonomy&lt;/li&gt;
&lt;li&gt;Ownership&lt;/li&gt;
&lt;li&gt;Trust&lt;/li&gt;
&lt;li&gt;Sustainable pace&lt;/li&gt;
&lt;li&gt;Customer proximity&lt;/li&gt;
&lt;li&gt;Protected focus time&lt;/li&gt;
&lt;li&gt;Psychological safety&lt;/li&gt;
&lt;li&gt;Fast feedback loops&lt;/li&gt;
&lt;li&gt;Cross-functional collaboration&lt;/li&gt;
&lt;li&gt;The freedom to improve the system itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are often discussed as independent initiatives. They're not. They're all investments in engineering performance. This holistic reality is precisely why frameworks like SPACE exist. Developed by researchers from GitHub and Microsoft, the SPACE framework—which evaluates Satisfaction, Performance, Activity, Communication, and Efficiency—was built to stop organizations from falling into the trap of measuring developers purely by raw output (Activity). It proves mathematically and empirically that Performance and Efficiency cannot exist without Satisfaction and effective Communication.&lt;/p&gt;

&lt;p&gt;When we look at software through the lens of SPACE, we see that developer well-being and system performance aren't competing priorities—they are interdependent variables of the exact same equation. That's why I believe &lt;em&gt;Developer Experience&lt;/em&gt; has unintentionally constrained the conversation. The name naturally draws our attention toward tools and developer happiness, when the real objective is building an environment where exceptional engineering performance emerges naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interest Rate Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Technical debt is usually discussed in terms of its external cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slower delivery&lt;/li&gt;
&lt;li&gt;More production defects&lt;/li&gt;
&lt;li&gt;Operational incidents&lt;/li&gt;
&lt;li&gt;Higher infrastructure costs&lt;/li&gt;
&lt;li&gt;Lost customers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These costs are real. But technical debt has another interest rate. The human one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every day spent fighting the tooling.&lt;/li&gt;
&lt;li&gt;Every obvious refactoring postponed because "we don't have time."&lt;/li&gt;
&lt;li&gt;Every unnecessary approval.&lt;/li&gt;
&lt;li&gt;Every layer separating engineers from customers.&lt;/li&gt;
&lt;li&gt;Every interruption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every reminder that shipping today's feature matters more than improving tomorrow's system. None of these destroys motivation overnight. They compound.&lt;/p&gt;

&lt;p&gt;Eventually developers stop improving the system. Not because they stop caring. Because the system slowly teaches them that caring isn't rewarded. This is the part of technical debt we rarely discuss.&lt;/p&gt;

&lt;p&gt;Technical debt doesn't only accumulate in software. It accumulates in people. Developers gradually lose ownership. Curiosity becomes compliance. Craftsmanship gives way to expediency. Enthusiasm slowly erodes into indifference.&lt;/p&gt;

&lt;p&gt;Those costs rarely appear on a balance sheet, but every engineering organization eventually pays them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maybe We've Been Optimizing the Wrong Abstraction
&lt;/h2&gt;

&lt;p&gt;If I asked an executive whether engineering performance matters, the answer would be immediate. If I asked whether developer experience matters, the conversation would probably require an explanation. Those are fundamentally different starting points. The irony is that we’re often talking about exactly the same thing.&lt;/p&gt;

&lt;p&gt;A developer who spends hours waiting for builds, navigating brittle internal platforms, fighting legacy code, requesting permission for obvious refactorings, and constantly context-switching isn't simply having a poor experience. The organization is paying a massive price to hold them in that environment.&lt;/p&gt;

&lt;p&gt;Maybe we should stop calling it Developer Experience and start calling it Developer Carry Cost.&lt;/p&gt;

&lt;p&gt;In finance, Carry Cost is the ongoing expense required to hold and maintain an asset while waiting for it to generate a return. If you buy a physical or financial asset, you don't just pay for the acquisition—you pay the carrying costs to keep it operational.&lt;/p&gt;

&lt;p&gt;In software engineering, the developer is the primary asset. Software is merely what that asset produces. &lt;/p&gt;

&lt;p&gt;Now, framing human beings as "assets" and human frustration as a "carry cost" might feel uncomfortably cold—especially to engineers who are tired of being treated like cog-in-the-machine resources. But that discomfort is precisely the point.&lt;/p&gt;

&lt;p&gt;The comfortable, polite language of "developer experience" and "well-being" has failed us. It allows leadership to relegate systemic engineering friction to an HR quality-of-life initiative—something to look into once "real" business priorities are met. &lt;/p&gt;

&lt;p&gt;When developers spend 30% of their day battling broken tooling, slow pipelines, and administrative drag, that isn't just friction—it’s an astronomical Developer Carry Cost. You are paying top-market compensation for a high-value asset, while burning a third of its capacity just carrying it through a flawed system. Worse, under these conditions, human beings eventually disengage. They check out. The asset stops yielding not because the person lacks talent, but because the environment has worn them down.&lt;/p&gt;

&lt;p&gt;Technical debt is often just the visible symptom on the codebase. Developer Carry Cost is the actual disease on the balance sheet.&lt;/p&gt;

&lt;p&gt;We don't need softer words; we need language that immediately communicates what is at stake to leadership. Investing in the engineering environment isn't an internal luxury or an employee perk; it is a direct investment in lowering our Developer Carry Cost so our most valuable people can actually do their best work.&lt;/p&gt;

&lt;p&gt;Why do so many organizations still struggle with developer experience? What do you make of the term Developer Carry Cost? Can you think of a better alternative? I’d love to hear your thoughts in the comments.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>leadership</category>
      <category>discuss</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Emergent Design &amp; Gall's Law: When Complex Coding Problems Dissolve Instead of Being Solved</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Thu, 30 Jul 2026 12:05:34 +0000</pubDate>
      <link>https://dev.to/remojansen/emergent-design-galls-law-when-complex-coding-problems-dissolve-instead-of-being-solved-3lok</link>
      <guid>https://dev.to/remojansen/emergent-design-galls-law-when-complex-coding-problems-dissolve-instead-of-being-solved-3lok</guid>
      <description>&lt;p&gt;I recently read &lt;a href="https://dev.to/notaphplover/a-year-of-shaving-nanoseconds-off-dependency-injection-how-inversifyjs-rebuilt-its-resolution-4hnb"&gt;an article by the main maintainer of InversifyJS describing the journey of rebuilding its dependency resolution algorithm&lt;/a&gt;. What caught my attention wasn't the performance improvements or the technical details. It was something much more familiar.&lt;/p&gt;

&lt;p&gt;As I was reading, I realized they were experiencing the exact same phenomenon I had experienced years ago while creating InversifyJS. It reminded me of something that, until now, I had never really put into words.&lt;/p&gt;

&lt;h2&gt;
  
  
  The temptation to solve the hardest problem first
&lt;/h2&gt;

&lt;p&gt;Every engineer has experienced it. You're implementing a feature when you encounter a design problem that feels wrong. You know the current approach won't scale, and you know there must be a beautiful abstraction somewhere, so you stop writing code and start designing.&lt;/p&gt;

&lt;p&gt;Sometimes that's the right thing to do. Many times it isn't.&lt;/p&gt;

&lt;p&gt;While building InversifyJS, I eventually adopted a different habit. Whenever I found myself thinking, &lt;em&gt;"This feels too complicated, and I can't find a simple, elegant solution right now,"&lt;/em&gt; I decided to wait.&lt;/p&gt;

&lt;p&gt;Not because I ignored the problem, but because I didn't think I understood it well enough yet.&lt;/p&gt;

&lt;p&gt;Instead, I focused on features where I had a reasonable level of confidence. I kept improving the parts of the system that felt obvious, leaving the difficult problems untouched.&lt;/p&gt;

&lt;p&gt;At first, this almost felt irresponsible. Over time, it became one of the most valuable engineering lessons I have learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The magic wasn't finding the solution
&lt;/h2&gt;

&lt;p&gt;The interesting part is that I rarely came back later with a better idea. Something stranger happened.&lt;/p&gt;

&lt;p&gt;Implementing those simpler features changed the system itself. New abstractions naturally appeared. Responsibilities became clearer. Concepts that previously seemed unrelated suddenly fit together.&lt;/p&gt;

&lt;p&gt;Eventually, I would return to the "hard" problem only to discover it wasn't hard anymore. Not because I had become smarter or because inspiration had struck overnight. The problem itself had changed.&lt;/p&gt;

&lt;p&gt;Or perhaps a better way to put it is that &lt;strong&gt;the problem dissolved instead of being solved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Looking back, I think this is one of the defining characteristics of good software design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;Every feature teaches you something about the problem domain. When we invent abstractions too early, we are making educated guesses. Sometimes we are right, but often we are creating abstractions around our current understanding rather than around the problem itself.&lt;/p&gt;

&lt;p&gt;As more concrete features are implemented, patterns emerge naturally. The code starts revealing what actually belongs together. The abstraction is not invented; it is discovered.&lt;/p&gt;

&lt;p&gt;This is why experienced engineers often say things like &lt;em&gt;"the abstraction revealed itself,"&lt;/em&gt; &lt;em&gt;"the design fell out naturally,"&lt;/em&gt; or &lt;em&gt;"the code told me what it wanted."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These expressions sound almost mystical, but they describe a very practical engineering process. You are collecting evidence. Eventually, there is enough evidence that the right design becomes obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gall's Law explains it perfectly
&lt;/h2&gt;

&lt;p&gt;Years later, I came across Gall's Law from &lt;em&gt;Systemantics&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A complex system that works is invariably found to have evolved from a simple system that worked."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think this is one of the best observations ever made about software architecture.&lt;/p&gt;

&lt;p&gt;Notice what it doesn't say. It doesn't say complex systems cannot be designed. It says the ones that actually work usually evolve.&lt;/p&gt;

&lt;p&gt;That perfectly matches my experience with InversifyJS.&lt;/p&gt;

&lt;p&gt;Many of the abstractions people now associate with the library were not part of some grand master plan. They emerged because earlier, simpler decisions created the right conditions for them.&lt;/p&gt;

&lt;p&gt;Each feature made the next decision a little clearer. Each change revealed more about the shape of the problem. Eventually, some of the hardest problems no longer looked hard at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emergent design as pragmatism, not procrastination
&lt;/h2&gt;

&lt;p&gt;The approach I am describing is often called emergent design.&lt;/p&gt;

&lt;p&gt;Emergent design is the idea that a software system’s architecture should evolve from the implementation of real requirements rather than being fully designed upfront. Instead of trying to predict every future need and creating abstractions in advance, engineers start with the simplest solution that works and allow the design to emerge through usage, feedback, and refactoring.&lt;/p&gt;

&lt;p&gt;This does not mean designing without a plan or letting a codebase grow chaotically. It means recognizing that some architectural decisions are easier to make when we have more information. The structure of a system becomes clearer as we solve concrete problems, discover recurring patterns, and learn which concepts genuinely deserve to exist as abstractions.&lt;/p&gt;

&lt;p&gt;It is important to distinguish this from simply avoiding difficult work. Emergent design is not an excuse to postpone decisions forever or to avoid thinking deeply about architecture. It is a pragmatic approach to dealing with uncertainty.&lt;/p&gt;

&lt;p&gt;One of the easiest mistakes in software development is trying to solve problems before we fully understand them. We start designing abstractions for scenarios that might never happen, or we optimize parts of the system before we know where the real problems are.&lt;/p&gt;

&lt;p&gt;This is how overengineering and premature optimizations often begin.&lt;/p&gt;

&lt;p&gt;The problem is not that we are thinking too much. The problem is that we are making decisions based on assumptions instead of evidence.&lt;/p&gt;

&lt;p&gt;A pragmatic engineer understands that some decisions become easier when the system has more information. Instead of building a complex solution for a hypothetical future problem, they focus on solving the problems that exist today in the simplest way possible.&lt;/p&gt;

&lt;p&gt;Each solved problem gives you more information. Each refactoring changes the design space. Each feature teaches you something new.&lt;/p&gt;

&lt;p&gt;The goal is not to avoid complexity. The goal is to introduce complexity only when it is justified.&lt;/p&gt;

&lt;h2&gt;
  
  
  A lesson I keep relearning
&lt;/h2&gt;

&lt;p&gt;Reading the recent article about InversifyJS reminded me that this was not a unique experience. I had always assumed it was just a consequence of how I happened to build the project, but it was fascinating to see another maintainer independently arrive at the same place years later.&lt;/p&gt;

&lt;p&gt;It is easy to think good architecture comes from moments of brilliance or from correctly predicting the future. My experience has been almost the opposite.&lt;/p&gt;

&lt;p&gt;The best designs I have worked on did not come from trying to anticipate every possible problem or by creating the most sophisticated architecture from the beginning. They came from being pragmatic, solving real problems as they appeared, and allowing the design to evolve with better information.&lt;/p&gt;

&lt;p&gt;Sometimes the most productive thing an engineer can do is not to solve the hardest problem immediately. It is to solve the next simplest one with confidence, learn from it, and let that knowledge reshape the system.&lt;/p&gt;

&lt;p&gt;This approach avoids the trap of overengineering while also preventing premature optimizations based on assumptions.&lt;/p&gt;

&lt;p&gt;If you are patient, and if the design is evolving in the right direction, you will occasionally discover something surprising.&lt;/p&gt;

&lt;p&gt;The hard problem no longer exists.&lt;/p&gt;

&lt;p&gt;It didn't get solved.&lt;/p&gt;

&lt;p&gt;It dissolved.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I tried to compile TypeScript into a native binary with scriptc</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Thu, 30 Jul 2026 09:44:14 +0000</pubDate>
      <link>https://dev.to/remojansen/i-tried-to-compile-typescript-into-a-native-binary-with-scriptc-5d07</link>
      <guid>https://dev.to/remojansen/i-tried-to-compile-typescript-into-a-native-binary-with-scriptc-5d07</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I tried to compile the TypeScript 6 compiler into a native binary with &lt;a href="https://scriptc.dev/" rel="noopener noreferrer"&gt;scriptc&lt;/a&gt;, and I failed — it got 90% of the way there and then hit an internal compiler error it couldn't get past, so there is no native &lt;code&gt;tsc&lt;/code&gt; at the end of this story. But it's a failure worth having: I learned exactly where a week-old TypeScript-to-native compiler runs out of road, and I got some interesting numbers along the way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So when &lt;a href="https://scriptc.dev/" rel="noopener noreferrer"&gt;scriptc&lt;/a&gt; was released to the public a few days ago, I knew exactly how my week was going to end.&lt;/p&gt;

&lt;p&gt;If you haven't seen it yet, scriptc is a compiler that takes TypeScript — plain, ordinary TypeScript, no special dialect, no annotations — and turns it into a small, fast native binary. No Node.js. No V8. No JavaScript engine shipped alongside your code. A "hello world" comes out at around 320KB and starts in a few milliseconds. The pitch is bold: &lt;em&gt;what compiles behaves byte-for-byte like Node&lt;/em&gt;. It does this with a three-tier model — most code lowers straight to native, anything too dynamic can opt into an embedded engine with a &lt;code&gt;--dynamic&lt;/code&gt; flag, and the truly impossible fails at build time with a precise diagnostic instead of a surprise.&lt;/p&gt;

&lt;p&gt;It's experimental. It's early. It's exactly the kind of thing I can't leave alone.&lt;/p&gt;

&lt;p&gt;And almost immediately, a mischievous thought showed up: &lt;strong&gt;could I compile TypeScript itself?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a toy. Not a fibonacci function. The actual compiler — &lt;code&gt;tsc&lt;/code&gt; — the thing that has type-checked basically every line of TypeScript I've ever written. If scriptc can turn &lt;em&gt;that&lt;/em&gt; into a native binary, it can turn anything into a native binary. It felt like the ultimate stress test, and I wanted to see it either fly or fall over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 6, and not 7?
&lt;/h2&gt;

&lt;p&gt;Here's where the timing gets interesting.&lt;/p&gt;

&lt;p&gt;If you've been following the TypeScript roadmap, you know the ground just shifted. TypeScript 6.0 shipped as the &lt;strong&gt;final JavaScript-based release&lt;/strong&gt; of the compiler, and TypeScript 7 is the ground-up rewrite in &lt;strong&gt;Go&lt;/strong&gt; — the "native" port the team has been building in the open. So we now live in a brief, strange window where two &lt;code&gt;tsc&lt;/code&gt;s exist side by side: the old one written in TypeScript, and the new one written in Go.&lt;/p&gt;

&lt;p&gt;That window is the whole reason this experiment is fun.&lt;/p&gt;

&lt;p&gt;Compiling TypeScript 7 with scriptc would make no sense — it's already native. It's &lt;em&gt;already&lt;/em&gt; Go compiled down to a machine binary. There's nothing to prove there. But TypeScript 6 is different. TypeScript 6 is the last version of the compiler that is itself &lt;em&gt;written in TypeScript&lt;/em&gt;. It's the perfect candidate: a real, enormous, battle-tested TypeScript program that I could feed to scriptc and ask, politely, to make native.&lt;/p&gt;

&lt;p&gt;And it sets up a delicious comparison. On one side, TypeScript 6 — the JavaScript compiler, dragged into native code by scriptc. On the other, TypeScript 7 — the Go compiler, native by birth. Two paths to the same destination. I wanted to race them.&lt;/p&gt;

&lt;p&gt;So I pinned my versions — &lt;code&gt;typescript@6.0.3&lt;/code&gt; and the Go build &lt;code&gt;typescript@7&lt;/code&gt; — wrote a handful of scripts so I could reproduce the whole thing on demand, and told scriptc to do its worst.&lt;/p&gt;

&lt;h2&gt;
  
  
  The climb
&lt;/h2&gt;

&lt;p&gt;The first surprise was a pleasant one.&lt;/p&gt;

&lt;p&gt;I pointed scriptc's coverage tool at the shipped &lt;code&gt;tsc&lt;/code&gt; bundle — a single ~6MB file of generated JavaScript — bracing for a wall of red. Instead: &lt;strong&gt;90% of statements compile statically.&lt;/strong&gt; 51,045 out of 56,117. Nine out of every ten statements in the TypeScript compiler lower to native code without so much as a flag. That number genuinely made me sit up. The remaining 10% — the dynamic corners of a program that has to reflect on itself constantly — is exactly what &lt;code&gt;--dynamic&lt;/code&gt; exists for.&lt;/p&gt;

&lt;p&gt;So I asked for a &lt;code&gt;--dynamic&lt;/code&gt; build. And scriptc fell over.&lt;/p&gt;

&lt;p&gt;Not gracefully, either. A stack overflow — deep inside scriptc's own lowering pass, recursing until V8 ran out of room. Later I hit some &lt;strong&gt;internal compiler errors.&lt;/strong&gt; Six of them, spread across three patterns. A sparse array initializer in &lt;code&gt;createBracketsMap&lt;/code&gt; that scriptc couldn't represent. A &lt;code&gt;return condition ? voidCall() : voidCall()&lt;/code&gt; in the binder that tripped a "ternary must not be void" check. And a cryptic one — &lt;em&gt;"union u50: arm 0 is jsval"&lt;/em&gt; — that pointed at nothing in particular.&lt;/p&gt;

&lt;p&gt;Two of those three, I could fix. I wrote a tiny, behavior-preserving codemod that rewrote the bracket map and unrolled that ternary into a plain &lt;code&gt;if/else&lt;/code&gt;. Five of the six errors vanished. I was, briefly, convinced I was going to win.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wall
&lt;/h2&gt;

&lt;p&gt;The sixth error did not move.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"union u50: arm 0 is jsval"&lt;/em&gt; — reported at line 1, column 1, which is scriptc's way of telling you it isn't about any single line at all. It's a whole-program problem: somewhere in the compiler there's a union type where one of the arms is a fully dynamic value, and scriptc v0.0.17 simply doesn't know how to lay that out in native memory. There's no source edit that fixes it, because there's no source location to edit. And it's only the &lt;em&gt;first&lt;/em&gt; such union.&lt;/p&gt;

&lt;p&gt;That's the wall. And I want to be precise about what kind of wall it is: it's not a missing feature or a flag I forgot. It's the compiler's own youth. scriptc is at version 0.0.17. Asking it to swallow the entire TypeScript compiler — one of the most reflective, self-referential codebases in the JavaScript world — was always going to be the boss fight, not the tutorial.&lt;/p&gt;

&lt;p&gt;So, no. I did not get a native &lt;code&gt;tsc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark I &lt;em&gt;could&lt;/em&gt; run
&lt;/h2&gt;

&lt;p&gt;But I'd built the whole racetrack, and I wasn't going to leave it empty.&lt;/p&gt;

&lt;p&gt;If I couldn't put native-TypeScript-6 on the starting line, I could at least run the closest honest race: type-check the exact same project — the TypeScript compiler's own &lt;code&gt;src/compiler&lt;/code&gt; source, &lt;code&gt;--noEmit&lt;/code&gt;, same machine — with everything that could actually run it. And to be fair to scriptc, I also gave it two programs it &lt;em&gt;could&lt;/em&gt; fully compile — a cold-start "hello" and a compute-heavy prime sieve — so it wasn't judged only by the one test it fails.&lt;/p&gt;

&lt;p&gt;Here's everything in one place. &lt;strong&gt;Node (6.x)&lt;/strong&gt; is TypeScript 6 running on V8; &lt;strong&gt;Go (7.x)&lt;/strong&gt; is &lt;code&gt;tsgo&lt;/code&gt;; &lt;strong&gt;scriptc.dev&lt;/strong&gt; is the native binary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Node (6.x)&lt;/th&gt;
&lt;th&gt;Go (7.x)&lt;/th&gt;
&lt;th&gt;scriptc.dev&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type-check &lt;code&gt;tsc&lt;/code&gt;'s own source (&lt;code&gt;--noEmit&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;523 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;107 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✗ won't compile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start (&lt;code&gt;console.log&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;48.9 ms&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.6 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute (primes under 2,000,000)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.24 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;2.33 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things jump out.&lt;/p&gt;

&lt;p&gt;On the workload that actually matters — type-checking real code — the &lt;strong&gt;Go compiler is roughly five times faster&lt;/strong&gt; than TypeScript 6 on Node. On bigger projects the team reports closer to ten, and I believe them; here, Node's startup is eating a real slice of TS6's time. The rewrite is not hype. It's just fast. (&lt;code&gt;tsgo&lt;/code&gt; is a compiler, not a general-purpose runtime, so it doesn't line up for the other two rows — hence the dashes.)&lt;/p&gt;

&lt;p&gt;Then the good news for scriptc: its native binary starts in &lt;strong&gt;3.6 ms&lt;/strong&gt; against Node's &lt;strong&gt;48.9 ms&lt;/strong&gt; — about &lt;strong&gt;13× faster&lt;/strong&gt;, in a self-contained 350KB file with no runtime to boot. That's the dream, right there.&lt;/p&gt;

&lt;p&gt;And then the humbling one: on a hot numeric loop, &lt;strong&gt;Node was 9.6× faster&lt;/strong&gt; than scriptc's native output. V8's JIT has been tuned for a decade; scriptc's code generator has been public for a week. That gap is not a scandal.&lt;/p&gt;

&lt;p&gt;So here's the honest shape of it. Even if scriptc &lt;em&gt;had&lt;/em&gt; compiled &lt;code&gt;tsc&lt;/code&gt;, it would have done so in &lt;code&gt;--dynamic&lt;/code&gt; mode — the same JavaScript compiler, running on scriptc's &lt;em&gt;embedded&lt;/em&gt; engine rather than natively lowered logic. Given what I saw, that binary would most likely have been &lt;em&gt;slower&lt;/em&gt; than TypeScript 6 on Node, and nowhere near Go. The prize was never going to be speed. The prize would have been a single, dependency-free, instantly-starting binary — and that is a prize worth wanting.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>tooling</category>
      <category>node</category>
      <category>scriptc</category>
    </item>
    <item>
      <title>Your AI Agents Need Finite State Machines (FSMs)</title>
      <dc:creator>Remo H. Jansen</dc:creator>
      <pubDate>Tue, 28 Jul 2026 23:40:17 +0000</pubDate>
      <link>https://dev.to/remojansen/your-ai-agents-need-finite-state-machines-fsms-2i9j</link>
      <guid>https://dev.to/remojansen/your-ai-agents-need-finite-state-machines-fsms-2i9j</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/remojansen/from-rigidity-to-explicitness-how-ai-changes-the-role-of-constraints-in-software-5cp5"&gt;my previous article&lt;/a&gt;, I argued that AI changes the role of constraints in software development.&lt;/p&gt;

&lt;p&gt;For years, many developers saw constraints as something to minimize. Dynamic languages gained popularity over static ones. Schema-less databases promised greater flexibility. Convention replaced configuration. Implicit behavior was often preferred over explicit definitions.&lt;/p&gt;

&lt;p&gt;The motivation was understandable. Constraints felt like friction. They slowed us down. AI changes that trade-off.&lt;/p&gt;

&lt;p&gt;Today, every explicit constraint becomes valuable context that AI can use to better understand our systems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A database schema is no longer just a validation mechanism. It describes the structure and meaning of the data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An OpenAPI specification is no longer just a contract between services. It tells an AI how those services are expected to interact.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A type definition is no longer only for the compiler. It helps AI understand the intent behind an API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Constraints haven't become valuable because they prevent mistakes. They've become valuable because they communicate knowledge. I believe the same principle applies beyond software development. It applies to business workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with AI Workflow Automation
&lt;/h2&gt;

&lt;p&gt;There is enormous excitement around AI-powered workflow automation. Most current approaches are based on autonomous agents. An agent receives a request, reasons about the situation, selects tools, executes actions, and decides what should happen next.&lt;/p&gt;

&lt;p&gt;For simple workflows, this can work remarkably well. For business-critical workflows, however, this architecture introduces unnecessary operational risk.&lt;/p&gt;

&lt;p&gt;The AI is not only performing work. It is also responsible for discovering the business process while executing it. That is asking a probabilistic system to infer something that should already be known.&lt;/p&gt;

&lt;p&gt;Business workflows contain rules that are independent of AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some actions require approval.&lt;/li&gt;
&lt;li&gt;Some actions must never happen twice.&lt;/li&gt;
&lt;li&gt;Some transitions are impossible.&lt;/li&gt;
&lt;li&gt;Some failures require retries.&lt;/li&gt;
&lt;li&gt;Others require compensation or rollback.&lt;/li&gt;
&lt;li&gt;Some actions require human intervention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not AI problems. They are business rules. And business rules should not live inside prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Workflow Should Not Live in the Prompt
&lt;/h2&gt;

&lt;p&gt;When AI workflows become more complex, the common solution is to add more agents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A planner agent coordinates execution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Specialized agents perform individual tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validation agents review the results.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompts become increasingly sophisticated. Yet the workflow itself often remains implicit. The workflow exists only through prompts, examples and emergent agent behavior.&lt;/p&gt;

&lt;p&gt;This makes the process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Difficult to understand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Difficult to test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Difficult to audit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Almost impossible to verify.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Business processes deserve a better foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Make the Workflow Explicit
&lt;/h2&gt;

&lt;p&gt;Instead of asking AI to infer the workflow, define the workflow explicitly.&lt;/p&gt;

&lt;p&gt;One of the simplest and most powerful ways to do this is with a finite state machine.&lt;/p&gt;

&lt;p&gt;A finite state machine explicitly models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the possible states of the process&lt;/li&gt;
&lt;li&gt;the valid transitions between states&lt;/li&gt;
&lt;li&gt;the conditions required for each transition&lt;/li&gt;
&lt;li&gt;the actions associated with every transition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a purchase approval process could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Draft
  ↓
Submitted
  ↓
Approved
  ↓
Purchased
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the process is in the &lt;strong&gt;Submitted&lt;/strong&gt; state, the workflow might expose exactly three valid transitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Approve&lt;/li&gt;
&lt;li&gt;Reject&lt;/li&gt;
&lt;li&gt;Request More Information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI no longer has to invent the workflow. The workflow itself defines what is possible. The AI only has to determine which of the valid actions is the most appropriate. The workflow provides the boundaries. The AI provides the intelligence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explicit Constraints Become Explicit Context
&lt;/h2&gt;

&lt;p&gt;The value of the finite state machine is not simply that it constrains the AI.&lt;/p&gt;

&lt;p&gt;It makes the business process explicit.&lt;/p&gt;

&lt;p&gt;The AI immediately knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current state&lt;/li&gt;
&lt;li&gt;the valid next actions&lt;/li&gt;
&lt;li&gt;the required information&lt;/li&gt;
&lt;li&gt;the business rules&lt;/li&gt;
&lt;li&gt;the possible outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of inferring the workflow from prompts, examples, or previous executions, it receives a precise description of the process.&lt;/p&gt;

&lt;p&gt;Just as an OpenAPI specification helps AI understand an API, a finite state machine helps AI understand a business process.&lt;/p&gt;

&lt;p&gt;In both cases, explicit constraints become high-quality context.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Finite State Machine Is More Than Documentation
&lt;/h2&gt;

&lt;p&gt;This is where finite state machines become particularly interesting.&lt;/p&gt;

&lt;p&gt;Unlike a prompt, an FSM is a formal model.&lt;/p&gt;

&lt;p&gt;That means it is not only explicit.&lt;/p&gt;

&lt;p&gt;It is verifiable.&lt;/p&gt;

&lt;p&gt;Before an AI agent ever executes the workflow, we can analyze the model and answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the workflow reach an invalid state?&lt;/li&gt;
&lt;li&gt;Are there dead-end states?&lt;/li&gt;
&lt;li&gt;Are all approvals enforced?&lt;/li&gt;
&lt;li&gt;Can an action accidentally execute twice?&lt;/li&gt;
&lt;li&gt;Are rollback paths defined?&lt;/li&gt;
&lt;li&gt;Are all failure scenarios handled?&lt;/li&gt;
&lt;li&gt;Can every process eventually reach completion?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are properties that can be verified independently of the AI. This is a fundamental difference from prompt-driven workflows. A prompt describes a process. A finite state machine defines and constrains a process. The FSM becomes an executable safety boundary around the AI.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Operates Inside a Verified Boundary
&lt;/h2&gt;

&lt;p&gt;The objective is not to make AI deterministic.&lt;/p&gt;

&lt;p&gt;That is impossible.&lt;/p&gt;

&lt;p&gt;The objective is to make the environment in which AI operates deterministic.&lt;/p&gt;

&lt;p&gt;Without an explicit workflow, the AI constantly asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should happen next?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With an FSM, the conversation changes.&lt;/p&gt;

&lt;p&gt;The workflow says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These are the only valid actions that can happen next.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given these valid options, which one is the most appropriate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a subtle but profound architectural shift.&lt;/p&gt;

&lt;p&gt;The AI no longer owns the workflow.&lt;/p&gt;

&lt;p&gt;It navigates the workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP Makes This Natural
&lt;/h2&gt;

&lt;p&gt;This model becomes even more powerful when exposed through MCP.&lt;/p&gt;

&lt;p&gt;Instead of exposing dozens of low-level tools, the workflow exposes its current state and the transitions that are currently available.&lt;/p&gt;

&lt;p&gt;An AI agent can discover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current state&lt;/li&gt;
&lt;li&gt;the available transitions&lt;/li&gt;
&lt;li&gt;the information required to perform each transition&lt;/li&gt;
&lt;li&gt;validation rules&lt;/li&gt;
&lt;li&gt;rollback capabilities&lt;/li&gt;
&lt;li&gt;escalation paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow becomes self-describing. Changing the business process means updating the workflow model. Not rewriting prompts. The AI automatically receives the new constraints as part of its context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiy41wmy57oz6x9cdghp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiy41wmy57oz6x9cdghp.jpg" alt="Agent interacts with FSM via MCP" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Explicit Constraints Drastically Reduce Operational Risk
&lt;/h2&gt;

&lt;p&gt;Large language models are probabilistic. Business processes should not be. A finite state machine does not make the AI smarter. It makes the overall system safer.&lt;/p&gt;

&lt;p&gt;By limiting the AI to explicitly defined transitions, we drastically reduce the operational risks of AI-driven workflow automation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Invalid transitions become impossible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every state change is verifiable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every decision is auditable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rollback becomes part of the workflow instead of an afterthought.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compliance rules become explicit instead of implicit.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, the AI no longer decides what is possible. It decides how to navigate a process whose boundaries have already been defined.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explicitness Creates Business Value
&lt;/h2&gt;

&lt;p&gt;This is the same principle I discussed in my previous article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Explicit schemas help AI understand data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit contracts help AI understand APIs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit types help AI understand code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explicit workflows help AI understand business processes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value does not come from adding more constraints. It comes from making the right constraints explicit.&lt;/p&gt;

&lt;p&gt;As AI becomes increasingly responsible for business operations, I believe we will rely less on autonomous agents trying to infer business processes and more on explicit workflow models that define those processes in a way that both humans and AI can understand.&lt;/p&gt;

&lt;p&gt;The future of AI workflow automation is not unlimited autonomy. It is controlled autonomy. AI provides intelligence. Explicit, verifiable workflow models provide correctness. The role of AI is not to own the workflow. Its role is to navigate it intelligently.&lt;/p&gt;

&lt;p&gt;And that is where constraints stop being friction and start becoming business value.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
