Stuff about Software Engineering

Tag: Pattern

Pattern: Make Frontier Models Replaceable

Making language models pluggable is useful, but it does not make an AI application independent of the model behind it.

A system may support several providers while still relying heavily on the behavior of one particular frontier model. When prompts carry implicit business rules, priorities and workflow decisions, the API can be replaceable while the business solution remains tightly coupled to a model.

This pattern applies primarily to business applications built on general-purpose frontier models. Specialized scientific models can be different: the model itself may embody a unique domain capability.

Treat the model as a replaceable reasoning engine

The durable business capability should live in the surrounding system: explicit business rules, authoritative data, tools, workflows, interface contracts, guardrails, evaluation criteria and deterministic processing.

The model reasons within those boundaries. It should not have to invent the boundaries each time it runs.

Consider an application that assesses requests for a refund. A model might interpret a customer’s explanation or identify missing information. Eligibility rules, approval limits and the steps required to issue a refund should be explicit. Replacing the model should not silently change the refund policy.

Use model upgrades as a diagnostic

Ask what happens if today’s model is replaced with a materially better one.

Better reasoning, instruction following, robustness and language quality are welcome. A better model may also handle cases that the previous model could not. Those improvements do not, by themselves, indicate an architectural problem.

The useful question is what changed. Does the system apply the same policy more reliably, or does it start making different policy choices? Does it understand difficult requests better, or does it change which requests deserve priority? Does it follow the workflow more accurately, or invent a different workflow?

Changes in business decisions, priorities or required steps are a reason to investigate hidden model dependence. An upgrade can expose how much application behavior was implicit in the prompt.

Recognize the prompt as hidden architecture

During prototyping, it is tempting to put everything into a prompt: how to interpret situations, what matters, which decisions to make, how to handle exceptions and what actions should follow.

This is fast. Over time, however, it can make business logic difficult to locate, test and govern. Model upgrades then become changes to application behavior, even when nobody intended to change the application.

The model has become both a reasoning engine and an implicit part of the application’s architecture.

Make the dependency explicit

  1. Specify business behavior. Define policies, priorities, decision rights and exception paths so that they can be reviewed independently of a model’s answers.
  2. Move stable logic outside inference. Use ordinary software for calculations, validation and rules that have a determinate answer.
  3. Ground reasoning in authoritative data. Give the model access to the relevant facts rather than relying on its general knowledge to supply business context.
  4. Put model calls behind clear contracts. Define inputs, expected outputs, validation and failure handling. Enforce permissions in the surrounding system.
  5. Keep prompts focused on the reasoning task. Make any policy instructions explicit and testable, and avoid relying on the model to fill gaps in the workflow.
  6. Evaluate across multiple capable models. Test important scenarios, including exceptions, and distinguish improvements in reasoning from changes in intended business behavior.

The aim is substantially stable business behavior when the reasoning engine changes. That still requires evaluation: a common interface cannot guarantee equivalent behavior across models.

Design for better models without depending on them

This pattern follows two broader engineering principles: use AI where ambiguity requires interpretation, and compose the application through explicit software contracts and workflows.

Those principles help determine where AI belongs. Replaceability asks how much the solution should depend on the particular model doing the reasoning.

Make the model easy to swap, but also make it possible to explain what must remain true after the swap. A better model should improve the system’s ability to perform its intended work. It should not be the first place where the intended work becomes defined.

Anti-Pattern: Using AI as a Runtime for Deterministic Workflows

AI can be remarkably effective at turning vague, manual work into software. But that does not mean an AI model should execute the resulting workflow forever.

A common anti-pattern is to use an AI agent as the runtime for stable business logic: reading the same kind of file, rediscovering its structure, applying known mappings, and generating the same kind of output on every run.

The first demo may look impressive. Repeated in production, it becomes expensive, fragile, difficult to test, and hard to govern.

Context

This pattern appears in repeatable work such as file transformation, CSV or Excel processing, data mapping, schema validation, reference-data lookup, and template configuration.

A typical workflow accepts a file and some known context—such as a site, market, customer, or product—then applies established rules, validates the result, and returns a configured output file.

These are primarily workflow and contract problems, not reasoning problems.

The Anti-Pattern

The anti-pattern is using an AI model as the execution engine for deterministic business logic.

