While I work to complete my new book “AI-Driven API Design”, I’m keeping my posts here sparse (see below) and light (see my “Approved by Management” comic series). I’ll return to my story-telling posts later this year once the book is off my plate. -- mamund For some time I’ve been working on creating self-driven, autonomous systems. I started GRAIL to understand what it takes to build software that can pursue goals without relying on a human to continually decide what happens next. Subscribe now As a result, I have settled on a couple useful axioms. They are very compact but I find them helpful. Designing Autonomous Systems Designing autonomous systems means making enough of an environment explicit for programs to situate themselves within it, use a goal to determine what is relevant, decide what to do next, recognize success, and determine when progress is no longer possible. Programming for Autonomy Programming for autonomy means turning assumptions about an environment into explicit facts, relations, and capabilities so that programs can pursue goals within it without human intervention. What Does This Change? This has helped me move from asking, “What does the program need to do next?” to asking, “What does the program need in order to decide what it will do next?” That shift has helped me focus on ways to implement autonomous solutions that work. Subscribe now
I have recently been traveling more than usual and it allows me some private “thinking time”. On a recent trip, I took down some thoughts and decided to share an expanded version of them here. -- @mamund Subscribe now The future is not a place to which we are inevitably marching. It is what emerges from the transformations we make in the here and now. We sometimes talk about the future as though it already exists somewhere ahead of us on some distant shore. But, in reality, the future is formed in the present. Every action we take, every capability we acquire, every condition we change alters what may happen next. We do not simply arrive at the future with the passage of time. We constantly create it through the myriad choices we all make in the here and now. The opportunity for our success exists in the here and now. Progress is the concerted effort of transforming our present conditions until that opportunity is realized. Some of what we need is already here within reach. Other conditions may need to be created, modified, or removed. The work of transformation starts with seeing the present clearly enough to recognize what is possible, what prevents that possibility from being realized, and what we have within us to change conditions around us. We don’t need to travel a path in order to arrive at success. We need only look around us, recognize the opportunity already present, and understand what must change to realize it. Terms like path and journey lead us to think in terms of sequence, distance, and direction. More useful questions are about the conditions in which we find ourselves here. What is true? What is missing? What can we change? Progress comes from acting on the answers to those simple questions. Success is not a destination. It is a possibility in the present waiting to be recognized and realized. We may need to learn something new, acquire a new skill, remove an existing constraint, or change the conditions around us before that possibility becomes real. The way to measure progress is to focus on what is newly possible. Success itself is recognizable when the conditions of the present are sufficient to support it. We are always trapped in the here and now. The future depends, in part, on what we see and how we choose to transform it. Subscribe now
I’ve been spending time on my GRAIL project on and off for the last several months, and I think it has reached an important milestone. One that deserves a full-on beta-release for others to see and play with. GRAIL started with a simple idea A year ago I asked myself a question that flips agentic architecture on its side. What if, instead of giving an agent a predefined workflow, we describe a bounded environment of capabilities, conditions, inputs, and effects and then simply give our agent a goal. My first working experiment was posted a little over a year ago and ran just over 150 lines. It was a tiny offering, but it showed me my basic idea was sound. The experiments since then have taken that idea much further than I originally expected. GRAIL can now pursue goals through different routes, choose among alternative capabilities, recover when a capability fails, recognize when a goal can no longer be resolved, and invoke capabilities implemented as local modules, HTTP services, or independent processes (e.g. stdio). Subscribe now Bounded Autonomy GRAIL is an experiment in bounded autonomy and it asks the question: how much autonomous behavior can emerge when we carefully design the environment rather than continually adding intelligence to the agent? AS it turns out, the answer is quite a bit when you create guardrails for the agent. And, in the case of GRAIL, everything is accomplished without the use of an LLM. That doesn’t mean you cannot use an LLM to make decisions, it means LLMs are not a requirement for creating self-driven agents. In fact, the agent I created is quite simple. It hosts a single algorithm and has just a few key points in engineered as “decision-points” where judgment can be implemented. More on that for another time. Agents and Environments As I wrap up a year of experimenting (24 posted in all), the basic lesson I have learned so far is this: What is not in the agent must be in the environment. Much of the agentic work I see today is focused on creating “smart” agents. I chose to do it another way. I’m pleased with how it is going and invite you to check it out. The repo is a bit rangey but, with some effort, I think you can figure out what’s going on. Beta Release Coming Soon The experiments are winding down now. I’m moving into stabilization and preparing the first public beta. This will be an installable CLI that you can use to create your own custom environments and launch your own experiments. When you do, I’d love to hear about it. More soon. Subscribe now
While I work to complete my new book “AI-Driven API Design”, I’m keeping my posts here sparse (see below) and light (see my “Approved by Management” comic series). I also apologize in advance for the “instructional tone” this piece has as my brain is in that head space while I work though the new book. I’ll return to my story-telling posts later this year once the book is off my plate. -- mamund Over the last year or so I’ve been experimenting with a simple model for empowering autonomous agents in constrained environments. I’ve dubbed this work “Goal-Resolution through Affordance-Informed Logic” or GRAIL since one of the key elements in the model is defining and referencing affordances (links and forms) for clients to call. NOTE : I’ve discussed my GRAIL work here on substack in the past. You can go to my archives page and search for “GRAIL” and see several articles. Feel free to refer to the previous articles at any time. Recently I created some experiments exploring how the system holds up when the service’s preconditions are presented to the client in random order. Does this model hold up even when the possible “next steps” are presented in random order? The GRAIL Algorithm As a quick refresher, the GRAIL algorithm is very small and, so far, very effective. The code for a fully operational GRAIL client is less than 50 lines of NodeJS. That includes console.log statements peppered through the instrumented client. The algorithm itself is straightforward: 1. Start with a goal 2. Attempt the action associated with that goal if SUCCESS then EXIT if BLOCKED by an unmet precondition, follow an available action that can satisfy the unmet condition then go to step 2 Continue until the original goal succeeds or there is no available path forward. The trick is to start at the end. Try the end goal first. This algorithm also depends on services knowing their own preconditions (you must be logged in first, you must have a non-zero balance, etc.) and exposing those preconditions to the client when they are unmet. Of course, this is all recursive so attempting to execute on an unmet precondition might also return a BLOCKED message with one or more unmet preconditions for that precondition; and so forth. Visually, it looks like this: At the heart of all this is to a registry of affordances (actions) arranged as pre-conditions for a particular goal (onboardCustomer, computeNetPresentValue, produceOverdueAccountsReport, and so forth). The registry is, essentially, the environment in which the agent exists and interacts with affordances. Let’s Randomize it A set of actions lives in an environment and that environment is expressed in a registry.json file. Each entry lists all the details needed to successfully execute the action such as name, associated function, inputs, expected outputs, side effects, and (if any) preconditions. The client is built to understand this standardized representation and uses the affordance collection to complete its assigned goal. In some cases, the affordance has quite a few preconditions that need to be met before the client can execute the selected action. For example, the registry entry below shows an affordance with eight preconditions. And, as we’ve already stated, it may be true that any one of these eight preconditions have their own list of additional preconditions (and so forth). The assertion I set out to test is whether the GRAIL system requires execution of preconditions in a specific order (e.g. in listed order). Since my current examples are all run as CLI utilities (there is no MCP/LLM needed), I needed to add a bit of randomization to the unmet precondition routine. This would approximate the possibility that a client app might not execute the preconditions in a fixed (and repeatable) order. All it took for that was to add a single line of code that randomly selected one of the unmet preconditions in the runtime registry: if (unmetPreconditions.length > 0) { const pre = unmetPreconditions[ Math.floor(Math.random() * unmetPreconditions.length) ]; Now, when I run the test, each trace has the potential to be slightly different. The questions is: Does the client consistently complete the goal, no matter the execution order of the preconditions? Tracing in GRAIL The GRAIL test harness has the ability to produce a trace file that shows the path of the client through the provided environment. With my randomizer in place, I ran several traces and happily discovered that first, each traced path is different and second, all traced runs reached the goal successfully. Below are screenshots of two example traces. Note the step-by-step order in the numerals next to each navigation line in the diagram. In this first example (below), you can see that the first precondition executed (after attempting the goal directly) is startCustomerOnboarding. Then there’s a series of back-and-forth actions to resolve the remaining preconditions before returning to execute the associated goal action. However, in the second run below (see below), the client followed a different path in attempts to resolve most of the minor details (starting with verifyCustomerPhonenumber before actually finding the startCustomerOnboarding action. An interesting side note that I did not have time to explore is that, while each trace shows a unique navigation order, both traces took the same number of steps (16) to complete. In fact, all the traces I ran took 16 steps. This leads me to assume that, while the navigation path may vary, the differences in path order do not impose any additional execution cost. There is no “cheaper” path in these GRAIL models. Different Routes, Same Destination In most workflow type systems, we’re lead into authoring a fixed series of steps in order to reach a goal. for example, an approximate rendering of my above onboarding example looks like this in Arazzo (see specs here). And it is important to note the the example below is a fixed set of steps in order. arazzo: 1.0.1 info: title: Customer Onboarding Workflow version: 1.0.0 sourceDescriptions: - name: customerApi url: ./openapi.yaml type: openapi workflows: - workflowId: onboardCustomer summary: Satisfy customer onboarding requirements and onboard the customer steps: - stepId: onboardingStarted operationId: startOnboarding - stepId: customerProfileCollected operationId: collectCustomerProfile - stepId: customerEmailSet operationId: setCustomerEmail - stepId: customerEmailVerified operationId: verifyCustomerEmail - stepId: customerPhoneNumberSet operationId: setCustomerPhoneNumber - stepId: customerPhoneNumberVerified operationId: verifyCustomerPhoneNumber - stepId: customerAddressSet operationId: setCustomerAddress - stepId: customerTermsAccepted operationId: acceptCustomerTerms - stepId: onboardCustomer operationId: onboardCustomer successCriteria: - condition: $statusCode == 200 I’m even seeing people working on creating agents that do this kind of planning before they attempt to reach the goal. I think this is not needed and makes the work of designers and engineers harder than it needs to be. The GRAIL model is built in a way that agents are free to take more than one particular path through the environment (as defined by the registry.json file) and still meet the intended goal. And they can do that without any hints or set up from a human. They just need the end goal and access to the environment. Conclusion GRAIL shows us we can create systems where we don’t need to know exactly what that path will be before starting a run. And we don’t need to guarantee the same path is traced each time the agent attempts to reach the goal. Essential guidance for GRAIL authors would be: Define the environment, not the path. If this kind of thing is interesting to you, feel free to check out the public GRAIL repo where some other experiments will be appearing in the near future.
Subscribe now
Subscribe now
Subscribe now
For regular readers, you may have noticed that I’ve been writing less here on Substack lately. That’s because I’ve been deep into writing my latest O’Reilly book, “AI-Driven API Design”. The book grew out of the workshops of the same name that I’ve been presenting over the past year. Now that the manuscript is taking shape, I’m taking the newly updated workshop on the road again. As AI becomes part of everyday software development, both this workshop and my forthcoming O’Reilly book explore a simple question: What does good API design look like in the age of AI? Subscribe now The fundamentals haven’t changed. Every successful API still begins with understanding the problem domain, identifying the people and systems involved, capturing intended behavior, and designing interfaces that are clear, reliable, and easy to evolve over time. What has changed is the design process itself and the tools at our disposal. AI can help us discover, model, document, implement, and validate designs in ways that were impractical or too costly just a few years ago. This is an opportunity to preserve quality while reducing the effort required to get from idea to install. Those are the ideas I’ve been exploring in my forthcoming O’Reilly book, “AI-Driven API Design”, and they’re the foundation for this workshop tour. In the workshop, we’ll work through a complete AI-assisted design process together. Starting with domain discovery and API Stories, we’ll move through ALPS, OpenAPI, behavioral testing, and traceability to see how intended behavior can be preserved from the first design conversation through running software. Along the way, we’ll explore how each design artifact contributes to APIs that humans and AI agents can understand, trust, and use with confidence. If you’ll be at one of these public events, I’d love to see you there. Follow the links for registration. September 1 — API World (Santa Clara, CA) September 17 — O’Reilly Live (Online) September 23 — We Are Developers World Congress North America (San Jose, CA) October 1 — API Conference New York (New York, NY) Discount: Join_APINY26 November 20 — API Conference Berlin (Berlin, Germany) The conversations that emerge during this tour will also help shape the final stages of the book. If you’ll be attending one of these events, I hope you’ll stop by and share your own perspective on what good API design looks like in the age of AI. Finally, if your organization or event is interested in hosting this workshop, or if there’s a city where you’d like to see it offered, let me know. I’d enjoy bringing the conversation to your community. Subscribe now
Subscribe now
For most of my software career I have focused on APIs, distributed systems, hypermedia, and software architecture. My writing, speaking, tool-making, and advising have all centered around improving the design, implementation, and evolution of running systems. I gravitated toward APIs because it was there, most clearly, that design decisions, both good and bad, had a direct and visible impact on the value and quality of software in the real world. It was there that I learned, and eventually taught others, how early choices shape later options or, as I often say, how to get from idea to install. Looking back over that journey, there are three ideas that connect nearly everything I’ve done over the last fifty years. Software development is a sequence of translations. Software architecture is about coordinating translations. Software engineering reduces manual translation while preserving behavioral intent. Let’s take each of these in turn... Subscribe now 1. Software development is a sequence of translations. Every software project begins with a story: an idea of intended behavior. Typically, the phrases “It would be great if we could ...” or “Our customers really want ...” and so forth. There is a set of actions that need to be translated from idea form into something more tangible. We give these translations different names such as requirements, user stories, data models, API descriptions, source code, test scripts, but they’re all doing the same thing. Each is another attempt to preserve behavioral intent while expressing it in a form suitable for the next participant in the process. This is often referred to as the software development life cycle (SDLC), Of course, every translation introduces opportunities for misunderstanding, drift, omission, and accidental complexity. And usually these translations are chained together in a sequence. This means the translation of the idea into a user story is one step and then translation of the user story into an API description document (e.g. Open API) is another translation. And that second translation depends on the quality and accuracy of the first translation (idea to story). Translations upon translations is the norm. Every translation inherits the strengths and the weaknesses of each translation that preceded it. The challenge isn’t simply writing software. It’s preserving behavioral intent across all the translations in the software development lifecycle. Designers must think not only about each translation, but also about the dependencies that form between translations. As the cost of generating source code continues to fall, the important question is “Is this code a faithful translation of the behavioral intent that came before it?” And what kind of evidence do you have that each translation along the way continues to preserve and protect the intended behaviors? Validating behavior preservation requires tooling. Translation is a skill. And that raises the question: if software development is a sequence of translations, what exactly is the role of software architecture? 2. Software architecture is really about coordination. If software development is a sequence of translations, software architecture determines where, when, and how those translations and the resulting systems coordinate. And coordination sits at the heart of building effective, available, scalable, and efficient systems (EASE). Looking back, I’ve come to see the history of software architecture as the history of reducing and reallocating coordination. Whether between data models and object models, services and clients, or people and teams, architecture has always been about deciding where coordination belongs. Amundsen’s Maxim is a haiku on the topic of systems coordination. Early systems relied heavily on meetings, requirements documents, and manual coordination. Object-oriented methods and UML attempted to coordinate increasingly complex systems through shared models. Service-oriented architectures and APIs shifted coordination into well-defined interfaces. Amazon’s “API Mandate” pushed even further, insisting that teams coordinate through explicit contracts rather than hallway conversations. After years of thinking about APIs and distributed systems, I’ve come to this definition: Software architecture is the art of determining where, when, and how coordination occurs so that intended behavior can be achieved while minimizing accidental coordination. Viewed this way, architectural styles are simply different strategies for managing coordination. I have spent my time on architecture models that favor extreme late-binding (via hypermedia affordances), reduced central control (via composable systems), and runtime adaptation. They differ less in what they build than in how they allocate coordination. Which brings us to the third idea. Nearly every major advance in our field has reduced the amount of manual translation and coordination required to preserve intended behavior. 3. Software engineering reduces manual translation while preserving behavioral intent. When I started in this field, I spent hours hand-writing COBOL programs on yellow legal pads, checking logic with truth tables and sequence charts before ever touching a computer. Then I’d wait for an open terminal to type everything in and submit the job to a batch queue. Hours later, I’d finally get the results—hoping for a few pages of successful output instead of a thick stack of compiler errors. Looking back, what strikes me most is how much of software development consisted of manual translation. Over the decades we’ve steadily reduced the manual effort required to translate ideas into running systems. Compilers eliminated one translation. High-level languages eliminated another. Libraries, frameworks, and cloud platforms reduced still more. AI continues that same trajectory. Together, these advances reveal a decades-long trend: Every major advance in software engineering has reduced manual translation without losing sight of behavioral intent. The work of translation is the work of abstraction; of converting thought into action. For decades we treated source code as the definitive description of a system. Increasingly, that role belongs to behavioral intent. Source code has become another translation rather than the destination. As manual translation disappears, evidence becomes more important than ever. The question is no longer who wrote the code. The question is whether the implementation faithfully preserves the intended behavior. Languages evolve. Platforms come and go. Even source code is becoming less central than it once was. But software engineering has always been, and will continue to be, the discipline of preserving behavioral intent across successive translations. The real question is whether behavioral intent survives the translation. Closing While I am best known for my work on web APIs, I have always thought of it as part of a broader effort to improve the way we design, build, and evolve software. REST, hypermedia, CSP, GRAIL, TRAM, and now AI coaching are simply different explorations of the same underlying question: How do we preserve intended behavior while requiring less manual translation and less unnecessary coordination? That question has shaped my work for almost than fifty years. I don’t expect it to stop now. Subscribe now
Subscribe now
Subscribe now
Subscribe now
Subscribe now
Now that summer has arrived in the northern climes, I’ve decided to lighten up on the long-form pieces for a bit and share some comic panels I’ve been playing with for a while. I hope everyone enjoys the summer months and gets a chuckle from my new series, Approved by Management. Let’s see where this leads. And, if you’re so inclined, feel free to drop me a note with feedback or suggestions on where the series might go next. Subscribe now