<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Owen Zanzal on Medium]]></title>
        <description><![CDATA[Stories by Owen Zanzal on Medium]]></description>
        <link>https://medium.com/@ozanzal_81488?source=rss-d380ba8c5fb0------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*MMco_t94j7J6m5PIUf8FDg.jpeg</url>
            <title>Stories by Owen Zanzal on Medium</title>
            <link>https://medium.com/@ozanzal_81488?source=rss-d380ba8c5fb0------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 04 Nov 2026 22:47:48 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ozanzal_81488/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Conway’s Law for AI Agents: Rethinking Microservices in the Age of AI]]></title>
            <link>https://medium.com/devops-ai/conways-law-for-ai-agents-rethinking-microservices-in-the-age-of-ai-6025497cb0c4?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/6025497cb0c4</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[conways-law]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Thu, 29 Oct 2026 18:48:27 GMT</pubDate>
            <atom:updated>2026-10-29T18:48:27.016Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*umapOmOCNgPc6e6hjA1BUA.gif" /></figure><p>I’ve been spending a lot of time working with AI coding agents on larger codebases, and I’ve started to rethink something I thought I’d already settled years ago: microservices.</p><p>For a long time, my advice to smaller engineering teams has been pretty simple. Start with a monolith. Keep things modular, establish good boundaries, and don’t introduce distributed systems complexity until you have a real reason to. I’ve seen enough companies go all-in on microservices to know how that story can end.</p><p>But working with AI agents has made me realize there might be another reason to reach for microservices. It has less to do with scaling infrastructure or engineering teams and more to do with managing the complexity and risk of making changes to large systems.</p><p>The interesting part is that the underlying constraint might be changing. Historically, we’ve thought about software architecture in terms of how human teams communicate and coordinate. With AI agents doing more of the implementation work, I think we need to start considering a different constraint: <strong>how much context an agent can effectively reason about.</strong></p><p>And perhaps more interestingly, the architectural boundaries we create to accommodate that constraint don’t necessarily have to be permanent.</p><h3>We got a little carried away with microservices</h3><p>Microservices became particularly attractive as engineering organizations grew. When you’ve got multiple teams working on different parts of a large application, breaking functionality into independently deployable services can make a lot of sense. Teams can own their functionality, deploy on their own schedules, and scale parts of the system independently.</p><p>But doing microservices well requires being intentional about both your architecture and your engineering organization. Service boundaries need to reflect real functional boundaries, and the organization needs to be structured to support them.</p><p>A lot of companies learned this the hard way.</p><p>They adopted microservices because it was the modern way to build software, not because they had a problem that required them. The result was often a distributed monolith. All the coupling of a traditional monolith, except now everything communicates over the network.</p><p>Instead of following a function call, you’re digging through distributed traces. Instead of debugging a transaction in one process, you’re trying to figure out which of six services introduced the problem. Deployments have to be coordinated, contracts drift, and suddenly a relatively simple change requires understanding half the system.</p><p>In a large organization, this becomes sprawling technical debt. In a small organization, it can become an existential problem. Every service means another deployment, another CI pipeline, another set of logs, metrics, and traces, and potentially another database to manage.</p><p>That’s a lot of overhead when you’ve only got a handful of engineers.</p><p>None of this is particularly controversial. Martin Fowler and James Lewis discussed these trade-offs in their original writing on microservices, and the industry has spent plenty of time learning what happens when service boundaries don’t align with actual business capabilities.</p><p>So I’ve generally been in the “start with a monolith” camp. And to be clear, I still am.</p><p>But I think AI coding agents are starting to change part of the trade-off.</p><h3>Conway’s Law and the usable context window</h3><p>Conway’s Law tells us that the systems we build tend to reflect the communication structures of the organizations building them. It’s one of the reasons microservices made sense for large engineering organizations. If you have independent teams that need to develop and deploy independently, it helps to have software boundaries that reflect those responsibilities.</p><p>But what happens when much of the implementation work is being done by AI agents?</p><p>I think the forcing function starts to change. Instead of organizing software primarily around the communication boundaries of human teams, we also need to consider how much context an AI agent can effectively reason about.</p><p>And I don’t mean the advertised context window of a model.</p><p>Just because a model can accept hundreds of thousands, or even millions, of tokens doesn’t mean it can reliably reason about every dependency, historical trade-off, and unintended consequence represented in that context.</p><p>What matters is the <em>usable context window</em>. How much relevant information can an agent actually understand well enough to make a change and establish reasonable confidence that the change is correct?</p><p>That’s a much more interesting constraint than the raw number of tokens a model can process.</p><p>In a large legacy system, an agent might be able to read an enormous amount of code, but that doesn’t mean it has a reliable mental model of how everything interacts. The problem isn’t necessarily accessing the information. It’s understanding which information matters, how the pieces relate to one another, and what consequences a change might introduce.</p><p>A well-designed architectural boundary can help. Explicit contracts, clear data ownership, limited dependencies, and independent verification give the agent a more manageable problem to work on.</p><p>This is where microservices become interesting again. Not necessarily because we need independent teams or independent scaling, but because a service boundary can create a useful unit of work that fits within an agent’s effective reasoning capabilities.</p><p>That doesn’t mean we should size services according to token counts or create one service per agent. That would be a pretty terrible way to design software. The relevant boundary isn’t how many lines of code an agent can read. It’s how much of the system it needs to understand to make a particular change safely.</p><p><strong>Conway’s Law reflected the communication constraints of human organizations. AI-driven architecture may increasingly reflect the reasoning constraints of AI agents.</strong></p><p>I don’t think this replaces Conway’s Law. Humans still own the systems, make architectural decisions, and remain responsible for production. But it introduces another constraint that may become increasingly important as agents take on more implementation work.</p><h3>The problem with AI agents and large codebases</h3><p>One thing I’ve noticed working with AI agents on substantial codebases is that there’s a big difference between an agent being able to make a change and being confident about the consequences of that change.</p><p>Modern coding agents can do a surprisingly good job navigating repositories, tracing dependencies, and modifying functionality across multiple files. But understanding what a piece of code does isn’t the same as understanding everything that might happen when you change it.</p><p>Imagine you’ve got a large application that’s been around for ten years. It’s been through multiple engineering teams, a few architectural shifts, migrations that never quite finished, and plenty of decisions that made sense at the time but were never documented.</p><p>There are implicit contracts everywhere. A database field that looks optional but isn’t. A background job that depends on a particular sequence of state changes. An API endpoint with behavior some customer integrated against five years ago.</p><p>An experienced engineer might know some of these things because they’ve been around long enough to see what breaks. An AI agent doesn’t have that history unless it’s been captured somewhere it can access.</p><p>It can inspect the code, read the tests, trace dependencies, and make a reasonable assessment. But there’s a difference between understanding the implementation and establishing confidence that a change won’t have unintended consequences.</p><p>This isn’t exclusively an AI problem. We’ve been dealing with change-impact analysis and undocumented dependencies for decades. But AI makes the problem more interesting because agents can generate significant amounts of code without requiring a comparable amount of human implementation time.</p><p>In the workflows where I’ve found agents most effective, the code itself is often relatively cheap to produce. The harder part is understanding, reviewing, testing, and safely deploying it.</p><p>And the larger and more complicated the system, the harder that becomes.</p><p>The problem isn’t that an agent can’t work on a large codebase. It’s that the usable context required to confidently verify a change can grow well beyond the code that was actually modified.</p><p>That makes architectural boundaries valuable in a different way. They don’t just help organize code. They help constrain how much of the system an agent needs to reason about.</p><h3>You can’t reproduce production on your laptop</h3><p>Let’s say your agent makes a significant change to how data gets ingested and processed. You run the tests. Everything passes. You deploy to staging. Looks good.</p><p>Does that mean it’s safe?</p><p>Not necessarily.</p><p>For a lot of systems, the complexity isn’t just the business logic. It’s how that logic behaves when exposed to production data and production traffic. Reproducing those conditions locally, or even in a staging environment, is incredibly difficult.</p><p>Data fidelity isn’t just having a reasonably accurate copy of production data. It’s also the distribution of that data, the volume, how quickly it arrives, how many operations are happening concurrently, and how the system behaves under sustained load.</p><p>A pipeline processing a few thousand records locally isn’t necessarily going to behave like one processing millions of events per minute.</p><p>You can test the individual components, but some problems only become visible when those components are operating together under realistic conditions. Google’s Site Reliability Engineering guidance discusses exactly this challenge and why canary deployments and production testing are important parts of building reliable systems.</p><p>For some changes, there’s simply no perfect substitute for observing how the software behaves under real production conditions.</p><p>Of course, that doesn’t mean you should blindly deploy untested code and hope for the best. It means you need a way to expose changes gradually, observe their behavior, and limit the consequences when your assumptions turn out to be wrong.</p><p>And there’s another issue here. Even if the software works exactly as designed, you might discover that you’ve built the wrong thing. Customers use functionality differently than you expected, the data looks different, or the performance requirements turn out to be nothing like your initial estimates.</p><p>Those are things you often can’t fully resolve through upfront design.</p><p>This is where I’ve started looking at microservices differently.</p><h3>Microservices as a way to isolate uncertainty</h3><p>What if we thought about microservices not just as a way to organize engineering teams, but as a way to create smaller, independently verifiable units of functionality?</p><p>Suppose you want to introduce a substantial new capability into an existing application. It’s mostly additive, but it needs to interact with important parts of the system. Maybe it’s a new data processing workflow, an ingestion pipeline, or something with unknown performance characteristics.</p><p>You could ask an agent to implement it directly inside the existing application. And sometimes that’s absolutely the right choice.</p><p>But depending on how deep the change goes, you’re asking the agent to solve two problems at once. It needs to figure out how the new functionality should behave, and it needs to figure out how to integrate that functionality safely into a complicated existing system.</p><p>Those are two different kinds of uncertainty, and both consume the agent’s effective reasoning capacity.</p><p>Instead, you could have the agent build the functionality as a relatively isolated service. Give it a clear API contract, define its dependencies and data ownership, and make sure it can be deployed and observed independently.</p><p>Now the agent has a more bounded problem to work on. Rather than reconstructing the entire legacy application, it can focus on the functionality within the service and the specific contracts connecting it to the rest of the system.</p><p>You can expose the feature to a subset of customers, observe how it behaves with actual production traffic, and gradually expand the rollout as you gain confidence.</p><p>If something goes wrong, you’ve got a clearer place to investigate and potentially a simpler path to rolling back the functionality.</p><p>That doesn’t mean the service is automatically safe. If it’s modifying shared database tables, creating side effects across the application, or participating in complicated distributed transactions, the isolation might be more apparent than real.</p><p>And you don’t necessarily need microservices to do this. A modular monolith, feature flags, and carefully designed boundaries can accomplish many of the same things. A separate service becomes more compelling when you need runtime isolation, independent deployment, or different scaling characteristics.</p><p>But where the boundary is meaningful, the benefit is interesting.</p><p><strong>You’re not just isolating functionality. You’re isolating uncertainty.</strong></p><p>You’re also making the amount of relevant context more manageable for the agent. Instead of needing to understand the entire system, it can work within an explicit boundary and rely on defined contracts for the rest.</p><p>That gives you a way to learn how something behaves without immediately introducing all of that uncertainty into the core application.</p><p>And here’s the part that I think deserves more attention: once you’ve learned what you need to learn, you might not need the microservice anymore.</p><h3>What if the first implementation is disposable?</h3><p>We’ve been talking about temporary software for decades. Anyone who’s been building software long enough knows how that usually works out.</p><p>The temporary workaround becomes a permanent dependency. The prototype becomes the product. The service that was only supposed to exist for six months is still running five years later.</p><p>There’s a pretty straightforward reason for this.</p><p>Historically, writing software required a substantial investment of human engineering time. Once you’d spent several months building something, replacing it meant spending more engineering time and money to get back to functionality you already had.</p><p>Rework was often viewed as a failure. It increased project costs, delayed delivery, and consumed engineering capacity that could have been used to build something new.</p><p>Even when everyone agreed that the implementation wasn’t ideal, the economics usually favored keeping it.</p><p>This is part of why we put so much effort into getting architecture right before implementation. We knew that changing our minds later could be expensive.</p><p>Of course, the idea of deliberately disposable software isn’t new. Martin Fowler described <em>Sacrificial Architecture</em> back in 2014, discussing systems built with the expectation that they would eventually be replaced. The strangler-fig pattern is another example of using intermediate architecture to support gradual system evolution.</p><p>What’s interesting to me is whether AI agents are changing the economics enough to make this approach useful in situations where it previously wouldn’t have been practical.</p><p>If agents can substantially reduce the labor involved in implementing and reimplementing functionality, then throwing away a working implementation becomes a much more reasonable option.</p><p>That doesn’t mean rework is free. Moving production data, preserving compatibility, validating behavior, and migrating customers still carry real costs and risks. These are often the hardest parts of changing a mature system, and AI doesn’t make them disappear.</p><p>But the labor required to write and restructure code may no longer be the primary constraint in some workflows.</p><p>And if that’s true, we should reconsider how much effort we spend trying to avoid reimplementation.</p><p><strong>What if rework isn’t necessarily a failure? What if it’s something we intentionally plan for?</strong></p><p>Instead of trying to make the perfect architectural decision before we’ve had a chance to understand the problem, we could build a temporary implementation, use it to eliminate uncertainty, and then decide what the permanent implementation should look like.</p><p>The first version wouldn’t be a failed attempt at the final architecture. It would have served a different purpose.</p><p>It would have helped us learn.</p><h3>The implementation becomes the specification</h3><p>Imagine you’ve used an AI agent to build a new capability as an isolated service. You’ve deployed it to a subset of customers, watched it operate under real production conditions, and iterated on it based on what you’ve learned.</p><p>Maybe you’ve discovered that the original data model wasn’t right. Maybe customers use the feature differently than expected. Maybe the actual workload has completely different characteristics from what you anticipated.</p><p>After a few rounds of iteration, you’ve got something that works. More importantly, you understand the problem much better than you did when you started.</p><p>At this point, something interesting has happened.</p><p>The implementation isn’t just a working piece of software anymore. It’s become a reference for how the feature should behave, informed by real production experience.</p><p>You have API contracts, tests, observed usage patterns, known edge cases, performance metrics, and actual customer feedback. You’ve worked through many of the ambiguities that existed when you started.</p><p>I think of this as an executable specification, although there’s an important qualification. Source code by itself isn’t a complete specification. It can contain accidental behavior, undocumented assumptions, and bugs that happen to work under the conditions you’ve observed.</p><p>You still need to deliberately capture what you’ve learned. That means preserving the behavioral contracts, tests, data semantics, failure modes, operational characteristics, and requirements that matter.</p><p>But once you’ve done that, you have something much more useful than the original design document.</p><p>You have a specification informed by a working implementation that real users have exercised under production conditions.</p><p>And now you can make a much better architectural decision.</p><p>Maybe you discover that this functionality really belongs in the existing monolith. Now you can give an agent the validated behavior, contracts, and tests, along with the reference implementation, and ask it to integrate the functionality into the larger application.</p><p>You’re no longer asking the agent to discover what the feature should do while simultaneously navigating the complexity of the legacy system. The requirements are better understood, and many of the uncertainties have already been resolved.</p><p>This also changes the context problem. Instead of asking the agent to reason about the entire legacy system and a loosely defined new feature at the same time, you’re giving it a well-understood behavioral contract and asking it to solve the narrower problem of integrating that behavior.</p><p>You still have integration risk, but you’ve separated the problem of understanding the feature from the problem of integrating it.</p><p>You can even run the two implementations side by side, compare their behavior under real workloads, and gradually cut traffic over as you establish confidence.</p><p>Or maybe the production evidence tells you that keeping the service independent is the right architecture. Its scaling requirements are different. Its data ownership is clear. Its operational isolation provides real value.</p><p>Great. Keep it.</p><p>The important thing is that you’re now making that decision based on actual evidence rather than trying to anticipate every requirement and trade-off before building anything.</p><p>This creates a useful distinction between <strong>architecture for learning and architecture for long-term operation</strong>.</p><p>The architecture that makes it easiest for an agent to understand, build, and validate functionality isn’t necessarily the architecture that makes the most sense for maintaining that functionality over the next five years.</p><p>And there’s no reason those have to be the same implementation.</p><h3>AI changes the economics, but not the complexity</h3><p>This is where I think things get interesting. Historically, one of the strongest arguments against microservices for smaller teams was the operational overhead. If you’ve got four engineers, you probably don’t want them managing fifteen services. That’s fifteen things to build, deploy, instrument, monitor, and maintain.</p><p>But what happens when much of that implementation work can be handled by AI agents?</p><p>In my own work, I’ve found that agents can take on a surprising amount of the mechanical effort involved in building software. Creating a service, setting up deployment configurations, wiring up instrumentation, writing tests, and restructuring an implementation can take considerably less human effort than it used to.</p><p>I’m not suggesting that all of this is suddenly free. Distributed systems are still distributed systems. Network calls fail, data consistency is complicated, and operational problems don’t disappear just because an agent wrote the code. Someone still needs to understand the architecture and be accountable for what happens in production.</p><p>But the cost of writing the software and the cost of operating the software are two different things. And I think we’re starting to see a meaningful change in the first one.</p><p>There’s another cost that’s changing, too: the cost of throwing an implementation away.</p><p>Historically, replacing working software was a difficult decision because it meant committing substantial engineering time to rebuilding something that already existed. Even if everyone agreed that a different architecture would be better, it was hard to justify the investment.</p><p>When AI agents can take a validated specification and produce a new implementation with substantially less human effort, that calculation starts to look different.</p><p>It doesn’t mean every feature should be built twice. And it doesn’t mean the second implementation will automatically be better. But it gives us an option that was often too expensive to seriously consider before.</p><p>We can build something to learn, validate it in production, and then make an informed decision about whether to keep it or replace it.</p><p>I don’t know exactly where the economic break-even point is yet, and I suspect it varies considerably by application. But based on what I’m seeing working with agents, I think that point is moving.</p><p><strong>The interesting question isn’t how much faster AI can write code. It’s what we can afford to do differently when writing code is no longer the dominant cost.</strong></p><p>And one of those things might be designing temporary architectural boundaries around what an agent can effectively understand, without feeling obligated to preserve those boundaries forever.</p><h3>Not every change deserves a microservice</h3><p>It’s worth emphasizing that I’m not advocating for turning every new feature into a microservice.</p><p>In fact, I think that would be a pretty good way to recreate all the problems we spent the last decade learning to avoid, only faster.</p><p>If an agent is working on a UI feature with an existing, stable API contract, there’s probably no reason to introduce a new service. That’s already one of the situations where agents can be very effective.</p><p>The scope is clear, the interfaces are established, and the behavior can be tested without having to understand the entire backend.</p><p>Similarly, if you’re adding functionality that’s tightly coupled to existing business logic and database transactions, splitting it into a service may introduce more complexity than it removes.</p><p>A modular monolith can provide many of the same benefits of bounded context without the complexity of distributed state and network communication.</p><p>And there’s another important point about the context window argument. I don’t think we should interpret it as a reason to make every service as small as possible.</p><p>The goal isn’t to fit an entire service into an agent’s context window. The goal is to make the relevant context for a change understandable and verifiable.</p><p>A service with a thousand lines of code and a dozen undocumented dependencies could be much harder for an agent to reason about than a module with ten thousand lines of code and clean interfaces.</p><p>What matters is the effective reasoning scope, not the raw size of the implementation.</p><p>The distinction I keep coming back to is whether I need code isolation or runtime isolation. If I just need to make it easier for an agent to understand and modify a particular part of the application, a well-structured module might be enough.</p><p>But if I need to deploy functionality independently, evaluate it under production load, expose it to a subset of customers, or limit its operational blast radius, a separate service becomes more compelling.</p><p>Especially when the functionality is additive, the requirements are uncertain, and the implementation is likely to evolve significantly once customers start using it.</p><p>There’s another discipline I think matters here. If you’re going to build a service primarily to learn something, design it so that it can actually be removed.</p><p>Keep its contracts explicit. Be careful about shared state. Understand what data it owns and what dependencies you’re introducing. Make sure the behavior you learn about can be captured outside the implementation itself.</p><p>Otherwise, you haven’t really created a temporary service. You’ve created another permanent dependency and called it temporary.</p><p>And that’s how we end up right back where we started.</p><h3>The real shift might be how we think about rework</h3><p>There’s a broader idea here that goes beyond microservices.</p><p>For most of my career, we’ve treated rework as something to minimize. That makes sense when engineering labor is expensive and implementation consumes a substantial portion of the project budget.</p><p>We spend time planning, designing, and reviewing architecture partly because we want to avoid discovering halfway through the project that we need to build everything again.</p><p>But if AI agents substantially reduce the labor involved in implementation, then some of those assumptions deserve another look.</p><p>Maybe the optimal approach to building certain kinds of software involves more deliberate rework, not less.</p><p>Instead of spending months trying to anticipate every requirement and architectural consequence, we might build a relatively isolated implementation, validate the behavior, capture what we’ve learned, and then implement it in the architecture that makes the most sense.</p><p>That doesn’t mean abandoning thoughtful design. Quite the opposite. The engineering judgment shifts toward identifying useful boundaries, understanding which risks can be isolated, deciding what evidence is sufficient, and determining when an implementation should be retained or replaced.</p><p>The important thing is that we’re no longer necessarily treating the first working implementation as an investment we need to protect.</p><p><strong>The value of the first implementation might be the information it produces, not the code we keep.</strong></p><p>That’s an idea we’ve had in software engineering for a long time. What’s changing is that AI may make it economical to apply that idea much more broadly.</p><p>And if that happens, it could change how we think about the entire development lifecycle.</p><p>Production becomes more than the destination for completed software. It becomes a place where we gather evidence, validate assumptions, and refine specifications before deciding what the durable implementation should look like.</p><p>The first version helps us understand the problem. The next version, if we need one, can be built around what we’ve learned.</p><h3>The architecture for building software may not be the architecture for running it</h3><p>This is where the context window argument, temporary implementations, and microservices all come together.</p><p>Under traditional Conway’s Law, software architecture tended to reflect relatively persistent organizational structures. Teams had long-term ownership boundaries, and the services they built often reflected those boundaries.</p><p>But AI agents introduce a different kind of constraint. Their effective reasoning context is limited, and the scope of the work they can confidently perform depends heavily on how clearly the problem is bounded.</p><p>That means we might intentionally create architectural boundaries because they make a particular piece of work easier for an agent to understand, implement, and verify.</p><p>Those boundaries can serve a real purpose during development without necessarily being the right boundaries for the final system.</p><p>A microservice might be the best way to give an agent a manageable implementation problem and a safe production rollout path. Once the feature has been validated, the same behavior might be better implemented as part of a larger application.</p><p>And because the agents doing the work aren’t persistent engineering teams with long-term ownership structures, changing those boundaries doesn’t necessarily require reorganizing the humans responsible for the system.</p><p>The important distinction is that the service boundary is serving the development process rather than necessarily defining the permanent system architecture.</p><p>I’m not suggesting we reorganize software every time an agent runs out of context. That would be absurd. But I do think the usable reasoning context of our agents should become one of the inputs into architectural decisions, alongside team ownership, scalability, reliability, and operational complexity.</p><p>The question becomes: what boundaries make it possible to understand and safely change this system?</p><p>Sometimes those boundaries will be permanent. Sometimes they’ll exist only long enough to reduce uncertainty.</p><p>And increasingly, we may have the economic flexibility to choose between those options.</p><h3>Where I think this is going</h3><p>I still think most small teams should start with a monolith. I still think premature microservices are a bad idea. And I definitely don’t think AI agents have somehow eliminated the complexity of distributed systems.</p><p>But I’m starting to believe that the economics behind some of those architectural decisions are changing, and that the usable context window of AI agents is becoming another constraint worth designing around.</p><p>Not because AI has made distributed systems simple, but because <strong>the cost of implementing software may be falling faster than the cost of understanding the risks of changing a complex system.</strong></p><p>That creates an interesting opportunity.</p><p>We can use isolated implementations to explore substantial new functionality, give agents a bounded context in which to work, expose those implementations to real production conditions, and use the resulting evidence to make better decisions about what should become permanent.</p><p>Sometimes the right answer will be to keep the service. Sometimes it will be to throw away the implementation and move the functionality into the monolith.</p><p>Either way, the first implementation wasn’t wasted. It helped us discover what we actually needed to build.</p><p>And that’s what I think is most interesting about working with AI coding agents. They don’t just give us the potential to build software faster. They may change which approaches to building software are economically reasonable in the first place.</p><p>For decades, we’ve made architectural decisions under the assumption that software implementation is expensive, rework should be minimized, and system boundaries should often reflect the structure of the organizations maintaining them.</p><p>Those assumptions aren’t necessarily wrong. But AI introduces a new variable.</p><p><strong>What if the usable reasoning context of an AI agent becomes as important an architectural constraint as the communication structure of the engineering organization?</strong></p><p>And what if the boundaries we create to accommodate that constraint don’t have to be permanent?</p><p>I think those are questions worth exploring.</p><p>Because if implementation becomes cheap enough, the most valuable thing to optimize for may no longer be how quickly we can produce code or how much of that code we preserve.</p><p>It may be how quickly we can eliminate uncertainty, establish confidence, and turn what we’ve learned into software we’re comfortable running for the long term.</p><p>That’s what has me reconsidering microservices.</p><h3>References and further reading</h3><ul><li><a href="https://martinfowler.com/articles/microservices.html">Martin Fowler and James Lewis — Microservices</a></li><li><a href="https://martinfowler.com/bliki/MicroservicePrerequisites.html">Martin Fowler — Microservice Prerequisites</a></li><li><a href="https://martinfowler.com/bliki/SacrificialArchitecture.html">Martin Fowler — Sacrificial Architecture</a></li><li><a href="https://martinfowler.com/bliki/StranglerFigApplication.html">Martin Fowler — Strangler Fig Application</a></li><li><a href="https://sre.google/workbook/canarying-releases/">Google SRE — Canarying Releases</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6025497cb0c4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/conways-law-for-ai-agents-rethinking-microservices-in-the-age-of-ai-6025497cb0c4">Conway’s Law for AI Agents: Rethinking Microservices in the Age of AI</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What I Found When I Turned Four Years of ChatGPT Into a Knowledge Base]]></title>
            <link>https://medium.com/devops-ai/what-i-found-when-i-turned-four-years-of-chatgpt-into-a-knowledge-base-62bfbd767841?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/62bfbd767841</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[data-sovereignty]]></category>
            <category><![CDATA[chatgpt]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Wed, 14 Oct 2026 12:32:54 GMT</pubDate>
            <atom:updated>2026-10-14T12:32:54.389Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YCOKbWW3Cq53kzIdIfXYBQ.png" /></figure><p>The export came in at 1,791 conversations. That number stopped me for a second, because it didn’t feel like 1,791 of anything. It felt like one long conversation I’d been having since December 2022, about whatever I was working on or worried about that week. Blog drafts, spec designs, arguments about agent architecture, meetup planning, homework help, a cod recipe. If you’d asked me what I’d been thinking about for four years, the honest answer was somewhere in that file.</p><p>The problem was that I couldn’t ask it anything. ChatGPT’s sidebar is fine when you remember a word from a title. It’s useless when the question is “where did this idea start?” or “which of these twenty chats were actually the same thought?” I had four years of thinking and no way to read it back.</p><p>So I decided to turn it into a knowledge base. I already keep my research in the <a href="https://github.com/GoogleCloudPlatform/open-knowledge-format">Open Knowledge Format</a>, which is a pleasantly boring idea: a directory of Markdown files, one per concept, each with a bit of YAML at the top saying what it is, who wrote it, and where it came from. No server, no SDK. cat works, git works, and any agent that can read Markdown can read the whole thing. I assumed the hard part would be the summaries, and that the export itself would be the easy bit.</p><h3>The conversation I remembered wasn’t the one in the file</h3><p>The first surprise was the shape of the data. I’d assumed a conversation was a list of messages. It isn’t. It’s a tree. Every time I edited a prompt or hit regenerate, ChatGPT kept the old branch and started a new one, and the export saves all of them. The conversation I actually remember is just one path through that tree, from a field called current_node back to the root.</p><p>That sounds like a parsing detail, but it changes what a summary means. If you read the whole tree, you end up summarizing answers I looked at and threw away, and prompts I rewrote because they were wrong. Of the roughly 23,500 messages in the export, about 19,100 survive once you keep only the branch I chose and drop the hidden messages and reasoning traces. The rest is real data about what ChatGPT said. It just isn’t what I decided.</p><p>The export records what happened. What I wanted was what I kept.</p><p>The second surprise was smaller but told me something similar. I had 48 ChatGPT Projects and custom GPTs, and the export names none of them. They’re IDs like g-p-6884c76e…. The names only ever lived in the app. So I had an agent read each project&#39;s conversation titles and propose a name, and I went through the list and approved or fixed them. A lot of the structure I thought was in my data was actually in the interface around it.</p><h3>Splitting the work between code and an agent</h3><p>Once I understood the data, the design mostly fell out of one question: which parts of this can be wrong in an interesting way?</p><p>Parsing the tree, building file names, linking conversations to projects, writing indexes: none of that should ever be a judgment call. So that’s plain Python, about 900 lines, with no network and no model calls. I run it with make import-chatgpt. It writes a clean, readable transcript for every conversation into a git-ignored cache, along with a queue of conversations that don&#39;t have a summary yet. Each transcript ends with a pre-filled template for the summary, so whoever writes it doesn&#39;t have to remember the format.</p><p>Deciding what a conversation was <em>about</em> is the part that can’t be deterministic. That’s where Claude Code comes in. It reads a transcript and writes one concept file: a title, a one-sentence description, tags, and a summary where every sentence is footnoted back to the original chat. Then I run the importer again, and it fills in the IDs and dates, links the new concepts into their project pages and indexes, adds a line to the log, and checks the whole bundle against OKF’s rules.</p><p>The rule that made this work is that neither side is allowed to touch the other’s fields. The importer owns the type, the link back to ChatGPT, the source entry, and a block of metadata like the model, message count and attachments. Everything else belongs to whoever wrote the summary, and the importer will never overwrite it or delete a file. That’s what lets me drop in a newer export next month without losing a single summary.</p><p>Here’s what one of those concepts looks like, trimmed a little:</p><pre>---<br>type: Conversation<br>title: &#39;Code is clay, specs are the mold: stackable specs and LLM simulation&#39;<br>description: The owner explained stackable specs (ADRs, BLTs and BDRs) as a mold<br>  for AI-generated code, and ChatGPT produced a blog outline, a sample BDR and BLT<br>  for password reset, a simulation and a verification.<br>tags: [spec-driven-development, writing-and-communication]<br>status: draft<br>generated:<br>  by: claude-code/claude-opus-5-5<br>  at: &#39;2026-09-27T15:12:46Z&#39;<br>chatgpt:<br>  model: gpt-5-5-thinking<br>  messages: 20<br>  content_sha256: 5a0e2c4a…<br>  summarized_sha256: 5a0e2c4a…<br>---</pre><p>Those two hashes at the bottom are my favorite part. I go back to old chats all the time, and that has a consequence. The importer hashes the messages I kept. The summary records the hash it was written from. If I reopen a conversation from last spring and add three more messages, the next export produces a different hash, and the importer tells me that summary is stale.</p><p>A summary isn’t a description of a conversation. It’s a description of a specific version of one. I hadn’t thought of it that way until the data forced me to.</p><h3>Deciding the words before the summaries</h3><p>Before any summaries got written, I did something that felt like a detour: a tag vocabulary. Forty-eight tags in eight groups, each with a definition, what it includes and what it doesn’t. context-engineering, for example, covers what goes into a model&#39;s context, but explicitly not the wording of individual prompts, which is prompt-engineering.</p><p>It turned out to matter more than anything else I did. Seventeen hundred summaries written by agents, with free-form tags, would have produced something like 400 tags that mostly meant “AI.” With a fixed list, the OKF check simply fails if a tag isn’t in the vocabulary. No agent gets to invent a new category at conversation 900 because it’s been a long day.</p><p>The same instinct showed up in what I left out. Some conversations aren’t worth a page: a greeting, a bare link with no question, an image request that never got an answer. Those go in a skip list, and the importer refuses to run if a skip doesn’t have a reason written next to it. Forty-three conversations are skipped, and I can read why for every one. An exclusion with a reason is a decision. An exclusion without one is just a place where things quietly disappear.</p><h3>The two days of summaries</h3><p>The code was the small part. The summaries were the big part. I split the queue into batches and ran Claude Code subagents in parallel, each working through its own slice, while one lead session was the only thing allowed to run the importer. Two importers running at once would have fought over the indexes and the log.</p><p>The log tells the story better than I can. On the first day it went from 0 concepts to 20, which was a trial batch I read before trusting the process, then to 624. The next morning it was 1,149, then 1,715, then 1,744 with nothing left in the queue. The agents ran into my plan’s usage limit roughly every 550 conversations. Because the queue was just a file and the importer is safe to re-run, a limit reset was mostly an inconvenience: re-run, re-slice, keep going.</p><h3>What was actually in there</h3><p>Once it was done, the first thing I did was look at which tags came up most, expecting agent architecture or specs. It was writing-and-communication, with 414 conversations. The next closest, agent architecture, had 154.</p><p>I’d have told you I use ChatGPT to think about software. The data says I mostly use it to figure out how to <em>say</em> what I think about software. That’s a different job, and honestly a more useful thing to know about myself than any single summary in the bundle.</p><p>The other surprise was the timeline. Of the 1,744 summaries, 1,599 come from 2025 and 2026. The first two years were a trickle. Somewhere in there it stopped being a tool I tried and became where I did the thinking.</p><p>Search became something I actually use. Instead of scrolling, I ask the bundle:</p><pre>make query BUNDLE=chatgpt TAG=spec-driven-development Q=&quot;stackable&quot;</pre><p>That returns 12 conversations spread across more than a year: blog outlines, spec simulations, workflow designs. I’d never have connected them by title. One of them is called “GitHub README access issue.”</p><h3>Looking at the whole thing at once</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tI5YoQMboJnulHnghIGXGw.png" /></figure><p>Search answers questions you already have. I wanted to see the thing I didn’t know to ask about, so I rendered the bundle as a graph:</p><pre>make graph BUNDLE=chatgpt OUT=out/chatgpt-graph.html</pre><p>It’s a single HTML file with the data baked in. No server, nothing loaded from the network, so it opens straight from disk. Each of the 48 tags is a large dot, colored by its group in the taxonomy. Each conversation is a small dot, pulled toward the tags it carries and colored by the group most of its tags belong to. Click any dot and a side panel shows its summary, its tags, who wrote it, and a small badge that says <em>draft</em> and <em>unverified</em>, because nobody has checked it yet.</p><p>The first thing you see is the shape. The middle is a dense blue and orange knot of AI-assisted development and building with AI: coding agents, specs, agent architecture, all tangled together because those conversations tend to carry two or three of those tags at once. Around the right edge, family, food, health, home and money sit apart from it, each surrounded by a tight little fan of dots. Those fans are conversations with exactly one tag. There are 516 of them, and most of them are the parts of my life that don’t overlap with anything else.</p><p>writing-and-communication is the biggest dot on the screen, and it doesn&#39;t sit in either half. It hangs right between the professional knot and the personal edge, which is the tag count from earlier made visible. Its most common partner is the AI software lifecycle, with 61 conversations carrying both. That&#39;s the whole pattern in one pair of tags: building something, then working out how to say it.</p><p>Down at the bottom, all alone, floats agentic-commerce. It came along when I copied the professional tags from my research bundle, where I&#39;ve saved several sources on it. In four years of conversations I never talked about it once. My research bundle knows what I read. This one knows what I actually thought about, and they&#39;re not the same list.</p><p>Then I looked at the numbers in the header: 1,796 concepts and 366 links. Every one of those links goes from a project page to a conversation. Not one goes from a conversation to another conversation.</p><p>That makes sense once you think about how ChatGPT works. A chat can’t cite another chat. Every conversation starts from nothing, so the summaries, which describe what was in each one, have nothing to link to. Whatever held those conversations together was never in the conversations. It was in my head, and in the tags and projects I added afterwards.</p><p>The graph didn’t show me how my ideas were connected. It showed me that until this week, nothing recorded those connections except me.</p><p>That’s also what made the next step feel necessary rather than tidy.</p><p>The moment it really clicked was later, in a different project. I was writing up a set of positions on software factories, my own ideas next to talks and articles by other people. For the first time, my ideas had sources too. The page on one idea, which I’d been calling change grammar, cites the two ChatGPT conversations where it took shape, and you can follow the footnote straight to them. As far as I know, it’s the first page anywhere that connects those two conversations to each other.</p><p>Before, my own thinking was the one thing in my research with no provenance. It came from “somewhere in ChatGPT.” Now it has footnotes like everything else.</p><h3>The part that isn’t technical</h3><p>A ChatGPT export is probably the most personal dataset I own. Mine mixes work with health, family and relationships, sometimes in the same week. I had to decide how to treat that before the first summary was written, not after.</p><p>The raw export and the transcripts never go near git. They live in an ignored cache folder, and the account files with my email and phone number are never read. Photos and binary attachments stay out of the bundle entirely; the concepts just list them by name and size. And I made a choice that surprised me a little: I told the agents to summarize personal conversations as specifically as the technical ones, names and actual issues included. A vague summary of something that mattered to me is worse than no summary. That choice also settles something else. This bundle is for me and my agents, and it doesn’t get published.</p><p>Every summary also says plainly who wrote it. Each one carries generated.by: claude-code/… and starts life as a draft, and the agents aren&#39;t allowed to mark anything as verified by a human. Only I can do that. Seventeen hundred pages were written about my life, and every one of them tells you that I haven&#39;t checked it yet.</p><p>“The AI summarized my chats” is a claim. “This summary was written by this model, from this version of the conversation, and no human has reviewed it” is something you can actually build on.</p><p>There’s another part of this that bothers me. OpenAI lets me export my conversations, but only as the whole history at once. There’s no incremental export, no practical way to keep a local copy in sync without downloading and processing everything again. I built around that limitation, but it still seems like an odd way to give someone access to their own data.</p><p>And the more I looked at that data, the less comfortable I became with what it represents. One conversation about a recipe or a work problem doesn’t say much about me. Seventeen hundred conversations, across four years, say an enormous amount. What I care about, what worries me, how I make decisions, what I’ve changed my mind about. It’s a remarkably detailed picture of a person, sitting with one provider.</p><p>I’ve been uneasy for a while about the combination of model lab, inference provider and user interface in a single company. Add the prospect of public-market pressures, and I’m even less convinced that the incentives of that company will always line up with mine, or with the public’s. That isn’t an accusation about what OpenAI is doing with my data today. It’s a question about who should control something this personal, and what happens when the incentives change.</p><p>As AI companies grow from startups into the next generation of public corporations, I think it’s a good time to separate the value of these tools from the ownership of what we put into them. I still use ChatGPT. I just don’t want ChatGPT to be the only place my thinking exists.</p><h3>Back to the sidebar</h3><p>I started with a feeling that four years of ChatGPT was one long conversation I couldn’t read back. That was only half right. It wasn’t one conversation. It was a record of what got said, including a lot of things I rejected, rewrote or abandoned, with the structure I’d put around it stripped away.</p><p>What I have now is different. It isn’t what ChatGPT said. It’s what those conversations were about, what I kept from them, and who says so. It’s still thousands of drafts, and most of them I’ll never read. But the next time I wonder where an idea came from, I won’t be scrolling a sidebar. I’ll be following a footnote.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=62bfbd767841" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/what-i-found-when-i-turned-four-years-of-chatgpt-into-a-knowledge-base-62bfbd767841">What I Found When I Turned Four Years of ChatGPT Into a Knowledge Base</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What if an AI Agent Could Grow Its Own Tools?]]></title>
            <link>https://medium.com/devops-ai/what-if-an-ai-agent-could-grow-its-own-tools-02c87c3195cb?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/02c87c3195cb</guid>
            <category><![CDATA[lisp]]></category>
            <category><![CDATA[clojure]]></category>
            <category><![CDATA[clojurescript]]></category>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Thu, 08 Oct 2026 16:19:42 GMT</pubDate>
            <atom:updated>2026-10-08T16:19:42.461Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8QDPittYVRpMwI9x5K4H-g.gif" /></figure><p><em>Building bba: a small Clojure agent that grows its own programming environment.</em></p><p>Something interesting happened while I was experimenting with <a href="https://github.com/o3-cloud/bba">bba</a>, a small AI agent I’m building in Clojure.</p><p>I gave the agent a fairly open-ended request: “I need some functions to make this an awesome coding agent.”</p><p>It started building tools for itself. Nothing particularly surprising there. That’s what coding agents do. But as I dug through the history of what it had created, I found something I wasn’t expecting.</p><p>The agent had built its own conversation-compaction logic.</p><p>It created functions for estimating token usage, organizing exchanges, identifying messages, and planning which parts of a conversation should be summarized. These were ordinary Clojure functions living inside the agent’s programming environment.</p><p>The interesting part is that those same functions eventually made their way into bba’s own extension system. One of them, estimate-tokens, was promoted line for line into the compaction extension that now ships with the project.</p><p>The agent had prototyped a capability for its own harness.</p><p>That got me thinking about something I’ve been exploring for a while. What if agents didn’t just produce code as an output? What if the code they created became part of the environment they used to do their work?</p><p>And what if that environment kept growing, even after the conversation that created those capabilities was gone?</p><p>That’s the idea behind bba, which takes inspiration from Geoffrey Huntley’s <a href="https://ghuntley.com/lisp/">Growing Lisp Applications</a> and the minimalist architecture of <a href="https://pi.dev/">pi</a>.</p><p>The premise is pretty simple.</p><p><strong>Instead of giving an agent an ever-growing collection of tools, give it a programming environment where it can build and reuse its own tools.</strong></p><h3>A shell is useful. A REPL is interesting.</h3><p>Most coding agents follow a familiar pattern. Give a language model access to a shell, let it read files, write code, run commands, inspect the results, and repeat until the task is finished.</p><p>Tools like Claude Code and pi have demonstrated just how effective this can be.</p><p>But there’s an interesting limitation to this approach.</p><p>When the agent writes software, that software generally lives somewhere outside the agent. If it creates a script and wants to use it again, it needs to find the script, invoke it, parse the output, and decide what to do next.</p><p>There’s nothing inherently wrong with that. The shell is an incredibly useful interface because it can do almost anything.</p><p>But a shell is fundamentally process-oriented. You launch something, pass it arguments, collect the output, and move on.</p><p>A REPL gives us something different.</p><p>REPL stands for Read-Eval-Print Loop. It’s a live programming environment where you can define functions, evaluate expressions, change existing definitions, and keep working with the same running program.</p><p>Lisp languages have a long history of working this way. You don’t necessarily need to stop an application, rebuild it, and start again when you want to change its behavior.</p><p>You can modify the running environment directly.</p><p>Imagine asking an agent to count the lines in a file and make the operation reusable.</p><p>It might start with a shell command:</p><pre>(sys/sh &quot;wc -l notes.txt&quot;)</pre><p>Then it could create a function:</p><pre>(defn line-count [path]<br>  (count (str/split-lines (slurp path))))</pre><p>And call it:</p><pre>(line-count &quot;notes.txt&quot;)</pre><p>Nothing especially exotic so far.</p><p>Except that line-count now exists in the agent&#39;s live Clojure environment. It can call it again, combine it with other functions, or redefine it when requirements change.</p><p>It doesn’t have to regenerate the implementation every time.</p><p>And when bba exits, it saves the accepted definitions so they’re available the next time the agent starts.</p><p>The application grows as the agent works.</p><h3>Why Lisp makes this easier</h3><p>There’s a reason I wanted to explore this with Lisp rather than simply building another Python or JavaScript agent.</p><p>Most languages allow you to generate and execute code, but there’s often some additional machinery involved in turning that code into a capability the running application can reuse.</p><p>You might need to create a module, manage imports, dynamically load the code, expose an interface, or register something with the agent’s tool system.</p><p>Individually, none of those steps is particularly difficult. But when an agent is continually creating, modifying, and composing capabilities, the extra friction starts to matter.</p><p>Lisp has an interesting advantage here because of how it represents programs.</p><p><strong>In Lisp, code is data.</strong></p><p>Lisp expressions are represented using the same basic data structures that Lisp programs manipulate. That means programs can construct expressions, inspect their structure, transform them, and evaluate them.</p><p>Combine that with an interactive programming environment, and the distance between writing a program and using it becomes pretty small.</p><p>For an AI agent, that’s an interesting property.</p><p>The model is already generating code. Why make it continually translate that code into external scripts, command-line interfaces, and tool schemas when it could extend the environment it’s already operating in?</p><p>Imagine the agent discovers it needs a reusable operation. Instead of writing a standalone script and registering another tool, it defines a function.</p><p>That function becomes available to the running environment. Other functions can call it. The agent can inspect it, modify it, and build more sophisticated capabilities around it.</p><p>Now, Lisp isn’t the only language that can do this. Python and JavaScript support dynamic evaluation and interactive programming. Smalltalk has an especially rich history of live development.</p><p>You can build similar systems with those languages.</p><p>The distinction is how naturally the pieces fit together.</p><p>Lisp’s code-as-data model, interactive development style, and ability to redefine a running program make this approach fairly direct. And while code-as-data is useful, especially for manipulating programs, the immediate benefits in bba come from the live REPL and its persistent definitions.</p><p>What interests me is how much infrastructure we can avoid.</p><p>Every interface we introduce between the model and the environment is another thing we have to describe, translate, maintain, and potentially debug.</p><p>If we can express more of the system as ordinary executable code, the agent harness itself can stay remarkably small.</p><p>Which brings me to another interesting part of bba.</p><h3>What if the agent only needed two tools?</h3><p>One of the ideas I liked from Jiti was separating the development of a program from its execution.</p><p>bba follows a similar approach.</p><p>Instead of exposing a long collection of tools to the model, it provides two primary operations.</p><p><strong>develop_form</strong> allows the agent to add, change, or remove definitions in its live Clojure environment.</p><p><strong>execute_form</strong> evaluates code against that environment.</p><p>That’s the core interface.</p><p>The agent doesn’t need a separate model-facing tool for every filesystem operation or shell command because Clojure already provides a programmable interface to those capabilities.</p><p>Reading a file is just:</p><pre>(slurp &quot;README.md&quot;)</pre><p>Writing one:</p><pre>(spit &quot;hello.txt&quot; &quot;Hello, world!&quot;)</pre><p>Running a command:</p><pre>(sys/sh &quot;git status --short&quot;)</pre><p>Or finding files:</p><pre>(fs/glob &quot;.&quot; &quot;**/*.clj&quot;)</pre><p>When the agent discovers an operation it’ll probably need again, it can define a function and make that behavior reusable.</p><p>This changes how I think about the role of inference.</p><p>The language model is useful when we need to figure out how to do something new. It can reason through a problem, explore possible implementations, test an approach, and capture a working solution.</p><p>But once we’ve captured that behavior as code, why keep asking the model to work through the same implementation?</p><p>Just call the function.</p><p><strong>Use inference to create capabilities. Use code to reuse them.</strong></p><p>There will still be inference involved in deciding when and how to invoke those capabilities. But we don’t have to spend it reconstructing behavior we’ve already implemented.</p><p>And what’s interesting is that the early usage data from bba shows this pattern emerging.</p><p>I looked across four different worlds created during experimentation. Together, they recorded 2,314 tool operations: 194 calls to develop_form and 2,120 calls to execute_form.</p><p>That’s roughly eleven executions for every development operation.</p><p>The coding agent’s world was even more interesting. It recorded 623 executions against just 19 development operations, about 33 to 1.</p><p>The job-search world showed almost the same pattern, with 254 executions and eight development operations.</p><p>Those numbers don’t prove that the system saves a particular number of tokens. The model still has to decide which functions to call and interpret their results.</p><p>But they do show something useful about the execution model.</p><p>The agent isn’t continually modifying its environment. It spends much more of its time using the capabilities that are already there.</p><p>And as those capabilities accumulate, the environment becomes increasingly useful.</p><h3>Inference is a resource, too</h3><p>There’s another reason I’ve become interested in separating the creation of capabilities from their execution.</p><p>Model inference isn’t free, even when you’re using a subscription that includes a generous amount of usage.</p><p>I’ve been experimenting with Ollama Cloud alongside my other AI development tools, and looking at the usage dashboard made that pretty tangible.</p><p>In one week, the dashboard showed 1,993 cloud requests, representing 10.3% of my weekly allowance. There was also a separate rolling four-hour usage bucket, which was sitting at 8.6%.</p><p>So there are two different constraints at work. There’s the longer-term allowance, and there’s the shorter-term limit on how quickly that allowance can be consumed.</p><p>The interesting part is where those requests were coming from.</p><p>A lot of the activity was concentrated around two fast models, deepseek-v4.1-flash and glm-5.3-flash. My local proxy logs showed requests arriving through two different API interfaces.</p><p>One used the Anthropic-compatible /v1/messages endpoint, associated with the Claude integration. Another used the OpenAI-compatible chat-completions interface.</p><p>Underneath those interfaces, both were reaching Ollama Cloud.</p><p>That distinction is useful because the interface an agent uses to communicate with a model doesn’t necessarily tell you where the inference is actually happening.</p><p>It also reinforces something I want to preserve in bba: the model provider should be interchangeable without forcing us to redesign the agent’s programming environment.</p><p>bba already supports multiple providers, including Ollama. That gives me the option to experiment with different models while keeping the world, its functions, and its execution contracts intact.</p><p>But the more interesting observation isn’t really about which model I’m using.</p><p>It’s about how many model requests an active AI development workflow can generate.</p><p>Those 1,993 requests came from my broader tooling, so I wouldn’t attribute them all to bba or claim that the REPL architecture reduced that number.</p><p>What they do illustrate is that inference has an operational budget, even when individual requests aren’t separately billed.</p><p>And if we’re building systems that rely on agents doing more and more work, that budget matters.</p><p>Think about the difference between asking a model to figure out how to run a project’s tests every time and asking it to create a reusable function that knows how to run those tests.</p><p>The first time, the model might need to inspect the repository, identify the build system, discover the right command, and determine how to interpret the output.</p><p>That’s useful reasoning.</p><p>Once the agent has a working test! function, though, we can reuse the implementation instead of asking the model to rediscover the process.</p><p>If the agent still needs a model call to decide to invoke test!, we haven&#39;t eliminated inference. But we&#39;ve avoided repeating some of the reasoning that went into creating it.</p><p>And when the function is invoked directly by another program, workflow, or scheduled process, the operation itself doesn’t require a model call at all.</p><p>That’s the opportunity I find interesting.</p><p><strong>Inference should be spent where reasoning is needed. Deterministic work should become executable capability whenever it makes sense.</strong></p><p>I don’t have a controlled benchmark showing how many tokens this saves, and the current operation ratios aren’t a substitute for one.</p><p>But the architecture creates a way to measure that question. We can compare how much inference is needed to accomplish a task before and after the world has accumulated the relevant capabilities.</p><p>Over time, I’d like to see whether a more capable world can accomplish the same work with fewer model calls, smaller prompts, or less repeated exploration.</p><p>The goal isn’t necessarily to eliminate inference. It’s to stop treating inference as the only way an agent can make progress.</p><h3>The world becomes the application</h3><p>In bba, I call the live programming environment the <em>world</em>.</p><p>Each project gets its own world, represented by a Clojure namespace. The agent can define functions, invoke them, inspect what’s already available, and build new capabilities from the existing ones.</p><p>What’s important is that the world exists independently of the conversation.</p><p>Most agents rely heavily on conversation history to maintain context. That works reasonably well for reasoning, but it’s a poor place to store durable capabilities.</p><p>Conversations grow, get summarized, and eventually fall outside the context window. Even when the information remains available, processing it consumes tokens.</p><p>If an agent figured out how to perform an operation yesterday, I don’t necessarily need it to remember the conversation where that happened.</p><p>I need it to know how to perform the operation today.</p><p>Executable code gives us another way to preserve that knowledge.</p><p>And the worlds created during these experiments are starting to look like small software libraries rather than collections of disposable snippets.</p><p>The coder world accumulated 44 live functions. It built helpers for running shell commands, searching files, inspecting Git changes, editing files, and running tests. It even built its own task tracker with functions like todo!, todo-list, and todo-done!.</p><p>The job-search world accumulated 27 functions. Instead of repeatedly reasoning through how to evaluate job opportunities, it started building a domain model.</p><p>Functions like score-job, rank-jobs, qualify-job, and deduplicate-jobs captured behavior that could be reused across different searches.</p><p>For example, it created a function for scoring a job against a candidate’s skills:</p><pre>(defn score-job<br>  &quot;Score a job against a candidate profile by counting matching skills.&quot;<br>  [item profile]<br>  (let [required  (set (:skills item))<br>        available (set (:skills profile))<br>        matches   (count (clojure.set/intersection<br>                          required available))]<br>    (assoc item :score matches)))</pre><p>That function doesn’t do anything especially sophisticated, but that’s part of the point.</p><p>The agent now has a named operation with defined behavior. Another function, rank-jobs, can build on it without recreating the scoring logic.</p><p>The functions also have source code, arguments, and documentation. They’re inspectable pieces of software rather than instructions buried in a transcript.</p><p>bba exposes commands for exploring that environment:</p><pre>/functions<br>/describe line-count<br>/history<br>/status</pre><p>A new conversation can start with the same world available. The agent doesn’t need the original conversation to keep using what it built.</p><p>And that brings me back to the compaction example.</p><p>During the coder session, the agent created a group of functions around revisions 71 through 81:</p><pre>estimate-tokens<br>msg-chars<br>exchanges<br>has-block?<br>user-text?<br>final-answer?<br>compaction-plan<br>summarizer-system</pre><p>These functions handled different pieces of conversation management. Some estimated message sizes, some identified message types, and others helped determine how conversation history could be compacted.</p><p>One of them looked like this:</p><pre>(defn estimate-tokens<br>  &quot;Rough token count for a message list: about 4 characters per token.&quot;<br>  [msgs]<br>  (quot (reduce + (map msg-chars msgs)) 4))</pre><p>It’s a simple heuristic rather than an exact tokenizer, but it provides a reusable estimate.</p><p>And that exact function now appears in extensions/compaction.clj.</p><p>The agent originally developed it as part of its world. Later, the working implementation was promoted into bba’s extension source.</p><p>That promotion is an important distinction. The agent wasn’t silently changing its own harness while it ran. It developed the capability inside the world, where it could be inspected and tested, before the implementation became part of the harness.</p><p>I find that progression particularly interesting.</p><p>A useful behavior starts as an experiment, becomes an accepted world function, gets reused, and can eventually graduate into the application itself.</p><p><strong>The world isn’t just remembering what the agent did. It’s preserving the software that came out of the work.</strong></p><h3>Of course, now the agent can break itself</h3><p>There’s an obvious problem with allowing an agent to modify its own running programming environment.</p><p>What happens when it changes something that already worked?</p><p>Imagine the agent has accumulated several functions that depend on one another. It decides to modify one, and that change accidentally breaks another.</p><p>If we’re going to let an agent continually grow a live application, we need a way to reject changes that violate the contracts we’ve already established.</p><p>That’s why bba has checkpoints, invariants, and revisions around evaluation.</p><p>Before an operation, bba captures a checkpoint of the managed world state. It evaluates the requested code and checks whether the resulting state still satisfies its invariants.</p><p>If the change passes, it becomes a new accepted revision. If it fails, bba reconstructs the previous accepted state and reports the problem to the agent.</p><p>The agent can then decide how to recover.</p><p>I originally thought of this mostly as a safety mechanism, but looking through the actual execution history made me appreciate how important it is to the development workflow itself.</p><p>Here’s an example from a job-search session.</p><p>The agent tried to create a persistent definition through execute_form:</p><pre>(def jobs (parse-jobs (slurp &quot;/tmp/li-gj.html&quot;)))</pre><p>But execute_form isn&#39;t supposed to introduce new definitions. That&#39;s the responsibility of develop_form.</p><p>Instead of allowing the operation to modify the world, bba rejected it:</p><pre>{:status :rejected<br> :reason :new-var<br> :vars [jobs]<br> :message &quot;execute_form must not define vars; use develop_form&quot;<br> :restored-to 44<br> :op &quot;execute&quot;<br> :ms 17}</pre><p>The interesting part isn’t the error. It’s what happened next.</p><p>The model received the rejection, understood that it had used the wrong operation, and switched to develop_form on the next turn.</p><p>The world stayed intact, and the agent continued working.</p><p>The rejection and restoration were recorded in just 17 milliseconds.</p><p>Across the observed rejection cases, restoration timings ranged from 6 to 147 milliseconds. That’s a useful property because it means the cost of rejecting a bad change can be very small compared with the inference needed to propose and repair it.</p><p>The system also uses clj-kondo to catch certain problems before evaluation. The operation history included 43 lint rejections, including issues such as unresolved symbols and incorrect function arity.</p><p>I like how this changes the interaction.</p><p>The agent doesn’t have to get every operation right on the first attempt. It can propose a change, receive a concrete explanation when the change is rejected, and try something else.</p><p>But the accepted world doesn’t have to absorb every mistake along the way.</p><h3>Invariants and goals are different things</h3><p>One distinction I wanted to preserve in bba is the difference between what must remain true and what we’re trying to accomplish.</p><p>Invariants describe the behavior the system has already committed to preserving.</p><p>Goals describe behavior we’d like to introduce.</p><p>Both can be represented in a simple EDN configuration file:</p><pre>{:ns world<br> :invariants<br> [{:name &quot;reverse works&quot;<br>   :form (= &quot;cba&quot; (reverse-string &quot;abc&quot;))}]</pre><pre> :goals<br> [{:name &quot;has shout&quot;<br>   :form (ifn? shout)}]</pre><pre> :timeout-ms 30000}</pre><p>If an invariant fails, the proposed change doesn’t get accepted.</p><p>If a goal remains unmet, that’s useful information, but it doesn’t necessarily invalidate the current world.</p><p>Those are two different questions.</p><p>Does the system still satisfy its existing contracts?</p><p>And has it accomplished the new thing we’re trying to build?</p><p>I think this matters whenever we talk about letting agents modify software autonomously.</p><p>We want them to have enough freedom to experiment, but we also need a way to express the things we’ve decided must not change.</p><p>The more capable the agent becomes, the more useful those boundaries are.</p><p>There’s a limitation worth mentioning here, though.</p><p>Restoring a world isn’t the same as rolling back everything that happened outside it. If an operation deletes a file, writes to an external service, or sends a message, restoring the Clojure namespace won’t undo those effects.</p><p>The current checkpoints protect managed world state. They aren’t full transactions across every system the agent can touch.</p><p>That’s an important distinction, especially for consequential automation.</p><h3>Making the world survive a restart</h3><p>Once the agent can grow a useful programming environment, we need a way to preserve it.</p><p>One option would be to save the entire runtime image. Lisp systems have explored variations of that idea for a long time.</p><p>But I wanted something simpler for bba.</p><p>Accepted definitions are stored as an ordered source log, and supported mutable state is saved as EDN data.</p><p>When bba starts, it reconstructs the world from those definitions and restores the saved state.</p><p>Each accepted operation produces an immutable revision file. A CURRENT pointer identifies the active revision and is updated after the new revision has been written.</p><p>That gives the world a history.</p><p>We can inspect earlier revisions and roll back to a previous one without deleting everything that came afterward.</p><p>I also like that the durable artifact is ordinary source code and data. There’s no special runtime image format at the center of the architecture.</p><p>Of course, replaying code has its own complications. Definitions can contain side effects, and not every runtime object can be serialized cleanly.</p><p>But for this experiment, it’s a reasonable compromise.</p><p>The important thing is that the useful work survives the process and conversation that produced it.</p><h3>Keeping the agent itself small</h3><p>The other big influence on bba is pi.</p><p>One of the things I appreciate about pi is that it keeps the agent core relatively small and moves extensibility into plugins and extensions.</p><p>That’s an appealing alternative to building an increasingly elaborate agent framework.</p><p>In bba, extensions are Clojure files that can register tools, commands, event hooks, and provider behavior.</p><p>For example, an extension can register a new tool:</p><pre>(ns my.ext<br>  (:require [bba.ext :as ext]))</pre><pre>(ext/register-tool!<br> {:name &quot;reverse&quot;<br>  :description &quot;Reverse a string.&quot;<br>  :input-schema<br>  {:type &quot;object&quot;<br>   :properties {:text {:type &quot;string&quot;}}<br>   :required [&quot;text&quot;]}<br>  :handler<br>  (fn [{:keys [text]} ctx]<br>    (clojure.string/reverse text))})</pre><p>Extensions can also intercept tool calls and enforce policies.</p><pre>(ext/on! :tool-call<br>  (fn [{:keys [name input]} ctx]<br>    (when (some-policy-violation? name input)<br>      {:block true<br>       :reason &quot;Blocked by policy&quot;})))</pre><p>This gives us two different places where the system can grow.</p><p>The world accumulates application capabilities. These are the functions the agent creates to accomplish its work.</p><p>Extensions modify the agent harness itself. They can introduce tools, connect providers, register commands, and change how operations are handled.</p><p>The agent can write extension files, although activating them requires an explicit reload.</p><p>That’s a boundary I want to keep.</p><p>There’s a difference between growing the application and changing the machinery responsible for growing it.</p><p>And this is what makes the compaction example so interesting.</p><p>The agent first developed the compaction functions inside the world. They were ordinary capabilities, just like the file helpers or job-scoring functions.</p><p>Only later did those functions become part of the extension system.</p><p>The world effectively served as a place to develop and prove out functionality before promoting it into the harness.</p><p>That wasn’t an autonomous deployment mechanism. It was a development path.</p><p>But it’s a compelling example of what this architecture makes possible.</p><h3>Why Clojure and Babashka?</h3><p>Common Lisp is an obvious candidate for this kind of experiment.</p><p>It has a long history of interactive development, dynamically redefining programs, and handling failures through its condition and restart system.</p><p>That last capability is particularly interesting because it can allow a running computation to recover from an error without necessarily abandoning everything it was doing.</p><p>But I wanted to see how far I could take the idea with Clojure.</p><p>And <a href="https://github.com/babashka/babashka">Babashka</a> makes that pretty approachable.</p><p>Babashka provides a fast-starting Clojure environment with practical access to the filesystem, processes, HTTP, and the scripting ecosystem.</p><p>It’s a good foundation for a terminal-native agent because I can use Lisp’s programming model without having to build a heavyweight application around it.</p><p>Running bba is as simple as:</p><pre>bba</pre><p>Or I can invoke it with a prompt:</p><pre>bba -p &quot;Inspect this project and create a reusable function that runs its tests&quot;</pre><p>The current experiment also supports multiple model providers, including OpenRouter, Anthropic, OpenAI, and Ollama, although testing coverage varies.</p><p>There are tradeoffs.</p><p>Clojure doesn’t provide Common Lisp’s condition and restart system. For now, bba handles failed world changes by restoring the previous accepted state and letting the model try again.</p><p>That’s a less sophisticated recovery model, but it’s also much simpler to implement.</p><p>And based on the actual sessions, it’s already useful.</p><h3>There are still some hard problems</h3><p>I want to be clear that bba is a spike, not a production-ready agent runtime.</p><p>There are some important things that still need work, and looking at the execution history has made a few of them more concrete.</p><p>The first is sandboxing.</p><p>Right now, code runs with the permissions of the user running bba. A live REPL with filesystem and shell access is powerful precisely because it can do almost anything, but that also means it needs proper isolation before handling untrusted workloads.</p><p>Then there’s the side-effect problem.</p><p>Restoring the namespace doesn’t undo changes to files, remote services, or other external systems. I think a future version needs to distinguish between pure computation, managed state mutation, and external side effects as first-class concepts.</p><p>Execution cancellation is another issue.</p><p>Across the four worlds, 26 operations hit the configured 30-second timeout. The longest recorded operation ran for a little over 30 seconds.</p><p>That matters because a timeout doesn’t necessarily guarantee that a CPU-bound evaluation thread has stopped. Moving world execution behind a process boundary would give the runtime stronger isolation and cancellation behavior.</p><p>Latency also varies depending on what the agent is doing. Median operation times ranged from 38 milliseconds in the main world to 373 milliseconds in the job-search world, while longer-running shell operations could take tens of seconds.</p><p>So the live programming loop can be fast, but that doesn’t mean every operation is cheap or predictable.</p><p>Then there’s state serialization. EDN works well for ordinary data, but arbitrary runtime state is harder to preserve. We need clear rules about what constitutes durable state and what should be reconstructed.</p><p>And eventually there’s the question of scaling the world.</p><p>A single Clojure namespace is beautifully simple, but what happens when the agent accumulates hundreds or thousands of functions?</p><p>We’ll probably need better capability discovery, dependency tracking, independently versioned modules, and ways to understand how changes propagate through the world.</p><p>Those aren’t peripheral implementation details.</p><p>They’re central to turning an interesting self-growing program into a reliable operating model.</p><p>The more freedom we give an agent to change its environment, the more important it becomes to define what it can change, what must remain true, and what happens when something goes wrong.</p><h3>What if agents accumulated capabilities instead of just context?</h3><p>There’s a lot of attention on making AI agents smarter.</p><p>We’re getting better models, larger context windows, more sophisticated orchestration, improved memory, retrieval systems, and increasingly elaborate tool frameworks.</p><p>All of that has value.</p><p>But I keep coming back to a slightly different question.</p><p><strong>What if an agent became less dependent on inference as it accumulated capabilities?</strong></p><p>Not because the model itself was learning new weights, but because the environment it operated in was accumulating executable knowledge.</p><p>Think about what happens when you ask an agent to automate a workflow.</p><p>The first time, it needs to reason through the problem, inspect the environment, experiment with an implementation, and verify that the result works.</p><p>Once it has a working solution, it captures the behavior as a function.</p><p>The next time, it invokes that function.</p><p>Later, it might combine the function with other capabilities it’s accumulated. When requirements change, it can redefine the implementation while checking that the existing contracts still hold.</p><p>The useful work survives the conversation that created it.</p><p>That’s the part of this experiment I find most compelling.</p><p>Across the sessions I examined, the worlds accumulated real libraries of reusable functions. Some handled coding tasks. Others handled job searches. And a handful of functions originally developed inside a world eventually became part of bba itself.</p><p>The conversations provided the reasoning needed to create those capabilities, but the capabilities no longer depend on those conversations being available.</p><p>This isn’t learning in the machine-learning sense. The model isn’t becoming more intelligent.</p><p>It’s closer to incremental software development, where the model acts as an interactive programmer inside an application that continues to evolve.</p><p>And that’s why the Lisp REPL is such an interesting interface.</p><p>It brings writing code, executing code, and modifying the running application into the same environment.</p><p>A shell-enabled agent can already create reusable software, of course. The opportunity is to make that accumulation native, inspectable, testable, and persistent.</p><p>That’s what I’m exploring with bba.</p><p>The compaction story is a good example of why I think there’s something here. The agent needed a capability, developed it inside its world, and that capability eventually became part of the agent harness itself.</p><p>The conversation was useful while the work was happening, but it wasn’t the durable artifact.</p><p>The durable artifact was the code.</p><p>And in this case, some of that code became part of the product.</p><p>Maybe the next interesting step in agent architecture isn’t another orchestration layer.</p><p>Maybe it’s giving the agent a live programming environment, a few contracts it has to respect, and somewhere durable to put the things it builds.</p><p>Then letting the software grow from there.</p><p><strong>Code:</strong> <a href="https://github.com/o3-cloud/bba">bba on GitHub</a></p><p><strong>Influences:</strong></p><ul><li>Geoffrey Huntley — <a href="https://ghuntley.com/lisp/">Growing Lisp Applications</a></li><li>Mario Zechner — <a href="https://pi.dev/">pi</a></li><li>Michiel Borkent — <a href="https://github.com/babashka/babashka">Babashka</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=02c87c3195cb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/what-if-an-ai-agent-could-grow-its-own-tools-02c87c3195cb">What if an AI Agent Could Grow Its Own Tools?</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Turning AI Judgment Into Infrastructure]]></title>
            <link>https://medium.com/devops-ai/turning-ai-judgment-into-infrastructure-65b113e44a93?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/65b113e44a93</guid>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[ai-workflow]]></category>
            <category><![CDATA[jevs]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Thu, 24 Sep 2026 17:27:03 GMT</pubDate>
            <atom:updated>2026-09-24T17:27:03.680Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lFrboPE5_25dQHjTa1dyyg.png" /></figure><p>Sometimes you really do want to give a model a goal, a pile of tools, a large context window, and enough freedom to figure things out. Research is a good example. So is exploring an unfamiliar codebase, debugging something you don’t understand yet, or working through a problem where you genuinely can’t know the steps ahead of time. That’s where agents get interesting because they can explore, adapt, and discover intermediate steps you didn’t think to enumerate.</p><p>The problem starts when we take that same architecture and put it around something we need to behave predictably.</p><p>Over the last couple of years, we’ve spent a lot of effort trying to make agent loops behave more like traditional software. We add larger system prompts, better examples, more detailed tool descriptions, output schemas, reflection steps, retry loops, critics, planners, and validators. We tell the model to be careful, then we tell it to be very careful, and eventually we’re putting <strong>IMPORTANT</strong> in all caps and hoping that does the trick.</p><p>And to be fair, this works surprisingly well.</p><p>It also works surprisingly badly.</p><p>That tension is showing up pretty consistently in the research. Benchmarks like τ-bench have found that even strong tool-using models can struggle with consistency when they have to follow policies across multi-step interactions. Anthropic has written about the same problem from the evaluation side: once an agent is operating across a long trajectory, failures can come from planning, tool selection, interpreting tool results, context management, stopping decisions, or some interaction between all of them.</p><p>The longer the trajectory gets, the harder it becomes to answer a very basic engineering question: what exactly went wrong?</p><p>That’s the part I keep coming back to. Maybe the problem isn’t that agents need better instructions. Maybe we’re giving them ownership of too much.</p><h3>The agent is sitting at the wrong boundary</h3><p>A lot of agentic software ends up with roughly the same shape:</p><p>deterministic software → agent → deterministic software</p><p>The first piece gathers some state, then we hand a big chunk of the workflow to the model. Inside that middle box, the model may have to figure out what the user wants, decide which facts matter, determine which policy applies, decide what should happen next, choose a tool, construct the arguments, evaluate what came back, change its plan, retry if necessary, and eventually decide when it’s done.</p><p>That’s a lot of responsibility hiding behind one interface.</p><p>And most of the control surface is language. Please follow this rule. Don’t use this tool under these conditions. Remember the constraint from earlier. Check your answer before continuing. Return valid JSON.</p><p>Again, none of this is useless. Prompting clearly works. Reflection works in some settings. Structured outputs work. Better tool descriptions work. But the evidence also keeps showing us the limits of relying on model behavior alone as the thing enforcing the system.</p><p>OpenAI’s work on Structured Outputs is a good example. Prompting the model to produce valid JSON got pretty good, but “pretty good” wasn’t the same thing as a guarantee, so they added constrained decoding around the model to make the schema deterministic.</p><p>That pattern is interesting. The model still does the fuzzy part. The software constrains the part that doesn’t need to be fuzzy.</p><p>I think there’s a bigger architectural lesson hiding in there.</p><h3>Jev makes the boundary explicit</h3><p>This is part of why Jev caught my attention.</p><p>Jev is TypeSafe AI’s System One model, and its inference interface looks very different from the normal “give me some text and I’ll give you some text back” interaction we’ve gotten used to with LLMs.</p><p>You give Jev some state and then ask it focused, typed questions. It doesn’t return an essay explaining what it thinks you should do. It returns a bounded judgment that software can consume.</p><p>There are three basic shapes.</p><p>A <strong>Choice</strong> asks the model to select from options you provide. A <strong>Score</strong> asks it to evaluate something against an ordered rubric. A <strong>Noul</strong> asks a yes-or-no question and returns a probability.</p><p>That interface is interesting to me because it changes what inference means inside the application.</p><p>Imagine a support request comes in. Instead of giving a general-purpose model the ticket, the customer history, access to a bunch of tools, and instructions to “handle this,” I can ask Jev a few very specific questions.</p><p>Which queue best matches this request?</p><p>How severe does this problem appear to be?</p><p>Does the message explicitly request a refund?</p><p>How frustrated does the customer appear?</p><p>Jev returns judgments. My code decides what those judgments mean.</p><p>A high severity score might trigger escalation. A particular category might determine which queue receives the ticket. A low-confidence decision might go to a person. Refund eligibility might still be calculated entirely by ordinary policy code.</p><p>That last part is important.</p><p>Jev doesn’t need to own the workflow for its intelligence to be useful.</p><p>Its inference becomes a primitive inside the workflow.</p><p>That’s the architecture I’ve been trying to describe.</p><h3>Give the model a judgment, not the workflow</h3><p>Instead of asking the model to own an entire process, I’m increasingly interested in giving it one bounded judgment at a time.</p><p>Don’t ask, “Read this customer history, figure out what is happening, determine what policy applies, decide what we should do, update the account, and send the response.”</p><p>Ask, “What kind of request is this?” Then return to code. Ask, “How severe does this issue appear to be?” Then return to code again. Maybe the next question is, “Does this message express strong frustration?” or “Which of these five known exception categories best matches what you see?”</p><p>The model handles the ambiguity. The program handles the workflow.</p><p>Jev makes this separation unusually explicit. The possible shape of the answer exists before inference happens. For a Choice, the application supplies the available options. For a Score, the application supplies the rubric. For a Noul, the application supplies the proposition being evaluated. The inference happens inside a boundary the program has already defined.</p><p>That changes the shape of the model call. Here are the facts you’re allowed to consider. Here is the judgment I need. Here is what a valid answer looks like. Here is exactly how much authority that answer has.</p><p>Then deterministic software takes over again.</p><p>This isn’t unique to Jev. You can build the same architectural pattern with general-purpose models, structured outputs, classifiers, or other probabilistic systems. But Jev is interesting because this isn’t a pattern you have to impose on top of a text-generation model. It’s the inference primitive itself.</p><p>Unstructured state in. Typed probabilistic judgment out.</p><p>Then back to code.</p><p>Anthropic describes a related distinction in its guidance around routing, prompt chaining, and workflows. When the task is well defined, predefined workflows give you predictability and consistency. Agents become more useful when the path itself cannot reasonably be known in advance.</p><p>That’s a much more useful distinction than “agents good” or “agents bad.”</p><p>The question is really: where does the uncertainty live?</p><h3>Models should make judgments, not laws of physics</h3><p>There’s a dividing line here that I find useful. If the answer can be known exactly, I probably don’t want a model deciding it.</p><p>I don’t need an LLM to tell me whether an account balance is large enough to cover a transaction. I don’t need one to decide whether a date falls inside a contractual window if that rule can be written down. I don’t need one deciding whether a user is allowed to perform an action when I already have an authorization policy that can answer that question deterministically. And I definitely don’t want the model inventing the set of actions it is allowed to perform.</p><p>Those are software problems.</p><p>Where models become useful is where the system encounters ambiguity. Does this email sound threatening? Do these two documents appear to refer to the same real-world entity? Which category best describes this request? How similar are these two situations? Does this language sound urgent, hesitant, sarcastic, or hostile?</p><p>Those are judgments.</p><p>Jev’s inference model maps pretty cleanly onto this distinction. Choice, Score, and Noul aren’t replacements for arithmetic, authorization, or state machines. They’re ways of asking probabilistic questions inside software.</p><p>And the distinction matters because the testing strategy is completely different.</p><h3>Smaller judgments give you smaller tests</h3><p>Imagine I have a function that looks conceptually like this:</p><p>classify_request_type(message)</p><p>I know what to do with that. I can build a labeled dataset, compare model versions, look at a confusion matrix, inspect calibration if the confidence scores are meaningful, pick a threshold below which I send the case to a human, and test the function by itself.</p><p>That becomes even more interesting when inference gives me the distribution rather than just the winning answer. Jev’s Choice, for example, returns probabilities across the options rather than only handing back a label. Now uncertainty itself can become part of the software.</p><p>Maybe anything above a threshold proceeds automatically. Maybe the middle gets reviewed. Maybe two close options cause the workflow to gather another piece of information before asking again.</p><p>The model expresses uncertainty.</p><p>The application decides what to do about it.</p><p>I can also replace the model later without redesigning the entire application, assuming I’ve kept that interface stable.</p><p>Now compare that with:</p><p>run_support_agent(customer_context)</p><p>How do I evaluate that?</p><p>You can absolutely run scenarios. Agent evals are getting much better. But the test surface is fundamentally larger. Maybe the result was wrong because the model misunderstood the request. Maybe it picked the wrong tool, or the right tool with the wrong arguments. Maybe an intermediate result confused it. Maybe it summarized something badly and carried that mistake forward. Maybe it stopped too early, or kept going when it should have stopped.</p><p>Maybe the seventh step was perfectly reasonable given the mistake it made at step three.</p><p>This is one of the clearest things that came out of Anthropic’s work on agent evaluation: multi-turn systems are harder to evaluate precisely because state changes and mistakes propagate through the trajectory.</p><p>That doesn’t mean we shouldn’t build them. It means trajectory length is part of the risk surface.</p><p>So when I can turn one twenty-step fuzzy process into several smaller fuzzy judgments separated by deterministic code, that starts to look pretty attractive. Not because some paper has proven that this is always the best architecture. It hasn’t. But because it gives me much better things to measure.</p><h3>The security argument may be even stronger</h3><p>There’s another part of this that worries me more than reliability.</p><p>Agent systems often put two things together that I’d rather keep separated: untrusted content and powerful capabilities.</p><p>The model reads an email, document, webpage, or user message containing arbitrary text. Inside the same reasoning process, it may also have tools that can send messages, update records, change permissions, move money, or delete things.</p><p>Then we depend on the model to correctly distinguish between data and instructions.</p><p>That is essentially the prompt-injection problem.</p><p>OWASP’s guidance around agent security keeps coming back to the same basic ideas: minimize agency, restrict permissions, separate untrusted input from privileged capabilities, and enforce important controls outside the model.</p><p>One of the patterns they describe is particularly close to what I’m talking about here. Let one model interact with untrusted content without access to powerful tools. Have it produce a constrained, structured result. Then let another component operate on that result inside a much more privileged environment.</p><p>In other words: don’t let arbitrary text travel all the way to the actuator.</p><p>A Jev-style inference boundary gives you another way to think about that separation. The untrusted content can be evaluated as state, the model can return a bounded judgment, and the privileged code can decide whether that judgment is sufficient to perform an action.</p><p>That doesn’t make the model correct. A typed answer can still be the wrong answer.</p><p>But “the classifier picked the wrong category” is a much more bounded failure than “the email convinced the agent to use a privileged tool in a way we didn’t intend.”</p><p>That’s a security boundary I can reason about.</p><h3>This is not an argument against agents</h3><p>I still want agents.</p><p>In some places, I want them badly.</p><p>Research is probably the clearest example. The entire point is that you don’t know what you’re going to find or what path you’ll take to find it. The same thing happens when you’re exploring an unfamiliar system, debugging something weird, or working on an open-ended coding task where the implementation path changes as you learn more about the codebase.</p><p>In those situations, the ability to improvise is useful.</p><p>Anthropic makes essentially this distinction in its own engineering guidance. Use workflows when the process can be defined ahead of time. Use agents when the model needs to direct its own process and adapt based on what it discovers.</p><p>That framing makes a lot of sense to me.</p><p>Agents optimize for reach. They can navigate underspecified environments, discover intermediate steps, and recover when reality doesn’t match the plan.</p><p>Deterministic systems optimize for control. They can give you explicit state transitions, repeatability, enforceable permissions, and invariants you can reason about.</p><p>And something like Jev sits in an interesting place between the two. It gives ordinary software access to semantic judgment without requiring that semantic judgment to own the process around it.</p><p>Neither approach replaces the other. The mistake is pretending they’re all the same thing.</p><h3>Put inference near uncertainty</h3><p>I used to describe this as:</p><p><strong>Put agents at the edges, not at the core.</strong></p><p>I still like the phrase, but I think I’d refine it now.</p><p>Put inference near uncertainty.</p><p>Let an agent investigate when the path is uncertain. Let a model classify when the category is uncertain. Let something like Jev score, choose, or estimate a probability when the software needs a semantic judgment.</p><p>But once you cross back into something that can be expressed precisely, use software again.</p><p>Validate the input. Apply the policy. Check the permission. Constrain the available actions. Perform the state transition. If you run into another fuzzy decision, make another inference call and then come back.</p><p>The architecture starts looking less like one long excursion into agent land and more like a series of controlled crossings between probabilistic and deterministic computation.</p><p>I’m not claiming research has established that as the one true architecture for reliable AI systems. It hasn’t. But the evidence we do have keeps pointing in that direction.</p><p>Long agent trajectories are harder to evaluate. Model behavior is not a substitute for authorization. Prompt injection gets more dangerous as untrusted input gets closer to powerful tools. Narrow classification-style tasks are much easier to benchmark, calibrate, test, and replace. And predefined workflows remain useful precisely because predictability is sometimes more valuable than autonomy.</p><p>Jev makes that architecture easier to see because the model itself stops looking like a miniature application.</p><p>It looks like an inference primitive.</p><p>Put those things together and the design principle starts to feel less like a preference.</p><h3>The hard part becomes decomposition</h3><p>Of course, this doesn’t make the engineering easy. It just moves the difficulty somewhere more recognizable.</p><p>Instead of spending most of our effort trying to make one autonomous loop behave, we have to identify the actual judgments in the system. Which decisions are genuinely semantic? What information does each one need? Which decisions are independent? How should uncertainty affect what happens next? When should the system escalate to a person? What happens when two judgments disagree?</p><p>Jev actually makes those questions harder to avoid. If I have to choose between Choice, Score, and Noul, I first have to decide what kind of judgment I’m actually asking the model to make. If I’m defining the available options or writing the scoring rubric, I have to think about the boundaries of the decision before inference starts.</p><p>That feels like productive friction.</p><p>And maybe most importantly, we have to ask how we stop a convenient model score from quietly becoming an undocumented business rule. A probability is evidence. A score is evidence. A classification is evidence. What the application is allowed to do with that evidence is a separate decision.</p><p>These are hard questions, but I like them better because they lead to interfaces, tests, datasets, thresholds, policies, logs, and explicit failure modes.</p><p>Things I know how to inspect.</p><p>Things I know how to change without rewriting the personality of an agent and hoping it behaves differently next time.</p><h3>Shrink the probabilistic part</h3><p>The biggest shift for me is that I no longer think the goal is to make an application intelligent. That framing encourages us to spread the model everywhere.</p><p>I think the more interesting question is: where does this application actually encounter uncertainty?</p><p>Put the intelligence there. Keep the rest boring.</p><p>If I can calculate something, I want to calculate it. If I can express something as policy, I want to express it as policy. If I can enforce something as an invariant, I want to enforce it as an invariant.</p><p>And when I reach the part where the system genuinely needs judgment, that’s where inference earns its keep.</p><p>That’s what I find interesting about Jev. It doesn’t ask us to turn the whole application into an agent. It makes probabilistic judgment look like another primitive software can call when it needs one.</p><p>There’s still a time and place for looping and praying.</p><p>Just not around the parts I need to trust.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=65b113e44a93" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/turning-ai-judgment-into-infrastructure-65b113e44a93">Turning AI Judgment Into Infrastructure</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jev Through the Lens of a DevOps Engineer]]></title>
            <link>https://medium.com/devops-ai/jev-through-the-lens-of-a-devops-engineer-c8684f7e90d3?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/c8684f7e90d3</guid>
            <category><![CDATA[ai-engineering]]></category>
            <category><![CDATA[jevs]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[ai-engineer]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Wed, 23 Sep 2026 03:36:22 GMT</pubDate>
            <atom:updated>2026-09-23T03:36:22.393Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EWUFAuFi5V5tie4p1EOgMw.png" /></figure><p>The thing that caught my attention about Jev wasn’t that it’s another AI model. It was that it made me think about a pattern I’ve seen before in infrastructure: sometimes the interesting thing isn’t the larger system. It’s a useful capability hiding inside it that eventually deserves its own interface.</p><p>Caching is a good example.</p><p>For a long time, applications leaned on databases to repeatedly serve the same data. Then systems like Memcached made caching an independently addressable piece of infrastructure. Instead of asking the database for the same thing over and over, applications could put a fast distributed memory layer in front of it and fall back to the database when the value wasn’t there. Memcached was originally built for LiveJournal and became a general-purpose distributed caching system specifically designed to reduce repeated access to slower data sources.</p><p>Once the cache had its own interface, topology, and operational model, it became something the rest of the application could explicitly compose around.</p><p>Kafka gives us a similar pattern from another direction. Its early documentation described Kafka as a distributed, partitioned, replicated commit log. Today that same basic abstraction sits underneath event streaming, stream processing, event-driven architectures, and pipelines moving data between systems.</p><p>The point isn’t that these technologies followed exactly the same historical path. It’s that once a useful capability gets a stable contract, you can start composing around it in ways that weren’t practical when it was buried inside something larger.</p><p>That’s the lens through which I find Jev interesting.</p><h3>We’ve made generation the default interface</h3><p>Most of the AI systems we work with today are built around generation. You send the model some context and instructions, and it generates tokens.</p><p>Even when the thing your application actually wants isn’t really language.</p><p>Maybe I need to know which team should handle a ticket, whether a patch crosses a security boundary, how severe an incident appears to be, or whether a customer is asking for a refund. Those are semantic decisions, but we’ve gotten used to asking a generative model to produce a representation of the answer and then turning that representation back into something software can use.</p><p>For a while, that meant prompts, JSON parsing, validation, and retries. Structured-output APIs have improved this considerably. OpenAI, for example, explicitly introduced Structured Outputs because developers were repeatedly trying to force generated responses into schemas that application code could reliably consume.</p><p>So we’ve already been moving in this direction.</p><blockquote><em>I don’t really want an essay here. I want a value.</em></blockquote><p>Jev takes that idea another step. Its interface starts with state and typed questions. The question can be a <strong>Choice</strong>, a <strong>Score</strong>, or a <strong>Noul</strong> — essentially a bounded classification, an ordered evaluation, or a yes/no judgment. The result comes back as typed data, with probabilities and confidence where appropriate, for application code to consume directly.</p><p>The shape is closer to this:</p><pre>State + typed question<br>        ↓<br>Typed judgment + uncertainty<br>        ↓<br>Application logic</pre><p>That looks like a small difference.</p><p>From an operations perspective, I don’t think it is.</p><h3>Judgment as a service</h3><p>The easiest way for me to think about Jev isn’t as a smaller chatbot. It’s a judgment service.</p><p>The application still owns the workflow. Code still owns permissions, deterministic rules, and side effects. You insert the model where the system encounters something semantic that ordinary logic doesn’t express cleanly.</p><p>Something like:</p><pre>if ticket.status == &quot;closed&quot;:<br>    return</pre><pre>evaluation = jev.evaluate(<br>    state=ticket,<br>    questions={<br>        &quot;department&quot;: choice(...),<br>        &quot;urgent&quot;: noul(...),<br>        &quot;frustration&quot;: score(...),<br>    },<br>)</pre><pre>if evaluation[&quot;department&quot;].confidence &lt; ROUTING_THRESHOLD:<br>    send_to_manual_triage(ticket)<br>else:<br>    route(ticket, evaluation[&quot;department&quot;].choice)</pre><p>What I like about this is the boundary.</p><p>The model can say:</p><blockquote><em>This looks like billing.</em></blockquote><p>That doesn’t mean the model gets to issue a refund.</p><p>It can say:</p><blockquote><em>This command looks destructive.</em></blockquote><p>That doesn’t mean the model gets to decide what permissions the process has.</p><p>Jev’s own documentation is fairly explicit about this distinction: the model supplies the judgment while the surrounding application owns thresholds, retries, authorization, and the final action.</p><p>That division feels familiar if you’ve spent time operating production systems. We generally don’t want one component to observe the world, interpret it, establish policy, authorize itself, and perform arbitrary side effects.</p><p>We separate those responsibilities because separate responsibilities give us separate failure domains.</p><p>So the boundary becomes:</p><pre>Model: What does this information appear to mean?</pre><pre>Code: What are we allowed to do about it?</pre><p>I find that much easier to reason about.</p><h3>The contract may matter more than the model</h3><p>When infrastructure abstractions become useful, the implementation is only part of the story. The contract is what lets the rest of the system build around them.</p><p>Memcached gives you a simple cache contract: keys, values, expiration, and a fast place to look before you fall back to the slower backing store.</p><p>Kafka gives you ordered records, offsets, retention, and replay.</p><p>With learned judgment, the contract Jev is proposing looks something like:</p><pre>State + bounded question → bounded answer + uncertainty</pre><p>That boundary changes a few things.</p><p>First, the answer space is explicit. If there are three departments, the Choice is operating over those three departments. If severity has four defined levels, the Score operates over those levels. If the question is whether a statement is true, Noul returns the probability of yes.</p><p>That doesn’t make the model correct.</p><p>It makes the output space known.</p><p>There’s an important difference.</p><p>Structured-output systems have already shown the value of this distinction. Constrained decoding can guarantee that the <em>shape</em> of an answer follows a schema even though it cannot guarantee that the <em>content</em> is semantically correct.</p><p>That’s a much healthier contract for software. The system doesn’t have to pretend that uncertainty disappeared just because the JSON parsed.</p><h3>Break big decisions into small judgments</h3><p>There’s another consequence of making the questions explicit. You stop asking things like:</p><blockquote><em>What should we do with this customer?</em></blockquote><p>That’s actually several decisions hiding inside one question.</p><p>What is the issue about? Is it urgent? Is the person asking for a refund? Does the message contain sensitive information? Does this need a human?</p><p>Those judgments can be evaluated independently and combined by code.</p><p>Jev’s documentation specifically recommends small, well-scoped questions and supports evaluating several of them in parallel against the same state. This lines up with a broader pattern we’re already seeing in agent design.</p><p>Anthropic distinguishes between workflows, where tools and model calls follow predefined paths, and agents, where the model dynamically controls its own process. For well-defined work, they specifically call out workflows as offering more predictability and consistency.</p><p>That’s basically the design choice here.</p><p>Let the model answer the fuzzy question.</p><p>Let software own the graph.</p><h3>Confidence needs an operating model</h3><p>This is the part that becomes especially important once these judgments control real behavior.</p><p>Suppose the model gives you 0.91.</p><p>Is that enough to automate?</p><p>There’s no meaningful answer to that question without knowing what happens next. If the consequence of being wrong is showing someone the wrong help article, maybe the threshold can be fairly permissive. If the consequence is suspending an account, deleting data, or moving money, that is a completely different risk decision.</p><p>NIST’s AI Risk Management Framework makes essentially this point: risk tolerance is contextual, and the amount of oversight and risk-management effort should change with the impact and likelihood of failure. It also recommends measuring systems under conditions that resemble deployment and monitoring their behavior once they’re in production.</p><p>So I wouldn’t look at Jev’s confidence output and ask:</p><blockquote><em>Is 0.91 high?</em></blockquote><p>I’d ask:</p><blockquote><em>At 0.91, how often is this particular judgment wrong on data that looks like ours, and what happens when it is?</em></blockquote><p>That’s an operational question.</p><p>And you can measure it.</p><p>Collect representative cases. Run the exact model and exact questions you’re planning to deploy. Look at accuracy across different confidence ranges. Look at false positives and false negatives. Look at which segments behave differently. Look at how often humans override the system.</p><p>Then choose an automation threshold based on the risk behind the action.</p><p>Jev’s documentation makes the same distinction: probability and confidence are signals for automation, not guarantees of business correctness, and higher-risk actions should use stricter thresholds or human review.</p><p>The goal isn’t maximum automation.</p><p>It’s automation inside an error budget you understand.</p><h3>Questions start to look like production configuration</h3><p>Once the questions start affecting routing and automation, I don’t really think of them as prompts anymore.</p><p>They’re configuration.</p><p>Imagine changing this:</p><blockquote><em>Does this message request a refund?</em></blockquote><p>to:</p><blockquote><em>Could this customer reasonably expect a refund?</em></blockquote><p>That looks like a copy edit.</p><p>Operationally, it isn’t.</p><p>You just changed the behavior of the system.</p><p>And model behavior really can be sensitive to changes in how a question is expressed. That’s one reason production AI systems increasingly need evaluations around behavioral changes rather than treating prompt text as harmless prose.</p><p>So I’d want the things controlling judgment to look more like this:</p><pre>judgment/<br>├── questions/<br>│   ├── ticket-routing.yaml<br>│   ├── content-risk.yaml<br>│   └── patch-review.yaml<br>├── policies/<br>│   ├── automatic.yaml<br>│   └── high-risk.yaml<br>└── evaluations/<br>    ├── fixtures.jsonl<br>    └── expected-results.jsonl</pre><p>Now the questions can be versioned, reviewed, tested, rolled out gradually, observed, and rolled back.</p><p>The interesting shift is that prompt engineering starts looking less like conversation design and more like configuration management.</p><h3>Replay is where this gets really useful</h3><p>If questions and policies are configuration, the next thing I want is replay.</p><p>Keep a representative set of historical states and run proposed changes against them before those changes affect production.</p><p>Changed the model? Replay.</p><p>Changed the question? Replay.</p><p>Changed the threshold? Replay.</p><p>Changed how three judgments combine into a routing decision? Replay.</p><p>Now you can ask:</p><pre>How many historical decisions changed?</pre><pre>Which ones?</pre><pre>Did false positives increase?</pre><pre>Which customer segments moved?</pre><pre>How much larger will the review queue become?</pre><pre>What happens to the high-risk cases?</pre><p>This isn’t unique to Jev. It’s the same idea behind evaluations more broadly.</p><p>Anthropic describes evals as a way to make behavioral changes visible before they affect users rather than discovering them reactively in production.</p><p>What Jev makes interesting is that the artifact being replayed is small: a set of typed questions, a model version, some thresholds, and some deterministic policy.</p><p>That starts to feel a lot more like a deployment diff than tweaking a giant system prompt and hoping the overall behavior moved in the direction you intended.</p><h3>Batching makes judgment look like a data operation</h3><p>Jev also lets multiple independent questions inspect the same state in parallel.</p><p>I think that’s more important than it first appears.</p><p>Suppose every incoming support request gets evaluated for department, urgency, refund intent, sensitive information, frustration, and whether human review is required. Some of those answers may not matter for a particular ticket.</p><p>That’s okay.</p><p>The application can ignore them.</p><p>The alternative is an agentic loop where the model decides which question it should ask next, evaluates that answer, updates its state, and decides again.</p><p>Sometimes that’s exactly what you want.</p><p>But every loop is another decision, another call, more latency, more cost, and another place where behavior can diverge.</p><p>Anthropic makes essentially the same tradeoff when discussing agents: agentic systems are useful when the path genuinely needs to stay open-ended, but they trade additional cost and latency for that flexibility and introduce the possibility of compounding errors.</p><p>So for bounded semantic questions, I’d rather calculate the judgments broadly and keep the dependencies in code.</p><h3>This isn’t an argument against agents</h3><p>I actually think the opposite is more interesting.</p><p>A judgment primitive could make agent systems easier to contain.</p><p>An agent is useful when I can’t know the exact path in advance. A coding agent may need to explore a repository, form hypotheses, edit several files, run tests, and adapt based on what happens.</p><p>I don’t want to turn all of that into a predefined workflow.</p><p>But there are points inside that open-ended process where the decisions are much smaller.</p><p>Does this task touch authentication? Does this shell command appear destructive? Does this patch contain unrelated scope? Does this change require human approval? Does the implementation appear to satisfy this particular requirement?</p><p>Those are bounded judgments.</p><p>So you could have something like:</p><pre>Agent<br>  ↓<br>Investigate and propose action<br>  ↓<br>Bounded semantic judgment<br>  ↓<br>Policy / authority check<br>  ↓<br>Tool<br>  ↓<br>Deterministic result<br>  ↓<br>Agent continues</pre><p>The agent still reasons. The compiler still tells you whether the code compiles. Tests still establish deterministic facts. The host still owns permissions.</p><p>The judgment model helps with the fuzzy boundary in between.</p><p>That separation feels useful.</p><h3>The part I’m less certain about</h3><p>This is where I think the idea starts getting more speculative.</p><p>If you can turn unstructured state into bounded semantic values, why only use those values once?</p><p>Imagine every support ticket goes through a normal Jev evaluation like this:</p><pre>{<br>  &quot;questions&quot;: {<br>    &quot;intent&quot;: {<br>      &quot;type&quot;: &quot;choice&quot;,<br>      &quot;instructions&quot;: &quot;What is the customer&#39;s primary intent?&quot;,<br>      &quot;criteria&quot;: {<br>        &quot;billing&quot;: &quot;The customer has a billing or payment issue.&quot;,<br>        &quot;technical&quot;: &quot;The customer needs help with a technical problem.&quot;,<br>        &quot;account&quot;: &quot;The customer needs help with their account.&quot;,<br>        &quot;other&quot;: &quot;The request does not fit the other categories.&quot;<br>      }<br>    },<br>    &quot;urgent&quot;: {<br>      &quot;type&quot;: &quot;noul&quot;,<br>      &quot;instructions&quot;: &quot;Does the customer indicate that this issue is urgent or time-sensitive?&quot;<br>    },<br>    &quot;frustration&quot;: {<br>      &quot;type&quot;: &quot;score&quot;,<br>      &quot;instructions&quot;: &quot;How frustrated does the customer appear?&quot;,<br>      &quot;criteria&quot;: [<br>        &quot;Calm and neutral.&quot;,<br>        &quot;Concerned but civil.&quot;,<br>        &quot;Clearly frustrated.&quot;,<br>        &quot;Very angry or using strong language.&quot;<br>      ]<br>    },<br>    &quot;sensitive&quot;: {<br>      &quot;type&quot;: &quot;noul&quot;,<br>      &quot;instructions&quot;: &quot;Does the message contain sensitive personal or financial information?&quot;<br>    },<br>    &quot;wants_human&quot;: {<br>      &quot;type&quot;: &quot;noul&quot;,<br>      &quot;instructions&quot;: &quot;Is the customer asking to speak with a person?&quot;<br>    },<br>    &quot;needs_review&quot;: {<br>      &quot;type&quot;: &quot;noul&quot;,<br>      &quot;instructions&quot;: &quot;Should this ticket be reviewed by a person before an automated action is taken?&quot;<br>    }<br>  }<br>}</pre><p>This is already how Jev wants you to structure the problem. Each question is small and focused. Choice handles the categorical decision, Score handles the spectrum, and Noul handles the yes-or-no judgments. They can all inspect the same state independently in a single request.</p><p>Normally, the application would use those answers immediately. Route the ticket. Decide whether to escalate it. Maybe trigger a human review.</p><p>But what if you also stored them?</p><p>The result already gives you structured values. A Choice returns the selected option and its probability distribution. A Score gives you a position on the scale. A Noul gives you the probability that the proposition is true.</p><p>So instead of treating the evaluation as something ephemeral, you could materialize a versioned semantic record alongside the original ticket:</p><pre>{<br>  &quot;projection&quot;: &quot;support-triage&quot;,<br>  &quot;version&quot;: 1,<br>  &quot;intent&quot;: &quot;technical&quot;,<br>  &quot;urgent&quot;: 0.94,<br>  &quot;frustration&quot;: 2.1,<br>  &quot;sensitive&quot;: 0.03,<br>  &quot;wants_human&quot;: 0.91,<br>  &quot;needs_review&quot;: 0.76<br>}</pre><p>Now the semantic interpretation isn’t trapped inside a model response anymore.</p><p>It’s data.</p><p>And once it’s data, all the boring infrastructure becomes available. You can cache it, index it, put it on a stream, query it with SQL, join it against other records, build dashboards over it, or trigger deterministic rules from it.</p><p>A downstream system doesn’t necessarily need another model call to discover that this is an urgent technical issue from a frustrated customer who wants a person.</p><p>Somebody already paid the semantic interpretation cost.</p><p>The meaning has been materialized.</p><p>I think of that as something like a <strong>semantic projection</strong>:</p><pre>Raw unstructured event<br>          ↓<br>Jev evaluation<br>          ↓<br>Typed semantic answers<br>          ↓<br>Versioned semantic record<br>          ↓<br>Streams / SQL / rules / dashboards / automation</pre><p>I want to be clear about where I’m extending the idea.</p><p>Jev already gives us the typed semantic evaluation. Its documented model is to send state plus typed questions and get structured answers that code can use directly.</p><p>The speculative part is what happens if we stop treating those answers as temporary decisions and start treating them as durable data.</p><p>That’s where the abstraction leads me.</p><p>The model becomes part of ingestion.</p><p>Meaning becomes part of the data plane.</p><h3>Maybe there are more primitives hiding in models</h3><p>This is the larger question Jev leaves me with.</p><p>I’m not claiming that there’s a neat historical law where every useful internal mechanism eventually becomes standalone infrastructure.</p><p>There isn’t.</p><p>But infrastructure does have a habit of becoming more useful when a capability gets a precise contract and can be composed independently.</p><p>We’ve seen that with storage, logs, caches, queues, indexes, policy engines, and schedulers.</p><p>So what happens when we start doing that with model capabilities?</p><p>Not:</p><blockquote><em>Give me some text.</em></blockquote><p>Something narrower.</p><p>Evaluate this proposition.</p><p>Rank these options.</p><p>Tell me how strongly this state matches this criterion.</p><p>Compare these two things semantically.</p><p>Project this unstructured event into this known schema.</p><p>Those don’t necessarily look like chatbots.</p><p>They look more like ordinary infrastructure components that happen to contain learned behavior.</p><p>And maybe that’s the part of Jev I find most interesting.</p><p>It’s not really about generating fewer tokens.</p><p>It’s that it forces a different question:</p><p><strong>Was generation actually the primitive the application needed?</strong></p><p>Sometimes the answer will absolutely be yes.</p><p>But sometimes the application never wanted language at all.</p><p>It just needed a judgment.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c8684f7e90d3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/jev-through-the-lens-of-a-devops-engineer-c8684f7e90d3">Jev Through the Lens of a DevOps Engineer</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[It All Collapses Into Caring]]></title>
            <link>https://medium.com/devops-ai/it-all-collapses-into-caring-316372308d09?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/316372308d09</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[ai-productivity]]></category>
            <category><![CDATA[human-centered-ai]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[futureof-work]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Sun, 13 Sep 2026 15:00:48 GMT</pubDate>
            <atom:updated>2026-09-13T15:00:48.188Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R85PR4c0kAtI56wrvK2D5g.png" /></figure><p>It all collapses into caring.</p><p>A machine can write the email. Generate the code. Analyze the spreadsheet. Summarize the meeting. Design the presentation. Draft the contract. Propose the strategy. Give you a hundred variations before lunch.</p><p>What it can’t do is care whether any of it mattered.</p><p>I think that distinction is going to become more important, not less.</p><p>For a long time, we treated intelligence and skill as scarce resources. If you could write well, program well, analyze well, or design well, there was economic value in the simple fact that relatively few people could produce the thing.</p><p>AI changes that equation.</p><p>We’re already seeing it in the research. In one experiment with 453 professionals doing writing tasks, people using ChatGPT finished 40% faster while producing work rated 18% higher in quality. In a much larger study of customer support agents, AI increased productivity by around 15%, with some of the biggest gains going to less experienced workers.</p><p>Competent production is getting cheaper.</p><p>And when something becomes abundant, the scarce things around it start to matter more.</p><p>One of those things is care.</p><h3>Doing the Work Isn’t the Same as Caring About the Outcome</h3><p>Think about two people doing the same job.</p><p>The first does exactly what was requested. The deliverable is correct. The boxes are checked. The work is done.</p><p>The second looks at the request and realizes it doesn’t quite make sense. She asks why the customer needs it. She spots an assumption everyone else missed. She realizes the technically correct solution is going to create another problem three weeks from now, so she changes course and sticks with it until the outcome is actually good.</p><p>We often describe the second person as talented.</p><p>But I’m not sure talent is the right word.</p><p>She cares.</p><p>Care produces a lot of behaviors that look like intelligence from the outside. Attention to detail. Persistence. Curiosity. Judgment. Taste. Initiative. Empathy. Ownership.</p><p>You notice things because you care enough to look. You develop judgment because you stick around long enough to see what happens after your decisions. You develop taste because the difference between acceptable and excellent bothers you.</p><p>And you take ownership because completing the assigned task isn’t the same as solving the problem.</p><p>That distinction gets interesting when you look at where AI actually helps.</p><p>Researchers working with Boston Consulting Group gave 758 consultants realistic knowledge-work tasks. On tasks that were well within GPT-4’s capabilities, the consultants using AI completed 12% more tasks, worked about 25% faster, and produced higher-quality results.</p><p>But there was another part of the experiment.</p><p>The researchers deliberately gave the consultants a task where AI was more likely to lead them in the wrong direction. On that task, people using AI were less likely to arrive at the correct answer.</p><p>The researchers called this the <strong>jagged technological frontier</strong>. AI can be extraordinarily capable at one task and surprisingly unreliable at another that looks almost identical.</p><p>Which means knowing how to produce an answer is becoming less of the problem.</p><p>Knowing whether you should trust the answer is becoming part of the work.</p><h3>AI Makes Answers Cheap. Outcomes Are Different.</h3><p>Suppose an AI can generate a good marketing plan in thirty seconds.</p><p>That doesn’t eliminate marketing. It makes producing a marketing plan cheaper.</p><p>Someone still has to decide which market matters. Someone has to understand the customer well enough to recognize when the research is misleading. Someone has to choose among several plausible strategies, get people behind one of them, notice when reality starts disagreeing with the plan, and change direction before too much time and money are gone.</p><p>The artifact was never the whole job.</p><p>We just got used to confusing the visible output with the underlying responsibility.</p><p>The same thing is true almost everywhere. A teacher doesn’t just produce explanations. A doctor doesn’t just produce diagnoses. A manager doesn’t just produce plans. A programmer doesn’t just produce code. A designer doesn’t just produce pixels.</p><p>Their work is connected to some outcome in the world.</p><p>And this is where some of the AI productivity research gets especially interesting.</p><p>In 2025, METR ran a randomized study with experienced open-source developers working in codebases they knew well. Everyone expected AI to make them faster. The developers themselves thought it would.</p><p>It didn’t.</p><p>With AI tools available, they took 19% longer.</p><p>Even more interesting, after doing the work, the developers still believed AI had made them about 20% faster.</p><p>That result shouldn’t be generalized to all software development, and the tools have continued improving since the study. But there’s something useful hiding in the result.</p><p><strong>Generating code and moving a real software system forward aren’t necessarily the same thing.</strong></p><p>The AI can make the production step feel easier while introducing work somewhere else: understanding the output, checking it against the existing system, correcting subtle mistakes, integrating it, and deciding whether it actually solved the problem.</p><p>That gap between <em>producing something</em> and <em>being responsible for what happens next</em> is where I think a lot of the interesting work is moving.</p><h3>Abundance Usually Creates More Demand</h3><p>There’s an assumption buried in a lot of conversations about AI: if machines make work easier, there must be less work left for humans.</p><p>I’m not convinced that’s how abundance works.</p><p>When computation became cheap, we didn’t decide we had enough computation. We put computers everywhere. When communication became cheap, we didn’t communicate less. We started sending billions of messages. When photography became nearly free, we didn’t decide we had enough photographs.</p><p>We took more.</p><p>Economists have seen versions of this pattern for a long time. Lowering the cost of doing something doesn’t necessarily mean we consume less of it. Sometimes lower costs expand demand enough that we end up doing substantially more.</p><p>We don’t know yet whether AI will produce that kind of effect across knowledge work. But it’s not hard to imagine.</p><p>If software becomes ten times cheaper to build, we’ll attempt software projects that weren’t worth building before. If a high-quality video takes an afternoon instead of a month, we’ll make more video. If one person can investigate an idea that used to require a team, we’ll investigate more ideas.</p><p>The frontier moves.</p><p>And as it moves, we get new problems around selection, judgment, coordination, trust, integration, and responsibility.</p><p>Abundance doesn’t necessarily eliminate human ambition.</p><p>It can raise our expectations.</p><h3>So What Becomes Scarce?</h3><p>Imagine a world with effectively unlimited competent work.</p><p>Unlimited drafts, designs, analyses, code, proposals, and recommendations.</p><p>At that point, producing another plausible answer isn’t particularly valuable.</p><p>Knowing which answer deserves attention is.</p><p>Knowing what’s actually good becomes valuable. Understanding someone well enough to know what they need instead of simply giving them what they asked for becomes valuable. Having the judgment to say a perfectly plausible answer is wrong becomes valuable.</p><p>So does taking responsibility when things go badly.</p><p>So does sticking around after the deliverable is handed over.</p><p>The research doesn’t prove that these things become more economically valuable as AI improves. That part is my bet.</p><p>But the evidence is already showing us something important: AI’s value depends heavily on the person, the task, and the context.</p><p>It can make a professional writer 40% faster. It can help a less experienced support agent perform closer to an experienced one. It can make consultants dramatically better on one task and more likely to be wrong on another. It can even make experienced developers slower while they believe they’re going faster.</p><p>Capability alone doesn’t tell you whether you’re getting a better outcome.</p><p>Someone still has to make that judgment.</p><p>The more abundant execution becomes, the more valuable I expect judgment to become. The more abundant answers become, the more valuable good questions become. The more abundant content becomes, the more valuable taste becomes.</p><p>And the more abundant capability becomes, the more valuable responsibility becomes.</p><p>Underneath all of those is care.</p><h3>That Doesn’t Mean Care Automatically Wins</h3><p>There’s an uncomfortable part of this argument.</p><p>Just because care creates value doesn’t mean organizations will always reward it.</p><p>We’re very good at measuring throughput and surprisingly bad at measuring whether someone actually improved an outcome.</p><p>The person who quietly prevents ten problems can look less productive than the person who closes a hundred tickets. A nurse can provide enormous human value while working inside a system optimized around minutes per patient.</p><p>AI could make that worse.</p><p>It’s very easy to imagine organizations using AI to increase measurable output while squeezing out the time required to actually care.</p><p>More tickets. More content. More code. More reports. More everything.</p><p>That looks like productivity right up until you ask whether any of it made things better.</p><p>And there’s a warning in the research here too. The customer-support study found that AI’s effects weren’t uniform. Less experienced workers improved substantially, while the most experienced workers saw much smaller gains. In some measures, the highest-skilled workers even experienced small declines in quality.</p><p>The interesting question isn’t simply, “Does AI make people more productive?”</p><p>It’s: <strong>Which people? Doing what? Measured how? Toward what outcome?</strong></p><p>That last question matters.</p><p>The places where care has the biggest advantage will probably be the places where people stay close to the consequences of their work. Small businesses. Professional services. Healthcare. Education. Hospitality. Craft. Leadership. Entrepreneurship.</p><p>Anywhere reputation compounds and you can’t completely separate the person doing the work from what happens afterward.</p><p>In those environments, caring isn’t just a private virtue.</p><p>It can become an economic advantage.</p><h3>The People I’d Bet On</h3><p>If I were trying to predict who thrives in an AI-saturated economy, I wouldn’t start by asking who knows the most about AI.</p><p>I’d look for the people who can’t help taking ownership.</p><p>The engineer who wants to know whether customers are actually using the feature. The salesperson who tells a prospect not to buy something that won’t solve their problem. The manager who notices someone on the team has gone quiet. The carpenter who fixes the detail nobody else would have noticed. The founder who answers the support ticket because she genuinely wants to understand why the customer is frustrated.</p><p>Give those people better tools and something interesting happens.</p><p>They don’t become unnecessary.</p><p>They get leverage.</p><p>And maybe that’s the part of the AI productivity research I find most interesting.</p><p>A lot of the evidence so far isn’t showing a machine simply replacing a person. It’s showing the machine changing what the person can do. Sometimes dramatically. Sometimes unevenly. Sometimes in ways where the person doesn’t even correctly perceive whether it helped.</p><p>Which makes the person on the other side of the tool matter quite a bit.</p><p>A person with mediocre tools and extraordinary care can already accomplish remarkable things. Give that same person extraordinary tools and the ceiling gets much higher.</p><p>That’s one of the more optimistic ways I’ve found to think about what comes next.</p><p>We’re heading toward an abundance of capability. More answers than we can read. More software than we can use. More content than we can consume. More things we could build than we have time to pursue.</p><p>Which means someone still has to decide what matters.</p><p>Someone has to notice when something isn’t right. Someone has to stick around long enough to see what happens. Someone has to take responsibility for the outcome.</p><p>Someone has to care.</p><p>And I suspect there will be plenty of work for the people who do.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=316372308d09" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/it-all-collapses-into-caring-316372308d09">It All Collapses Into Caring</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Your Project Structure Is Part of the Agent Harness]]></title>
            <link>https://medium.com/devops-ai/your-project-structure-is-part-of-the-agent-harness-f8dbc7515a9e?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/f8dbc7515a9e</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[harness-engineering]]></category>
            <category><![CDATA[ai-sdlc]]></category>
            <category><![CDATA[agent-harness]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Thu, 10 Sep 2026 18:08:16 GMT</pubDate>
            <atom:updated>2026-09-10T18:08:16.396Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qr0pIas8sm_Lr6d-_6PXBg.png" /></figure><p>When we talk about AI agent harnesses, we usually talk about the things immediately surrounding the model.</p><p>Prompts. Tools. Permissions. Guardrails. Context management. The loop that lets the agent look at what happened, take another action, and keep going.</p><p>That definition makes sense. But the more I work with coding agents, the more I think it leaves out something important.</p><p>The project itself.</p><p>The programming language. Runtime. Dependencies. Package manager. Directory structure. Architecture. Type system. Schemas. Test framework. Build commands. Linters. CI. Deployment configuration.</p><p>Normally, we think of these as software engineering choices.</p><p>But from the agent’s perspective, they have another property:</p><p><strong>Every choice already encoded into the project is a choice the agent doesn’t have to make.</strong></p><p>And I think that makes project structure part of the agent harness.</p><h3>Every unspecified choice becomes an agent decision</h3><p>Imagine dropping a coding agent into an empty directory and asking it to build a service.</p><p>Before it can even get to the problem you care about, it has to make a surprising number of decisions.</p><p>What language should this be written in? Which version? Which framework? npm, Yarn, pnpm, or Bun? Where should tests live? How should configuration work? Which testing framework should it use? What should the build command be? How should errors be represented? How should dependencies be pinned? How does this thing eventually get deployed?</p><p>A capable model can answer all of those questions.</p><p>I’m just not sure why we’d want it to.</p><p>If your organization has already decided that services use TypeScript, pnpm, PostgreSQL, Vitest, ESLint, Docker, and a particular CI workflow, there isn’t much value in asking the agent to reconstruct those decisions every time it starts a task.</p><p>Encode them into the project.</p><p>Now the environment looks very different.</p><p>The agent doesn’t have to decide which language to use because the repository is already TypeScript. It doesn’t need to choose a package manager because there’s a pnpm-lock.yaml. It doesn&#39;t have to invent a testing strategy because the project already has tests, conventions, and a test command.</p><p>It doesn’t even have to guess which checks matter. CI tells it.</p><p>You’ve made the problem smaller before the model has done any reasoning at all.</p><h3>The project is choice architecture</h3><p>Think about two coding agents using exactly the same model, prompt, tools, permissions, and context window.</p><p>Drop one into an empty directory.</p><p>Drop the other into a mature repository with established dependencies, architectural boundaries, schemas, types, tests, build commands, lint rules, and CI.</p><p>Technically, both agents have the same capabilities.</p><p>In practice, they’re solving very different problems.</p><p>The first agent has to infer the environment its solution should live in. The second gets to discover it.</p><p>That distinction shows up in repository-level coding work too. Models tend to do better when they can see useful surrounding context: imports, types, dependencies, nearby implementations, and the structural relationships between pieces of code.</p><p>The model matters.</p><p>But so does the shape of the world we put around it.</p><h3>Architecture can live in the harness too</h3><p>This goes beyond tooling.</p><p>Say you want the agent to know that a project follows domain-driven design.</p><p>You could put this in an instruction file:</p><blockquote><em>This project follows domain-driven design.</em></blockquote><p>Useful, but now the agent has another set of questions.</p><p>What are the bounded contexts? Where does domain logic live? Where do application services go? How is infrastructure separated from the domain? What can depend on what? Where should a new capability be added?</p><p>Compare that with a repository that already looks like this:</p><pre>src/<br>├── billing/<br>│   ├── domain/<br>│   │   ├── aggregates/<br>│   │   ├── entities/<br>│   │   ├── value_objects/<br>│   │   └── events/<br>│   ├── application/<br>│   │   ├── commands/<br>│   │   ├── queries/<br>│   │   └── ports/<br>│   └── infrastructure/<br>│       ├── persistence/<br>│       └── adapters/<br>├── identity/<br>│   ├── domain/<br>│   ├── application/<br>│   └── infrastructure/<br>└── shared/</pre><p>Now the repository itself is carrying architectural information.</p><p>And if static analysis prevents domain from importing infrastructure, something even more interesting happens.</p><p>The architecture isn’t just being explained anymore.</p><p>It’s being enforced.</p><p>I keep coming back to this progression:</p><p><strong>Instruction → convention → structure → executable constraint.</strong></p><p>The further a stable decision can move along that path, the less interpretation we’re asking the agent to do.</p><h3>Executable constraints beat good intentions</h3><p>Structured output is a nice example of this.</p><p>You can tell a model:</p><blockquote><em>Produce JSON that conforms to this schema.</em></blockquote><p>And with a good enough model, that can be extremely reliable.</p><p>But extremely reliable still isn’t the same thing as impossible to violate.</p><p>If the generation system mechanically restricts the output to values allowed by the schema, the invalid choices disappear from the action space.</p><p>The model doesn’t have to remember the rule anymore.</p><p>The environment owns it.</p><p>Software repositories are already full of mechanisms that work this way.</p><p>A formatter removes a bunch of formatting decisions. A type checker rejects invalid relationships. A schema constrains valid data. A dependency rule blocks forbidden coupling. A test turns an expectation into executable behavior. A linter converts conventions into machine feedback. CI defines what has to pass before the work is considered acceptable.</p><p>None of those things make the model smarter.</p><p>They make certain kinds of intelligence unnecessary.</p><p>I think that’s an important distinction.</p><h3>The repository becomes a policy surface</h3><p>Once coding agents are doing meaningful work inside our repositories, a lot of boring project files start to look different.</p><p>A dependency manifest doesn’t just tell the build system what to install. It tells the agent which ecosystem it’s working in and what libraries are already available.</p><p>A lockfile doesn’t just make builds reproducible. It makes dependency decisions concrete.</p><p>A directory structure isn’t only organization. It’s architectural guidance.</p><p>The type system isn’t just helping developers catch mistakes. It’s a machine-readable validator the agent can use while it works.</p><p>The test suite isn’t only there to prevent regressions. It’s executable evidence about what the system is expected to do.</p><p>CI isn’t just automation around the repository. It defines the conditions under which the repository considers a change acceptable.</p><p>An architecture test can go one step further and say, effectively:</p><blockquote><em>Code shaped like this does not belong here.</em></blockquote><p>The repository isn’t passive context anymore.</p><p>It’s a <strong>policy surface</strong>.</p><p>It’s carrying decisions about what kind of software is allowed to exist inside it.</p><h3>Good harnesses don’t only add capabilities</h3><p>A lot of the conversation around agents is understandably about capability.</p><p>Give the agent shell access. Give it search. Give it a browser. Give it database access. Give it more tools. Give it subagents.</p><p>All of those things expand what the model can do.</p><p>But there’s another side of harness design that I think gets less attention:</p><p><strong>Reduce the number of things the model needs to decide.</strong></p><p>If a process is predictable, we already tend to prefer a predefined workflow over asking an agent to reason its way through every step. Autonomous reasoning becomes valuable when the problem itself is less predictable.</p><p>Why wouldn’t the same idea apply inside the repository?</p><p>Choosing how to model a genuinely new business capability might require intelligence.</p><p>Choosing whether your company uses npm, Yarn, pnpm, or Bun probably doesn’t.</p><p>Working through an ambiguous domain requirement might require intelligence.</p><p>Figuring out where tests belong probably shouldn’t.</p><p>Diagnosing an unfamiliar performance problem might require intelligence.</p><p>Remembering which command CI is eventually going to run should not.</p><p>There are decisions that require intelligence, and then there are decisions that merely consume it.</p><p>Those aren’t the same thing.</p><h3>Scaffolding starts to look like agent infrastructure</h3><p>This also changes how I think about project templates.</p><p>Historically, scaffolding has mostly been a developer convenience. Don’t make every team recreate the same boilerplate. Give people a standard starting point and move on.</p><p>For coding agents, the template is doing something else too.</p><p>It’s establishing the environment before the reasoning starts.</p><p>A good starter repository can already decide the language, runtime, package manager, project layout, architecture, module boundaries, testing strategy, static analysis, configuration conventions, observability, security defaults, CI, deployment, infrastructure, and the canonical commands used to interact with all of it.</p><p>Instead of explaining those assumptions to the agent on every task, you encode them once.</p><p>Then the agent starts from the result.</p><p>That makes scaffolding, starter repos, architecture tests, generators, and project conventions feel much closer to prompts, tools, and permissions than I used to think they were.</p><p>They shape the agent’s behavior before the first meaningful decision gets made.</p><h3>Structure is context that can enforce itself</h3><p>There’s also a useful distinction here between documentation and structure.</p><p>Documentation can say:</p><blockquote><em>The domain layer must not depend on infrastructure.</em></blockquote><p>That’s useful because it can explain why.</p><p>But structure can make the separation visible.</p><p>Static analysis can enforce it.</p><p>CI can verify it.</p><p>These things aren’t replacements for one another. They’re layers.</p><p>Documentation explains the intent. Structure makes the intended design discoverable. Tooling tells you whether the implementation actually follows it.</p><p>That leads to a rule I’ve started using when thinking about agent environments:</p><p><strong>If a stable decision can be encoded into the environment, don’t leave it solely as an instruction to the agent.</strong></p><p>If you can configure the formatter, configure it.</p><p>If you can provide the schema, provide it.</p><p>If you can establish the directory structure, establish it.</p><p>If you can enforce the dependency boundary, enforce it.</p><p>If there’s one canonical build or validation command, expose it.</p><p>If the definition of done can be executable, make it executable.</p><p>Natural language should be doing the work that actually requires interpretation.</p><h3>But structure has a boundary</h3><p>Of course, you can’t encode everything into a repository.</p><p>What does the customer actually mean by this requirement?</p><p>Should we optimize for delivery speed or long-term flexibility here?</p><p>Is this edge case important enough to support?</p><p>Should we refactor the surrounding subsystem or make the smallest possible change?</p><p>Why did the business make this strange decision three years ago?</p><p>Does the implementation technically satisfy the requirement while completely missing its intent?</p><p>You can’t solve those questions by adding another folder.</p><p>Domain-driven design is actually a good example of the boundary.</p><p>A repository can encode where bounded contexts live. It can enforce the dependencies between them. It can give the agent familiar patterns for aggregates, repositories, commands, and events.</p><p>But deciding what the bounded contexts <em>should be</em> still requires understanding the business.</p><p>The repository can encode the result of reasoning.</p><p>It can’t eliminate reasoning when the answer depends on meaning.</p><p>That’s the line I find useful:</p><p><strong>Stable decisions can often become structure. Contextual decisions still require interpretation.</strong></p><h3>Harness engineering is also decision engineering</h3><p>So I’ve started thinking about agent harnesses a little differently.</p><p>The question isn’t only:</p><p><strong>What capabilities does this agent need?</strong></p><p>There’s another question right beside it:</p><p><strong>What decisions can we make before the agent arrives?</strong></p><p>Tools expand what the agent can do.</p><p>Guardrails constrain what it’s allowed to do.</p><p>Context gives it information it needs to reason.</p><p>Project structure tells it what we’ve already decided.</p><p>And executable constraints determine which answers the surrounding system will actually accept.</p><p>That doesn’t mean every convention should become a hard rule. You can absolutely over-structure a project and encode the wrong assumptions. Some choices genuinely need to stay open.</p><p>The goal isn’t to remove choice.</p><p>It’s to remove <strong>irrelevant choice</strong>.</p><p>If the organization already made a decision, the model shouldn’t have to make it again.</p><p>If something can be checked mechanically, the model shouldn’t be solely responsible for remembering it.</p><p>If an architectural rule is stable, it probably shouldn’t exist only in prose.</p><p>If there’s one correct way to build, test, validate, or package the project, make that path obvious.</p><p>Then leave the model’s reasoning for the parts of the problem where we actually wanted intelligence in the first place.</p><p>That’s increasingly how I think about agent harness design.</p><p>Don’t just give the agent better tools.</p><p>Build a world where fewer irrelevant decisions are left for it to make.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f8dbc7515a9e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/your-project-structure-is-part-of-the-agent-harness-f8dbc7515a9e">Your Project Structure Is Part of the Agent Harness</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Don’t Learn to Code. Learn to Build Systems.]]></title>
            <link>https://medium.com/devops-ai/dont-learn-to-code-learn-to-build-systems-80eb9d0cb45d?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/80eb9d0cb45d</guid>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[sdlc]]></category>
            <category><![CDATA[systems-thinking]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Fri, 04 Sep 2026 17:34:50 GMT</pubDate>
            <atom:updated>2026-09-04T17:35:07.339Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KueYM4MjRyCrHTT9eobxfA.png" /></figure><p>I’ve been thinking about the phrase “learn to code.”</p><p>For a long time, that was pretty reasonable advice. If you wanted to build software, you needed to learn how to program. And even though professional software engineering has always involved much more than writing code — requirements, architecture, testing, operations, security — the code was still the thing sitting in the middle of all of it.</p><p>AI is starting to make that relationship a little weird because AI can produce a lot of code now. The more capable these systems become, the more I think we need to separate two things we’ve historically treated as almost the same: <strong>producing code and producing a working system.</strong></p><p>They’re not the same skill. And increasingly, I think the second one is the skill that matters.</p><h3>The Test Looks Obvious After Production Writes It for You</h3><p>There’s an old distributed-systems study that captures the problem really well. Researchers looked at roughly 200 production failures across Cassandra, HBase, HDFS, MapReduce, and Redis.</p><p>The surprising part wasn’t that distributed systems fail. It was how ordinary many of the failures looked once someone understood what had happened.</p><p>According to the research, 92% of the catastrophic failures involved incorrect error handling. Around a third involved handlers that were almost comically bad: empty catch blocks, enormously broad exceptions followed by process termination, or code paths containing comments that basically amounted to “we have no idea how we got here.”</p><p>Even more interesting, about 98% of the failures could be reproduced using only three nodes. Most were deterministic, and the clues needed to understand them were usually already sitting in the logs.</p><p>So these weren’t necessarily unknowable failures. They were sequences nobody had thought to test yet.</p><p>That matters because one conclusion from the research was that many of the failures could have been caught with relatively simple tests. Akshay Shah made a point about this in a Papers We Love talk that I keep thinking about.</p><p>The tests are simple <strong>after you know the failure</strong>.</p><p>Imagine a test that says:</p><blockquote><em>Partition node A. Let node B begin recovery. Heal node A. Roll the write-ahead log. Move traffic back. Verify that no writes disappear.</em></blockquote><p>Once you’ve experienced the outage, that test isn’t especially complicated. Before the outage, someone has to imagine that exact sequence.</p><p>That’s the hindsight problem.</p><p>We’re very good at turning incidents into regression tests. Production gives us the sequence, we encode it, and now we can make sure that particular failure never happens again.</p><p>Useful, absolutely.</p><p>But it doesn’t solve the harder problem:</p><p><strong>How do you test the failure nobody has imagined yet?</strong></p><h3>Build a World That Can Discover the Test</h3><p>This is where deterministic simulation testing gets interesting.</p><p>Instead of requiring a human to enumerate every failure sequence that might matter, you construct a small world where those sequences can emerge. You combine property-based testing, generated inputs, fault injection, unusual timing, network failures, and state transitions, then run enormous numbers of combinations.</p><p>The important part is determinism.</p><p>If the simulator discovers some bizarre interaction — one node fails, another starts recovering, the network heals, background maintenance kicks in, and suddenly an invariant breaks — you can replay that exact sequence.</p><p>The weird production accident becomes a reproducible experiment.</p><p>And many of these failures don’t require some theatrical disaster. They emerge from ordinary operations colliding in the wrong order: startup, recovery, node changes, compaction, network partitions.</p><p>Each event can be perfectly reasonable.</p><p>The sequence isn’t.</p><p>That changes the relationship between the engineer and the test. Instead of saying:</p><blockquote><em>Think of every important failure and write a test for it.</em></blockquote><p>You get:</p><blockquote><em>Define what must remain true, then build an environment capable of finding sequences that make it false.</em></blockquote><p>I think that distinction becomes even more important with AI.</p><p>If the implementation itself is increasingly generated, the scarce human contribution starts moving toward specifying the properties the implementation must not violate.</p><p>Money cannot disappear. Authorization cannot silently be bypassed. Acknowledged writes cannot vanish. One customer cannot observe another customer’s data.</p><p>The system needs enough information about what “correct” means that the simulated world can prove it wrong.</p><p><strong>Don’t ask humans to predict every failure. Build a world capable of discovering them.</strong></p><h3>What If Code Isn’t the Main Control Surface?</h3><p>StrongDM’s Software Factory is one of the more interesting examples I’ve seen of pushing this idea pretty far.</p><p>StrongDM describes an engineering process where humans aren’t writing the generated code and aren’t reviewing it in the traditional line-by-line sense either. Instead, correctness is established primarily by observing what the system actually does.</p><p>That’s a pretty aggressive constraint, and it doesn’t mean everyone should throw away code review tomorrow.</p><p>But the architecture of the idea is interesting.</p><p>If source inspection stops being your primary control surface, the engineering work doesn’t disappear.</p><p>It moves.</p><p>Now you need to get much better at describing desired behavior, defining properties that must hold, constructing realistic environments, generating failures, observing outcomes, and deciding whether the system actually did what you intended.</p><p>You didn’t remove engineering.</p><p>You moved the control surface up a level.</p><p>StrongDM’s Digital Twin Universe makes that idea concrete. Instead of tiny mocks, they build behavioral replicas of dependencies like Okta, Jira, Slack, Google Docs, Google Drive, and Google Sheets so software can interact with something that behaves more like the real environment.</p><p>That lets them run thousands of scenarios, inject failures, replay conditions deterministically, and exercise cases that would be expensive or dangerous against the real services.</p><p>The more I looked at that, the more I started thinking that the interesting artifact isn’t just the software anymore.</p><p>It’s the world around the software.</p><p>If implementation keeps getting cheaper, maybe it becomes rational to spend dramatically more effort constructing the <strong>world the implementation has to survive in</strong>.</p><p>Twin the dependencies.</p><p>Twin the infrastructure.</p><p>Twin the users.</p><p>Twin the economics.</p><p>Twin the failures.</p><p>Then put the system inside that world and see what happens.</p><h3>Assertion → Scenario → Trajectory → History</h3><p>Once you start building that world, another problem appears.</p><p>Suppose the requirement is:</p><blockquote><em>The user should be able to complete checkout.</em></blockquote><p>An agent can make that test pass while still building the wrong system. Maybe it bypasses fraud checks. Maybe it disables authorization. Maybe the user completes checkout perfectly while the business loses money on every transaction.</p><p>The assertion is green.</p><p>The system is wrong.</p><p>That’s reward hacking showing up inside software engineering.</p><p>This is why I like the progression:</p><p><strong>Assertion → Scenario → Trajectory → History</strong></p><p>An assertion asks whether some condition is true.</p><p>A scenario asks whether the user actually accomplished something.</p><p>A trajectory asks how the system accomplished it.</p><p>And a history asks what happens when thousands of those trajectories accumulate over time.</p><p>Each layer makes it harder to satisfy the letter of the requirement while violating what we actually meant.</p><p>Imagine the synthetic user successfully completes checkout. Great.</p><p>Now inspect the trajectory.</p><p>Did authentication work normally? Did payment flow through the intended service? Did retries behave correctly? Did latency remain acceptable? Did the system quietly route through some deprecated dependency? Did it bypass a security control to make the path easier?</p><p>The final outcome alone doesn’t tell you whether you should trust the system.</p><p>You need the path.</p><p>And even the trajectory only tells you about one interaction.</p><p>Systems accumulate.</p><p>So do engineering decisions.</p><p>Which brings us to history.</p><h3>Deterministic Simulation, But for Engineering Decisions</h3><p>Traditional deterministic simulation asks:</p><blockquote><em>What happens if we explore lots of possible sequences of system events?</em></blockquote><p>A longitudinal engineering simulator asks:</p><blockquote><em>What happens if engineering decisions themselves become part of the sequence?</em></blockquote><p>Suppose the agent chooses an architecture in simulated month one. It works beautifully, but changes infrastructure cost.</p><p>By month four, those costs start to matter, so the agent makes another decision. That reduces cost but introduces a reliability problem.</p><p>In month seven, the agent adds a service to fix the reliability issue. That works too, but now the system is more operationally complex.</p><p>Six months later, that complexity makes another product change substantially harder.</p><p>None of those decisions has to be obviously wrong.</p><p>That’s the point.</p><p><strong>Decision N changes the environment in which decision N+1 happens.</strong></p><p>The benchmark is no longer asking whether each patch is correct in isolation. It’s asking whether a series of locally reasonable decisions adds up to a healthy system.</p><p>A deterministic simulator can ask whether software survives its own state transitions.</p><p>A longitudinal simulator can ask whether an engineer survives its own decisions.</p><p><strong>Can you live with what you built?</strong></p><h3>Make the Benchmark Endogenous</h3><p>This gives us a useful property for AI evaluation: the test becomes <strong>endogenous</strong>.</p><p>The exact evaluation isn’t fully specified before the agent starts. It emerges from inside the system.</p><p>The agent makes a decision. That decision changes performance, cost, architecture, user behavior, or operational complexity. The simulator generates the consequences, and those consequences create the next decision.</p><p>So the exact state the agent sees six months from now depends partly on what it did today.</p><p>There is no fixed question 37 waiting in a file somewhere.</p><p>Your answer to question 12 helped create question 37.</p><p>That matters for evaluation because the exact history being tested can only exist after the benchmark starts. It doesn’t eliminate everything a model might have learned during training, but it makes a precomputed answer key much less useful.</p><p>The world responds to the agent.</p><h3>Run the Clock</h3><p>This is where I think the benchmark gets really interesting.</p><p>Give the agent a system.</p><p>Give it users, infrastructure, telemetry, objectives, constraints, and an engineering budget.</p><p>Then run time.</p><p>A week.</p><p>A month.</p><p>A year.</p><p>Maybe three years.</p><p>While that happens, change the environment. Increase traffic. Change the user population. Deprecate a dependency. Change infrastructure pricing. Introduce a security incident. Change product requirements. Add a competitor.</p><p>Now the agent has to choose.</p><p>Build the feature?</p><p>Fix reliability?</p><p>Migrate the database?</p><p>Pay down technical debt?</p><p>Accept the cost for another quarter?</p><p>Stop doing something entirely?</p><p>Give it enough telemetry to make those decisions: errors, latency, infrastructure costs, retention, support volume, user behavior.</p><p>And make the signals disagree.</p><p>Maybe a feature increases engagement but costs too much to operate. Maybe a reliability change improves availability while making every request slower. Maybe an optimization helps power users while destroying onboarding.</p><p>Maybe every technical dashboard is green while users quietly stop coming back.</p><p>Now the problem isn’t:</p><blockquote><em>Which metric should I maximize?</em></blockquote><p>It’s:</p><blockquote><em>What kind of system are we trying to maintain?</em></blockquote><p>That’s where the evaluation stops looking like coding and starts looking like stewardship.</p><h3>The Artifact Isn’t the Repository</h3><p>At the end of a benchmark like this, I’m not sure the final repository is even the most interesting artifact.</p><p>I want the history.</p><p>What broke first? Which signal exposed it? What did the agent think was happening? What did it change? Did the intervention work? What else did that change break?</p><p>Did the agent recognize when an earlier decision had become a problem? Did it reverse bad decisions? When did technical debt accumulate? When did it choose to pay that debt down?</p><p>What happened to reliability, users, cost, and operational complexity?</p><p>What happened when two objectives directly conflicted?</p><p>That gives you something different:</p><p><strong>an operating history.</strong></p><p>And my suspicion is that this history could tell us much more about engineering ability than whether someone — or some model — can solve a collection of isolated implementation problems.</p><p>That’s a hypothesis.</p><p>But it’s one we can test.</p><h3>A Three-Year Software Engineering Benchmark</h3><p>The benchmark itself is almost annoyingly simple to describe:</p><blockquote><strong><em>Here is a system. Own it for three simulated years.</em></strong></blockquote><p>Users arrive.</p><p>Things fail.</p><p>Requirements change.</p><p>Infrastructure degrades.</p><p>The agent gets telemetry and decides what to investigate.</p><p>It proposes changes. Those changes alter the system. The world responds.</p><p>Then the next period begins.</p><p>Don’t score each patch independently.</p><p>Score the history.</p><p>What happened to user outcomes? Reliability? Cost? Security? Recovery behavior? Technical debt? Adaptability? Operational complexity?</p><p>And don’t give it one neat number to optimize.</p><p>Real systems don’t work like that.</p><p>A system that achieves perfect reliability by refusing every request is useless. A system growing rapidly while becoming economically impossible to operate isn’t healthy either.</p><p>You’re looking for something more subtle.</p><p><strong>Stewardship.</strong></p><h3>From Coding to Stewardship</h3><p>I think that word captures the shift better than “systems thinking.”</p><p>Stewardship assumes you’re responsible for something that has to keep living. You don’t get to optimize one moment in isolation.</p><p>You have to understand the state of the system now, the pressures acting on it, and how today’s intervention changes tomorrow’s options.</p><p>Can the agent distinguish a symptom from a root cause?</p><p>Can it recognize when an architecture that made sense in year one has become a bottleneck in year three?</p><p>Can it decide when technical debt is worth accepting, and when it needs to be paid down?</p><p>Can it improve reliability without making the product unusably slow?</p><p>Can it reduce costs without creating an operational nightmare?</p><p>Can it make hundreds of locally sensible decisions that add up to a globally healthy system?</p><p>That’s much closer to the job.</p><p>At that point, software starts looking a little less like laying bricks and a little more like city planning. You’re managing infrastructure, economics, incentives, maintenance, human behavior, and unintended consequences.</p><p>The implementation still matters.</p><p>But the difficult part is how everything interacts.</p><h3>So, Don’t Learn to Code?</h3><p>The title is deliberately provocative.</p><p>Programming still matters. Understanding software still matters. Architecture matters. Databases matter. Networks matter. Security matters. Debugging definitely matters.</p><p>I’m not arguing that everyone should stop understanding how computers work because the autocomplete got better.</p><p>I’m arguing that the abstraction we should aspire to operate at is moving.</p><p>If AI continues making implementation cheaper, then implementation becomes less of the scarce resource.</p><p>The scarce resource becomes knowing what should exist, which properties must hold, what you’re actually optimizing for, and how to construct a world capable of proving you wrong.</p><p>It becomes knowing how to separate signal from noise, how to recognize when a local improvement creates a global problem, and how today’s change alters tomorrow’s choices.</p><p>Maybe the future software engineer spends less time asking:</p><blockquote><em>How do I implement this?</em></blockquote><p>And more time asking:</p><blockquote><em>What would have to be true for me to trust this system?</em></blockquote><p>Then you build the world that can answer that question.</p><p>Specify the system. Define its invariants. Build its world. Instrument it. Simulate the users. Simulate the dependencies. Generate the failures.</p><p>Break it. Replay it. Understand what happened. Change the system. Measure the consequences.</p><p>Then keep going.</p><p>Because the real challenge isn’t producing the code.</p><p>It’s building a world that forces you to confront the questions you didn’t know to ask, and then living with the answers.</p><p><strong>Run the clock.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=80eb9d0cb45d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/dont-learn-to-code-learn-to-build-systems-80eb9d0cb45d">Don’t Learn to Code. Learn to Build Systems.</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Makes Big-Tech Engineering Practices a Small-Team Problem]]></title>
            <link>https://medium.com/devops-ai/ai-makes-big-tech-engineering-practices-a-small-team-problem-0d0b432af53b?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/0d0b432af53b</guid>
            <category><![CDATA[software-factories]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[aidlc]]></category>
            <category><![CDATA[systems-engineering]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Tue, 01 Sep 2026 14:55:28 GMT</pubDate>
            <atom:updated>2026-09-01T14:55:28.772Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s-ZYR6je5ZXSBwcKq2PsAw.png" /></figure><p>What happens when AI coding agents actually work?</p><p>I don’t mean when they write a neat little function or save twenty minutes on a refactor. I mean when they materially increase the amount of software a team can produce.</p><p>The size of that effect is still unsettled, and it varies enormously by task and context. In one controlled experiment, developers using GitHub Copilot completed a specific coding task <a href="https://arxiv.org/abs/2302.06590">55.8% faster</a>. In a 2025 randomized field study, experienced open-source developers working in mature repositories were <a href="https://arxiv.org/abs/2507.09089">19% slower</a> with the AI tools available to them. Those results are a useful warning against pretending there is a universal multiplier.</p><p>The more defensible claim is narrower: wherever agents do increase implementation speed, they do not automatically accelerate review, testing, deployment, observability, or recovery. A small team can therefore create change faster than its engineering system can safely absorb it.</p><p>That is a problem large engineering organizations have spent decades learning to manage. AI may make it a small-team problem too.</p><h3>The bottleneck moves</h3><p>Think about the path a change takes through an engineering organization. Somebody has an idea. There is some kind of design. Someone implements it. The code gets reviewed, tests run, an artifact gets built, and eventually it gets deployed. After that, production telemetry tells you whether it actually works. If something goes wrong, the team has to detect it and recover.</p><p>Now suppose AI makes implementation much faster. The rest of the pipeline does not speed up merely because code arrived sooner.</p><p>It is like a restaurant where chopping vegetables has always been the slowest part of the kitchen. Then someone installs a machine that can chop ten thousand onions in a few seconds. The chopping problem is solved, but the kitchen still has the same stove, frying pans, and serving staff. The onions stop being the bottleneck and start piling up on the floor.</p><p>Software organizations can behave the same way. Pull-request volume rises. CI gets busy. Review queues grow. Engineers open large agent-generated diffs and skim changes they would once have tried to understand. More changes reach production, operational load rises, and incidents pull people away from planned work.</p><p>This is not just a thought experiment. DORA’s 2025 research found a positive relationship between AI adoption and delivery throughput, but also found that AI can <a href="https://cloud.google.com/blog/products/ai-machine-learning/announcing-the-2025-dora-report">amplify weaknesses in the delivery system</a>, with delivery stability remaining a concern. GitHub reported on March 5, 2026 that Copilot code review accounted for <a href="https://github.blog/ai-and-ml/github-copilot/60-million-copilot-code-reviews-and-counting/">more than one in five code reviews on GitHub</a>. In May, GitHub described agent pull requests as <a href="https://github.blog/ai-and-ml/generative-ai/agent-pull-requests-are-everywhere-heres-how-to-review-them/">saturating review bandwidth</a>.</p><p>That is why I find it useful to distinguish <strong>change-generation capacity</strong> from <strong>engineering capacity</strong>. AI may increase the first without doing much for the second. Engineering capacity is the organization’s ability to ingest a change, verify it, understand its consequences, expose it to customers, detect when it is wrong, and recover without creating more damage than the change was worth.</p><p>Your real capacity is not how quickly you can generate code. It is how quickly your organization can <strong>safely absorb useful change</strong>.</p><h3>We have seen this problem before</h3><p>The encouraging part is that the underlying engineering problem is not new. Large engineering organizations have built systems that let many people change shared software continuously without relying on every person to remember every rule.</p><p>For a small team, those systems have historically looked excessive. Sophisticated CI, automated policy enforcement, progressive delivery, architectural controls, observability, and fault isolation seem natural at the scale of Google or Amazon and extravagant at the scale of a startup.</p><p>AI changes the relevant variable. Headcount still matters, but so do the rate, size, and consequence of the changes a team produces. A small organization can remain small on the org chart while putting pressure on its review and delivery systems that used to appear only at a larger scale.</p><p>That does not mean copying Big Tech bureaucracy. A thirty-person startup does not need a change-approval board where twelve people meet every Thursday to decide whether a bug fix may ship.</p><p>In fact, <a href="https://dora.dev/capabilities/streamlining-change-approval/">DORA’s research on change approval</a> points in the opposite direction. Heavy external approval processes tend to reduce software-delivery performance, and DORA found no evidence that more formal external review was associated with lower change-failure rates.</p><p>The useful part of the Big Tech model is the control system underneath it: peer review, continuous integration, fast tests, observability, automated policy, progressive delivery, architectural boundaries, and more scrutiny where the consequences justify it.</p><p>There is a large difference between requiring a manager to inspect a spreadsheet and enforcing the same security policy when a change is proposed. If a dependency violates policy, infrastructure can reject the change. There is no meeting, debate, or queue.</p><p>The goal should not be to add process because the team is moving faster. It should be to remove entire categories of things humans have to worry about.</p><h3>Make mistakes harder to create</h3><p>My hypothesis is that architecture becomes more important as agents write more code, not less. Every strong boundary removes a decision that a person or an agent has to get right. DORA’s 2025 findings offer some support for that direction: teams with loosely coupled architectures and fast feedback loops appear better positioned to benefit from AI. They do not prove that better AI inevitably increases the value of architecture, but they reinforce the mechanism.</p><p>The analogy I like is bumper bowling. If you want to stop a child from throwing the ball into the gutter, you can stand behind them repeating instructions: keep your wrist straight, aim for the middle, take the right number of steps, do not release too early. Or you can put the bumpers up.</p><p>The first approach depends on someone remembering and following the instructions every time. The second changes the environment so that an entire class of failure becomes difficult or impossible.</p><p>Many software standards still rely on the first approach. If a service must not connect directly to a database, you can write the rule in a wiki and ask every engineer and agent to remember it. Or you can make <a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">network policy</a> prevent the connection. It does not matter how confidently an agent writes code that tries to reach the database; the environment rejects the path.</p><p>The same idea applies to validation. You can keep telling developers and agents to validate a value before using it, or design an API so that a sensitive operation accepts only a value that has already been validated. Raw input no longer fits through the interface.</p><p>Authorization works the same way. You can remind everyone to be careful with permissions, or design the authorization model so that bypassing it requires something conspicuously abnormal.</p><p>The strongest engineering standards are not instructions people remember. They are <strong>properties of the system people inherit</strong>. The standard stops being a page in the company wiki and becomes something closer to a law of physics inside the engineering environment.</p><p>That matters when some of the actors changing the system are AI agents.</p><h3>Assume something will still get through</h3><p>No architecture, test suite, or static analyzer will prevent every defect. Eventually an agent will write something logically wrong in a way the tools do not understand. A test will miss an edge case. A production dependency will behave differently from the simulated version.</p><p>So the next question is: when something is wrong, how wrong are you allowing it to be?</p><p>This is where blast radius matters. AWS describes <a href="https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/reducing-scope-of-impact-with-cell-based-architecture.html">cell-based architecture</a> as a way to isolate faults so that a bug, bad deployment, or overload event affects a bounded part of the system instead of everybody at once.</p><p>Think of the watertight compartments in a submarine. The goal is not to guarantee that the hull can never be breached. It is to make sure one breach does not sink the vessel.</p><p>A thirty-person startup probably does not need to reproduce Amazon’s architecture. The principle still travels well: limit permissions, avoid unnecessary global state, partition critical workloads where it makes sense, place risky behavior behind feature flags, use quotas and rate limits, separate control planes from the systems doing the work, and make dependencies explicit.</p><p>The same thinking should apply to agents. An agent that can modify application code probably should not also have unilateral authority to modify the CI system evaluating that code, weaken tests, expand its own permissions, and deploy directly to production.</p><p>Otherwise the same actor gets to write the exam, take the exam, and grade it.</p><p>This is not an AI-specific discovery. It is an application of separation of duties and least privilege. The actor proposing a change should not also get to redefine what counts as safe.</p><h3>Let machines do machine review</h3><p>When agents can produce code faster than people can inspect it, human attention becomes one of the most expensive resources in the system. It makes little sense to spend that attention answering questions a computer can determine reliably.</p><p>Does it compile? Do the tests pass? Did an API contract change? Is a migration backwards compatible? Did dependency risk increase? Did permissions expand? Did the change cross an architectural boundary? Did test coverage fall? Did someone modify the CI system itself?</p><p>If there is a deterministic answer, the engineering system should usually answer it before a human reviewer opens the pull request.</p><p>Google has built presubmit systems around this idea for years. Its published guidance describes <a href="https://abseil.io/resources/swe-book/html/ch20.html">automated formatting, static analysis, tests, and project-specific checks</a> running before code lands. The value is not only finding bugs. It is preserving reviewer attention for work that requires judgment.</p><p>There is another bottleneck hiding here: the feedback has to be fast. A comprehensive test suite is not enough if every small change waits forty-five minutes for the first useful signal. The bottleneck has merely moved from human review to compute.</p><p>Google’s CI guidance recommends <a href="https://abseil.io/resources/swe-book/html/ch23.html">fast, reliable presubmit tests</a> and broader validation later. That is the right shape for an agentic workflow too: cheap, high-signal checks first; more expensive evidence when the risk warrants it.</p><p>The goal is not maximal verification everywhere. It is <strong>fast, trustworthy evidence</strong> at the point where a decision must be made.</p><h3>Human review moves up the stack</h3><p>AI does not have to eliminate human review. It can change what human review is for.</p><p>If machines have already handled formatting, known static errors, test failures, dependency policies, and architecture checks, reviewers can focus on questions that require understanding the system.</p><p>Does the implementation solve the user’s problem, or is it merely functional? Are we creating a second abstraction for something that already exists? What happens under concurrency? Is the authorization model correct? What assumptions are we making about production data? What happens if a dependency disappears for ten minutes? Can the migration be reversed? Is this still the architecture we want six months from now?</p><p>Those are questions about semantics, tradeoffs, history, intent, and failure modes. They are better uses of human judgment.</p><p>They are also easier to answer when the change is small. Google’s review guidance says small changes are <a href="https://google.github.io/eng-practices/review/developer/small-cls.html">reviewed faster and more thoroughly</a>, are easier to reason about, and are easier to roll back. There is no universal line-count threshold, but the direction is clear: as a diff grows beyond a reviewer’s cognitive budget, review becomes more likely to degrade into skimming and ceremony.</p><p>That should influence how we evaluate agents. The most impressive demo is often an agent that disappears for an hour and returns with an entire feature. The more useful engineering behavior may be an agent that advances the work through small, independently understandable, independently verifiable changes.</p><p>The optimization target should not be “complete as much of the task as possible without involving a human.” It should be “advance the task through the largest change that can still be independently understood and verified.”</p><p>That is a different definition of autonomy.</p><h3>Production is part of the test</h3><p>Eventually every team encounters the same uncomfortable truth: the test environment is not production.</p><p>You can simulate a great deal, but some things appear only when real traffic, data, dependencies, timing, and users touch a change. Mature delivery systems do not treat that as surprising. They control exposure.</p><p>A new version might reach internal users first, then a small percentage of customers, then progressively larger groups. Google SRE describes <a href="https://sre.google/workbook/canarying-releases/">canarying</a> as evaluating a partial production deployment before continuing the rollout.</p><p>In that sense, deployment is not the moment testing stops. It is testing against reality under controlled exposure.</p><p>Feature flags create another useful separation: deploying code and releasing behavior do not have to be the same event. A team can deploy an implementation while leaving the behavior disabled, then enable it when the people who understand the system are available and watching.</p><p>That changes the psychology of deployment. Instead of pushing something and hoping, you deliberately expose behavior, watch the evidence, and increase exposure when the evidence looks good.</p><p>Deployment becomes a controlled experiment, and every experiment needs an escape hatch.</p><p>Rollback should be boring. If deploying is fast but recovery requires hours of coordinated manual work, the system does not have meaningful delivery velocity. AWS’s reliability guidance recommends <a href="https://docs.aws.amazon.com/wellarchitected/2024-06-27/framework/ops_mit_deploy_risks_auto_testing_and_rollback.html">automated testing and rollback</a> precisely to reduce recovery time and customer impact.</p><p>Every newly discovered failure mode should improve that machinery for future deployments. This feedback loop matters even more when agents increase the rate of change.</p><h3>Turn failures into infrastructure</h3><p>Something will still break. One of the clearest differences between mature and immature engineering systems is what happens next.</p><p>The immature question is, “Who broke production?” The mature question is, “Why was this failure possible?”</p><p>Why did we not detect it earlier? Why could it affect this many users? Why did recovery take this long? Why did review allow it? Why did the canary miss it? What could make the same class of failure harder to produce next time?</p><p>That is the point of <a href="https://sre.google/workbook/postmortem-culture/">blameless postmortems</a> in SRE: focus on gaps in the system rather than individual blame, then produce verifiable actions that prevent or mitigate recurrence.</p><p>Suppose an agent introduces an authorization bug. Fixing the bug is necessary, but it may be the least durable response.</p><p>Can the incident become a regression test? Could it become a static-analysis rule? Should the API make the invalid state impossible? Is a stronger permission boundary needed? Should every service use a shared library? Should CI detect the pattern? Could the canary have caught it sooner? Was operational context missing from what the agent knew?</p><p>Each move takes knowledge that existed in someone’s head after an incident and turns it into something the system can enforce next time. The lesson leaves human memory and enters the machinery.</p><p>I think of that as <strong>systemic immunity</strong>. The term is a metaphor, not an industry metric, but the underlying practice is concrete: a mature organization does not merely recover from incidents. It modifies the environment that produces future changes.</p><h3>Build the paved road</h3><p>These ideas converge on the paved road: the safe way to build software should also be the easiest way.</p><p>Creating a service should not require remembering fifteen separate rules about observability, ownership, CI, deployment policy, security defaults, rollback, and dashboards. The template should provide them.</p><p>Open a pull request and the appropriate verification should run automatically. Make a risky change and the rollout should become more conservative. Touch authentication or infrastructure and the right reviewer should be requested. Create a service and its logging, telemetry, ownership metadata, deployment configuration, security defaults, and rollback machinery should already exist.</p><p>This is established platform-engineering practice. Google describes <a href="https://cloud.google.com/blog/products/application-modernization/platform-engineering-control-mechanisms">golden paths, guardrails, and safety nets</a> as ways to make the preferred choice easy while enforcing non-negotiable constraints.</p><p>The same idea should extend to agents. An agent should receive canonical information about APIs, service ownership, allowed dependencies, operational constraints, architecture, and SLOs. That is a design recommendation rather than a proven optimal implementation, but it follows the same principle: important context should live in the environment, not in thirty lines copied into every prompt.</p><p>Small AI-native teams may end up investing heavily in this layer. They will not be recreating Big Tech bureaucracy. They will be recreating the <strong>leverage of its engineering control systems</strong>.</p><p>The platform becomes part of the team. Its job is to make the correct path fast, the dangerous path difficult, and the invalid path impossible.</p><p>Once generating code gets cheaper, everything around the code matters more.</p><h3>The engineer may become an orchestrator</h3><p>One plausible consequence is that the shape of engineering work changes.</p><p>If agents perform more mechanical implementation, productive engineers may become less defined by how much logic they personally type. More of the job becomes orchestration: deciding what work agents should do, how to decompose it, what context they need, what constraints the environment should enforce, what evidence is required, where human judgment adds value, how much production exposure a change should receive, and what the system should learn from failure.</p><p>This is a forecast, not an established end state. AI adoption is already changing workflows, but there is not yet evidence that “engineer as orchestrator” will become the dominant role everywhere.</p><p>If it does, engineering does not become less technical. Technical judgment moves toward the level of systems.</p><p>The valuable engineer is no longer only the person who can produce an implementation. It is also the person who can build an environment in which implementations can be generated, verified, deployed, observed, and corrected at high velocity.</p><p>That is a different kind of leverage.</p><h3>So what does velocity mean now?</h3><p>Some familiar engineering metrics look especially weak in this environment. Lines of code were never a useful proxy for value, and they become nearly meaningless when an agent can generate thousands on demand. Pull-request counts and the percentage of a codebase written by AI say little about whether customers received a useful, reliable change.</p><p>Those metrics tell you how fast the robotic chef is chopping onions. They do not tell you whether the kitchen can serve dinner.</p><p><a href="https://dora.dev/guides/dora-metrics/">DORA’s current delivery metrics</a> offer a better foundation: change lead time, deployment frequency, failed-deployment recovery time, change failure rate, and deployment rework rate. For AI-heavy workflows, I would also watch reliability, the human-review cost per successful production change, the size of changes people are expected to understand, customer impact from failures, and the time required to turn a new failure mode into a durable guardrail.</p><p>Some of those additions are proposals, not validated industry standards. But together they capture the outcome that matters better than code volume does.</p><p>I call that outcome <strong>risk-adjusted change throughput</strong>. It is not a standard DORA metric or a finished formula. It is a name for the quantity an engineering organization is really trying to improve: the rate at which it can deliver useful change, discounted by the human cost, operational risk, and customer harm involved.</p><p>That is why practices once associated with very large organizations may become small-team concerns. Large organizations built delivery controls because many people were changing shared systems at once. Agents can create similar pressure without a corresponding increase in headcount.</p><p>The magnitude will vary. A small team will not automatically operate like an organization five or fifty times its size. But if its change-generation capacity rises faster than its delivery capacity, the response should not be bureaucratic throttling. It should be a better system for absorbing change.</p><p>Generating enormous amounts of code is not the interesting capability. Safely shipping large amounts of useful change is.</p><p>There is one further, more speculative question. Today, humans usually investigate incidents, write postmortems, and decide which regression test, deployment rule, or security boundary should be added. Agents are beginning to assist with telemetry analysis and root-cause diagnosis. It is conceivable that an agent which contributed to a failure could also propose a guardrail against its recurrence.</p><p>That is not yet a reliable closed loop, and machine-generated safety systems are not evolving at anything like the demonstrated rate of machine-generated code. But if they eventually do, a new problem appears. A paved road made from constantly changing, machine-generated policies, tests, and architectural constraints could become difficult for humans to understand.</p><p>The system still needs accountable people who know why the road looks the way it does.</p><p>Otherwise we may solve the problem of safely absorbing machine-generated code by creating safety machinery that nobody can meaningfully reason about — which feels like exactly the kind of problem we will discover once the current one starts working.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0d0b432af53b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/ai-makes-big-tech-engineering-practices-a-small-team-problem-0d0b432af53b">AI Makes Big-Tech Engineering Practices a Small-Team Problem</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI is for Alignment: Execution Contracts]]></title>
            <link>https://medium.com/devops-ai/ai-is-for-alignment-execution-contracts-3f03d8577ae5?source=rss-d380ba8c5fb0------2</link>
            <guid isPermaLink="false">https://medium.com/p/3f03d8577ae5</guid>
            <category><![CDATA[spec-driven-development]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[agents]]></category>
            <dc:creator><![CDATA[Owen Zanzal]]></dc:creator>
            <pubDate>Sun, 23 Aug 2026 20:11:09 GMT</pubDate>
            <atom:updated>2026-08-23T20:11:09.189Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eAzHBpw-BLnzkwgOMOLosQ.png" /></figure><p>In the last post, I wrote about one of my favorite prompts when I’m working with an AI agent:</p><blockquote><a href="https://medium.com/devops-ai/ai-is-for-alignment-simulate-this-d5b54f19daa1"><em>Simulate this.</em></a></blockquote><p>The idea is pretty simple. Before I trust the agent to build something, I want to see what it thinks I mean. Walk me through a real scenario. Show me what success looks like. Show me how it fails. Try the weird edge cases.</p><p>Basically: don’t tell me you understand. Show me what your understanding predicts.</p><p>I’ve been doing something similar with coding agents lately, but one step earlier in the process.</p><p>Before I ask them to write the code, I ask them to write a contract.</p><p>I didn’t really think of it as a contract when I started doing this. I thought I was just getting more specific about the requirements: tell the agent what the software should do, spell out the acceptance criteria, make sure there aren’t any obvious ambiguities, then let it implement the thing.</p><p>But after doing this a few times, I realized I was solving a slightly different problem.</p><p>A specification tells you what the software is supposed to do. A contract tells the agent what it’s allowed to do while getting there.</p><p>That distinction doesn’t matter much if I’m using AI as autocomplete. In that situation, I’m still making nearly every decision myself. The AI suggests some code, I look at it, maybe I take it, maybe I don’t.</p><p>An agent is different. If I give it access to a repository, it can inspect the code, edit files, run tests, chase down failures, create commits, and potentially open a pull request. At that point I’m not really asking for a suggestion anymore. I’m delegating a chunk of engineering work.</p><p>And once I’m delegating, I care about more than the final behavior.</p><p>A simple bug fix is a good example. Imagine a renderer that expects some nested object to exist. Production data comes back without that object, the renderer throws, and the page breaks.</p><p>The software requirement is almost trivial:</p><blockquote><em>If the object is missing, render an empty value instead of throwing.</em></blockquote><p>That’s a perfectly good description of what the software should do. But it says almost nothing about how I want an agent to change the system.</p><p>The agent could add optional chaining to one line. It could change the API so the object is always returned. It could wrap the renderer in a try/catch, introduce a generic error-handling abstraction, refactor the data model, or notice three similar accesses nearby and “clean those up” too.</p><p>Some of those might even be reasonable engineering decisions. They’re just not necessarily the work I meant to delegate.</p><p>That’s the part the contract handles.</p><p>For a change like this, I might say that only two files are in scope. The API is explicitly out of scope. Shared rendering infrastructure is out of scope. Don’t refactor neighboring code. Add a failing test before changing the implementation. Preserve a few specific existing behaviors. Run a particular set of commands before calling the work complete.</p><p>And then there’s the line I’ve started caring about most:</p><blockquote><em>If reality doesn’t match the assumptions in this contract, stop and report the discrepancy.</em></blockquote><p>Don’t silently reinterpret the assignment.</p><p>That probably sounds obvious because it’s something experienced engineers do all the time without being told. If I give another engineer a small bug ticket, I usually don’t need to add, “Please don’t redesign the architecture while you’re in there.” I also don’t need to say, “If the ticket is based on a bad assumption, come talk to me instead of inventing a new requirement.”</p><p>Those expectations are just part of working on a team. There’s a social contract around delegation that humans pick up over time.</p><p>AI agents don’t really get that for free.</p><p>They have an objective, some context, a set of tools, and instructions. If there are five plausible ways to accomplish the objective, something has to tell them which decisions are theirs to make and which ones aren’t.</p><p>That’s how I’ve started thinking about execution contracts. They turn a bunch of implicit engineering expectations into explicit boundaries.</p><p>But this is where the idea from “Simulate This” comes back in.</p><p>Once the contract is written, I don’t necessarily want the agent to start coding yet.</p><p>I can say:</p><blockquote><em>Simulate this contract.</em></blockquote><p>Stay read-only. Don’t change any files. Don’t run anything that mutates the repository. Walk me through exactly what you expect to do, in order. Show me which files you’ll touch, which tests you’ll add, what you expect to fail before the fix, what you expect to pass afterward, which commands you’ll run, and where the contract requires you to stop.</p><p>That gives me a dry run of the work before the work happens.</p><p>I tried this recently with a real bug-fix contract, and this is where things got interesting.</p><p>The agent walked through the contract step by step. It checked the preconditions, identified the tests it intended to add, predicted the red phase, showed the one-line implementation change it expected to make, then walked through every verification command and the final delivery report.</p><p>That alone was useful. I could see the whole change before it touched anything.</p><p>But the simulation also found two bugs in the contract.</p><p>One part of the contract said that after adding the new tests, I should expect one test to fail. The simulation walked through the actual data shapes and showed that two tests would fail.</p><p>The implementation wasn’t wrong. The contract was.</p><p>Then it found a much more important problem.</p><p>One of the required verification steps was the project’s normal lint command. Nothing unusual about that, except the script included --fix.</p><p>So if the agent followed the contract literally, the verification step itself could modify files across the repository. The same contract that said “only these two files may change” was telling the agent to run a command that could change other files.</p><p>That’s exactly the kind of thing I want to discover before execution.</p><p>The dry run wasn’t just predicting the code. It was testing whether the instructions I had given the agent actually made sense when followed as a sequence of actions.</p><p>That changed how I think about simulation.</p><p>In the original version, “simulate this” was mostly about making the agent’s model of the <strong>system</strong> visible. Show me how you think this feature behaves. Show me what this configuration does. Show me what happens in this edge case.</p><p>When I pair simulation with a contract, I’m making the agent’s model of the <strong>delegation</strong> visible too.</p><p>What do you think I authorized you to change? In what order are you going to act? Which commands are you going to run? What do you think counts as evidence? What happens if one of your assumptions is wrong? Where do you believe your authority ends?</p><p>That’s a different kind of simulation.</p><p>It’s basically a rehearsal.</p><p>And I like that there’s a human approval point between the rehearsal and the real thing. The flow becomes:</p><blockquote><em>Write the contract.<br>Simulate the contract.<br>Fix the misunderstandings.<br>Then execute.</em></blockquote><p>The contract defines the boundaries. The simulation shows me how the agent interprets those boundaries before it exercises any authority.</p><p>That matters because capable agents notice things.</p><p>They notice the neighboring bug, the inconsistent API, the duplicated code, the abstraction that could probably be cleaner. And because they’re trying to be useful, there’s a natural temptation to fix those things too.</p><p>Humans do this, obviously. We call some versions of it scope creep.</p><p>The difference with agents is that implementation is cheap. An agent doesn’t get three files into an unnecessary refactor, feel the afternoon disappearing, and decide maybe this isn’t the day to clean up the whole subsystem. If you give it broad permission to solve a problem, it can solve it very broadly.</p><p>So I’ve started putting prohibited changes directly into contracts. Don’t change the API. Don’t add a dependency. Don’t refactor neighboring code. Don’t change the test configuration. Don’t weaken an acceptance test to get the suite green.</p><p>If you find another legitimate issue, record it as a known gap and leave it alone.</p><p>I don’t think that removes engineering judgment. It defines where the judgment belongs.</p><p>There’s a big difference between telling an agent exactly which keystrokes to make and telling it, “Choose the implementation, but these architectural boundaries are fixed.”</p><p>If I make the contract too prescriptive, I’ve basically written the patch in English. The agent is just translating prose into syntax. But if I make it too loose, I’m relying on the agent’s idea of a reasonable implementation being the same as mine.</p><p>The useful space seems to be somewhere in the middle. Define the outcome, the invariants, the scope, and the things that must not change. Define the evidence that will count as success, and define the points where the agent has to hand the decision back to me.</p><p>Testing is where this gets especially useful.</p><p>I don’t always want a contract to say:</p><blockquote><em>Add a test.</em></blockquote><p>Sometimes I want it to say:</p><blockquote><em>Add the test first. Run it against the current implementation. Show that it fails for the reason we think it should fail. Then change the implementation.</em></blockquote><p>I ran into a case recently where that distinction mattered a lot. There was already a unit test that looked almost exactly like coverage for a production bug. Even the name of the test made it sound like we were covered.</p><p>But the mock returned an empty object, while production returned no object at all. Those two states sounded almost interchangeable when you described them in English. In JavaScript, they behaved very differently.</p><p>The existing test passed against the broken implementation.</p><p>So the red phase became part of the contract. Use the actual production shape. Run the test before fixing anything. Capture the failure. Then change the code.</p><p>And this is another place where the dry run helps. Before changing the implementation, the agent can tell me which cases it expects to fail and why. If that prediction doesn’t match reality when it eventually runs the tests, we’ve learned something. Maybe the contract is wrong. Maybe the agent’s model is wrong. Maybe there’s something about the system neither of us understood.</p><p>Any of those are useful things to discover before the agent keeps going.</p><p>This is why I’ve started thinking of contracts less as detailed requirements documents and more as agreements about evidence.</p><p>What does the agent actually have to prove before it can say it’s done? Which tests ran? Which build? Which type checks? Did the diff stay inside the agreed scope? Were there manual checks the agent couldn’t perform? Is anything still uncertain?</p><p>I’ve started asking for a delivery report against the contract at the end of the work too. I don’t want the usual “implemented the requested fix and all tests pass.”</p><p>I want to know which requirements were satisfied and how, which acceptance scenarios passed, which commands ran, and which files changed. I also want to know what wasn’t tested, what gaps were discovered and deliberately left alone, and whether anything deviated from the original contract.</p><p>There’s a nice symmetry to that.</p><p>Before implementation, simulate the contract and show me what you think is going to happen.</p><p>After implementation, report against the contract and show me what actually happened.</p><p>Now I have something to compare.</p><p>And there’s another benefit to this that I didn’t expect. Writing and simulating the contract often exposes problems before anyone writes code.</p><p>If I can’t clearly describe the required behavior, maybe the requirement isn’t ready. If I can’t write down an acceptance scenario, maybe I don’t actually know what success looks like. If I keep adding exceptions to the allowed scope, maybe this isn’t a local change after all.</p><p>And if the dry run keeps hitting places where I don’t know whether the agent should continue or stop, there’s probably a product or engineering decision hiding there that I haven’t made yet.</p><p>That’s why these two ideas fit together so well for me.</p><p>A contract forces me to make the delegation explicit.</p><p>Simulation forces the agent to make its interpretation of that delegation explicit.</p><p>One makes my model visible. The other makes its model visible. And we get a chance to compare them before anything expensive happens.</p><p>So the distinction I keep coming back to is still:</p><p><strong>A specification constrains the software. A contract constrains the agent changing the software.</strong></p><p>But now I’d add one more thing.</p><p>Before I let the agent operate inside that contract, I want to see the rehearsal.</p><p>Write the contract.</p><p>Then simulate it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3f03d8577ae5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/devops-ai/ai-is-for-alignment-execution-contracts-3f03d8577ae5">AI is for Alignment: Execution Contracts</a> was originally published in <a href="https://medium.com/devops-ai">DevOps&lt;&gt;AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>