Each run asks the model to rediscover the same structure, reinterpret the same rules, inspect the same reference data, and reconstruct the same transformation. The system consumes tokens as if the problem were new, even though the workflow is stable.

Stable, repeatable transformations should be implemented as deterministic workflows, not repeated AI reasoning sessions.

Why It Fails

  • High marginal cost: every run spends tokens on parsing, reasoning, and validation.
  • Poor determinism: the same input may not reliably produce the same output.
  • Hidden business logic: rules live in prompts and examples instead of reviewable code and configuration.
  • Weak validation: correctness depends on model behavior rather than explicit, testable contracts.
  • Limited control: failures are harder to reproduce, debug, audit, and explain.

The deeper problem is that the design confuses discovery with execution. A successful proof of concept is mistaken for a sustainable architecture.

The Better Pattern: Compile the Workflow

Separate design-time AI assistance from runtime execution.

At design time, use AI to:

  • understand the existing manual or spreadsheet-based workflow;
  • identify schemas, mappings, rules, and edge cases;
  • generate parser and transformation code;
  • create validation tests and documentation.

At runtime, use deterministic software to:

  • parse the input;
  • validate required fields and schemas;
  • look up governed reference data;
  • apply known mappings;
  • produce the output and a validation report.

The implementation might be a script, web application, serverless function, workflow step, or agent action. The form is secondary. The important part is deterministic execution behind a stable contract.

Example

Suppose a spreadsheet performs site-specific file configuration.

The wrong solution is an agent that repeatedly reads the spreadsheet, interprets reference files, reasons about the necessary changes, and generates a CSV.

The better solution is to reverse-engineer the logic once, define the input and output schemas, move reference data into governed configuration, implement the transformation in code, and add tests. An agent may still provide the conversational interface, but it should call the deterministic workflow rather than embody its logic.

When Runtime AI Is Appropriate

AI still belongs at runtime when the task requires genuine interpretation:

  • the input is novel or its structure changes constantly;
  • the output requires judgment, summarization, classification, or explanation;
  • the workflow is exploratory and has not stabilized;
  • codifying deterministic rules would cost more than repeatability is worth.

Once a workflow stabilizes, move it from repeated reasoning into deterministic execution.

Detection Signals

You may be using this anti-pattern if people say:

  • “The agent needs to understand the spreadsheet every time.”
  • “It works when I explain it carefully.”
  • “Sometimes it creates the file correctly.”
  • “We need more prompt guardrails.”
  • “It consumes many tokens, but the task is always the same.”

Rule of Thumb

If the same input, reference data, and context should always produce the same output, the runtime should be deterministic.

Use AI to build the machine, not to act as the machine.

The goal is not to spend tokens forever. It is to convert repeated reasoning into reusable capability.

Pattern: Efficient Use of AI for Software Engineering

Why this matters

AI-assisted development is moving to usage-based cost models where:

  • Cost scales with model choice and interaction patterns

  • Long-running sessions and iterative loops increase spend

  • Higher-capability models are significantly more expensive

At the same time, AI enables:

  • Rapid exploration of designs

  • Large-scale code generation

  • Parallel execution of implementation work

Without structure, this leads to:

  • Unpredictable cost

  • Inconsistent quality

  • Unnecessary rework

This pattern defines a structured way of working that maximizes value while controlling cost and complexity.


Core pattern

Use powerful models to define the solution. Use cheaper models to implement the solution.

The goal is to:

  • Concentrate reasoning effort once

  • Avoid repeated re-evaluation

  • Execute implementation in small, independent units


Step 1 — Use AI for solution design

Use the most capable model available to:

  • Explore solution options

  • Evaluate trade-offs

  • Validate architecture

  • Identify risks and edge cases

  • Define architecturally significant requirements (ASRs)

Output:

  • Solution design document

  • Clear constraints and assumptions

  • Agreed direction

This step should remove as much ambiguity as possible.


Step 2 — Make the solution executable

Translate the design into structured work:

  • Break into epics and issues

  • Define scope and expected outcome for each

  • Ensure each unit is:

    • Scoped

    • Unambiguous

    • Testable

Good decomposition is the primary control mechanism.

Well-defined units enable:

  • Predictable AI execution

  • Minimal context per task

  • Independent implementation


Step 3 — Execute with clean context

For each issue:

  • Start with a fresh context

  • Provide only:

    • The issue description

    • Relevant constraints

    • Local code context

Avoid:

  • Long-running chat sessions

  • Accumulated conversation history

  • Repeated "compaction" of context

Treat every task as a clean execution.


Step 4 — Use cheaper models for implementation

Once tasks are well-defined:

  • Use faster, lower-cost models

  • Focus on:

    • Implementation

    • Test generation

    • Applying patterns

If a task requires a high-end model:

The issue is likely underspecified.


Step 5 — Execute in parallel where possible

When issues are independent:

  • Use subagents or worktrees

  • Implement in parallel

  • Rely on:

    • Clear boundaries

    • Well-defined contracts

This enables:

  • Faster delivery

  • Better utilization of AI

  • Consistent results


Step 6 — Avoid long-context degradation

AI performance degrades in extended sessions:

  • Context grows

  • Signal-to-noise decreases

  • Output quality drops

Common anti-pattern:

  • Iterate continuously in one session

  • Compact context

  • Continue

This accumulates errors over time.

Recommended approach:

  • Keep interactions short

  • Reset frequently

  • Reintroduce clean inputs


Step 7 — Store state in artifacts

Do not rely on the model to maintain system state.

State should be captured in:

  • Design documents

  • Specifications

  • Epics and issues

This ensures:

  • Reproducibility

  • Consistency

  • Independence between tasks

The model executes — artifacts define the system.


Step 8 — Keep feedback loops controlled

Even with strong design:

  • Issues will evolve

  • Edge cases will appear

Handle this by:

  • Updating artifacts (not conversations)

  • Refining issues

  • Re-running tasks with clean context


Summary

This pattern enables:

  • Predictable cost

  • Consistent quality

  • Scalable execution

By:

  • Separating reasoning from implementation

  • Minimizing context

  • Structuring work into independent units

  • Executing with clean, repeatable inputs

Solve once. Structure clearly. Execute repeatedly.

Software Architecture Patterns

Introduction

The following is an subset of software architecture patterns, which tend to be referenced when academic discussions around patterns arise. The following are my comments.

Patterns

Application Architecture

Microservice

The microservice pattern comes from domain-driven design where in particular the concept of bounded context came to be the decoupling of services. The post Microservices by Martin Fowler also played a large part in naming this pattern.

Drivers:

  • Loosely coupled with other services – enables a team to work independently the majority of time on their service(s) without being impacted by changes to other services and without affecting other services
  • Independently deployable – enables a team to deploy their service without having to coordinate with other teams
  • Capable of being developed by a small team – essential for high productivity by avoiding the high communication head of large teams
  • The application must be easy to understand and modify
  • You must run multiple instances of the application on multiple machines in order to satisfy scalability and availability requirements
  • You want to take advantage of emerging technologies (frameworks, programming languages, etc)

Problems:

  • Managing dependencies
  • Deployments of the entire system may become complex
  • Developers must implement the inter-service communication mechanism and deal with partial failure
  • Implementing requests that span multiple services is more difficult
  • Testing the interactions between services is more difficult
  • Implementing requests that span multiple services requires careful coordination between the teams

Database

Database per Service

This pattern is basically the natural follow-on to choosing the microservice application architecture pattern, where if a service is to become independant, then it must have it’s own independant data layer.

Drivers:

  • Services must be loosely coupled so that they can be developed, deployed and scaled independently
  • Databases must sometimes be replicated in order to scale
  • Different services have different data storage requirements, like relational database or NoSQL

Problems:

  • How to manage consistency across services
  • Who owns (masters) data?

Messaging

Messaging is a communications pattern which uses asynchronous messaging to replace the synchronous style of request/response used in most REST-style APIs. Most common styles of asynchronous messaging are:

  • Notifications – a sender sends a message a recipient but does not expect a reply. Nor is one sent.
  • Request/asynchronous response – a service sends a request message to a recipient and expects to receive a reply message eventually
  • Publish/subscribe – a service publishes a message to zero or more recipients
  • Publish/asynchronous response – a service publishes a request to one or recipients, some of whom send back a reply

In the following there’s no differentiation between “event”-driven and “data”-driven, as a message will always contain the full message body.

Event Driven with full Messages

Drivers:

  • The complete body of the message must be sent in each event
  • The broker may or may not allow for queries against the messages
  • Extreme loose coupling as the sender is completely decoupled from the receiver

Problems:

  • Subscriber/consumer overload which requires buffering so that events are not lost
  • The broker/mediator must always be available
  • Almost always creates mixed messaging styles with events for downstream and request/response across and upstream which requires carefull planning

Broker

Event driven messaging with a broker implies that all messages are delivered through a central broker but that there’s no processing control flow and messages are delivered using a publish/subscribe pattern.

Drivers:

  • Scalability

Problems:

  • How to handle orchestration? With “normal” synchronous request/response messaging services orchestrate business processes in the order that they are called – with broker driven messaging everybody potentially could get the same message at the same time, so who orchestrates the overall process?

Mediater

A mediater expands on the broker with support for business process workflows usually with support for BPEL.

Drivers:

  • Business processes change often
  • Traceability and governance is important

Problems:

  • Management and governance of platform takes time
  • Requires turnkey solutions from major vendors
  • Not really available as a cloud solution

Event Driven with Notifications

Drivers:

  • You don’t know how many subscribers/consumers of your events there will be, so you must preserve bandwidth
  • The size of the payload in the event is limited
  • A subset of the events doesn’t require the complete body of the message to be sent
  • Subscribers explicitly want to chose which message bodies they want to pull

Problems:

  • Requires a two-step dance where the consumer of an event must issue a request to the central broker and ask for the full body of the message

Requirements Engineering

Imagine that the sum of the business requirements for a system can be represented by the surface area of a square where the length of the side is 1 meter. Then the surface area is 1 square meter (m2).

Now image also that the shape of the system that can solve those business requirements is a circle and that we have to construct a circle with the surface area of 1 m2. Knowing that the area of a circle is Pi * r^2 then the radius is the square root of 1/Pi which is approximately 0.564 making the diameter 1.128. So we can draw the square and circle as follows:

Notice that when the figures are overlapped one doesn’t cover the other even if they have the same surface area.

So the implementation of the system which can solve the business requirements – but in a slightly different way. To turn the circle into a square custom implementations will have to be done and they are a lot more costly than a standard system.

I don’t have any statistical evidence but in my experience you can usually get 80% of the way with a standard system, but getting to 100% will cost you a lot more.

The Four Tenets of SOA

The SOA tenets originally appeared back in 2004 when  Don Box published an article on MSDN called “A Guide to developing and Running Connected Systems with Indigo” (Indigo is what’s known today as Windows Communication Foundation or WCF for short). Don Box wrote that WCF is based on SOA principles and that unlike other approaches, specifically object orientation, SOA requires a different set of assumptions:

In Indigo, a service is simply a program that one interacts with via message exchanges. A set of deployed services is a system. Individual services are built to last—the availability and stability of a given service is critical. The aggregate system of services is built to allow for change—the system must adapt to the presence of new services that appear a long time after the original services and clients have been deployed, and these must not break functionality.

Although Microsoft have pledged to keep the MSDN Magazine online at the time of writing the article linked above is not available.

Service-oriented development is based on the four fundamental tenets that follow:

  • Boundaries are explicit 
  • Services are autonomous
  • Services share schema and contract, not class
  • Service compatibility is determined based on policy 

Let’s go over what that means in terms of modern REST services.

Four Tenets

Boundaries are explicit

Services interact by sending messages across boundaries. These boundaries are formal and explicit. No assumptions are made about what is behind boundaries, and this preserves flexibility in how services are implemented and deployed.

This means that:

  • You must treat all services as external to you
  • Internal (private) implementation details should not be leaked outside of a service boundary
  • Avoid RPC interfaces because this can lead to an overuse of calls – accessing a service is not the same as accessing a local object

Services are autonomous

Services are not subservient to other code: a service reacts to a message – how that message was created and what will happen to any response the service creates is immaterial to the action that this service will take.

This means that:

  • Deploy and version services independently from the clients
  • Design contracts with the assumption that once published, they can’t be modified

Services share schema and contract, not class

Only messages pass from service to service, code does not.

This means that:

  • Contracts should be designed to be as explicit as possible to minimize misinterpretation
  • A service must be able to convert its native data types to and from some technology-neutral representation
  • The contract must be versioned using semantic versioning

Service compatibility is determined based on policy

A service must be able to express in a standard representation of policy what it does and how clients should communicate with it.

This means that:

  • The policy must be exposed using an Open API Specification

© 2026 Peter Birkholm-Buch

Theme by Anders Noren — Up ↑