CAChandrakanth AnnePDF

AI agents & software architecture

SOLID Was Written for Classes. It Works Even Better for AI Agents.

Applying five software design principles to agent responsibilities, tools, model contracts and governance

SOLID for AI agents: one job per agent, extensible tools, model contracts, least-privilege access and a model gateway

Abstract

Applying SOLID principles to enterprise AI agents: focused responsibilities, extensible tools, evaluation-based model substitution, least-privilege access and vendor-neutral model gateways. Examples from billing investigation and retrieval workflows show how to keep agents testable and govern their actions.

SOLID is more than 20 years old. Robert C. Martin's five principles were written for classes in object-oriented code, and most developers can recite the acronym in their sleep.

Recently I've been designing AI agent workflows for enterprise systems, including one that helps analysts investigate billing discrepancies. The most useful design guide I found wasn't a new agent framework. It was SOLID.

The reason is simple. Agents fail in exactly the ways SOLID was designed to prevent:

  • They try to do too much.
  • They get more access than they need.
  • They break when something underneath them changes.
  • They're tied to one vendor's quirks.

Most agent failures I've seen aren't model failures. They're design failures.

Here's what each principle looks like when the component is an AI agent instead of a class.

  • S: one job per agent
  • O: add tools without rewriting the agent
  • L: a replacement model must pass the same tests
  • I: no agent gets tools it doesn't need
  • D: depend on a model gateway, not a model vendor

S: One job per agent

Single responsibility says a class should have one reason to change. For an agent the test is even simpler: can you describe its job in one sentence without using the word "and"?

The most common anti-pattern I see is the do-everything agent. One long system prompt triages requests, queries three systems, drafts replies and takes actions. It works in the demo. Then someone tweaks the prompt to improve the replies, and triage quietly gets worse. Nobody can test one behaviour without retesting all of them.

Split agents by responsibility instead:

  • A coordinator that understands the request and routes it.
  • Specialist agents with narrow instructions and a small set of tools.
  • Plain code for every step that doesn't need judgment. Not everything needs to be an agent.

The most important split is between deciding and doing. In the billing discrepancy workflow I designed, the AI investigates and explains what went wrong. It never applies the financial adjustment. That's a separate step, and a human approves it. Investigation and action change for different reasons and carry different risks, so they are different responsibilities.

O: Add tools without rewriting the agent

Open/closed says software should be open for extension but closed for modification. For an agent, the core loop should stay stable: read the request, reason, call tools, respond. What changes over time is what the agent can reach.

The anti-pattern is hard-wiring every integration into the agent: custom code for each system, plus paragraphs about each one pasted into the system prompt. Every new system means editing and retesting the core agent.

The better pattern treats tools as plug-ins behind a standard interface. The Model Context Protocol (MCP) is the clearest example today. You expose a new system as an MCP server with its own tool descriptions, and a compatible agent can use it without its own code changing. Instructions can work the same way: loaded when a task needs them, instead of living permanently in one giant prompt.

One caution: extension isn't free. Every tool an agent can see competes for its attention. That's why open/closed needs its partner, interface segregation, which comes up shortly.

L: Swap models without breaking behaviour

Liskov substitution says a subtype must work anywhere its parent does, without surprising the caller. In an agent system, the "subtype" is the next model: a newer version, a cheaper tier or a different provider.

Models change constantly. Providers release new versions and retire old ones. If your agent breaks when the model changes, it was depending on one model's quirks, not on a contract.

So write the contract down:

  • Structured outputs: define the response as a schema, and validate every response against it.
  • Behavioural evals: a fixed set of real cases with expected outcomes. This is your substitution test. A model that passes is substitutable. A model that fails isn't, however good its benchmark scores look.
  • Failure behaviour: what the agent does when it's unsure (ask, escalate or stop) is part of the contract too.

In an earlier article I wrote about model selection as tiering: sending simple work to smaller, cheaper models and hard work to stronger ones. Tiering only works if the models are substitutable behind the same contract.

Evals are the Liskov test for AI.

I: Only the tools it needs

Interface segregation says no client should depend on methods it doesn't use. For an agent, the interface is its tool list, and this principle matters for two reasons.

Accuracy. In practice, the more tools an agent can see, the more often it picks the wrong one. More of its context also goes on tool descriptions instead of the actual task.

Security. Every tool is attack surface. Agents read untrusted content: emails, web pages, uploaded files. A poisoned document can carry hidden instructions, which is prompt injection. If the agent reading that document also holds a "send email" or "delete record" tool, an injected instruction becomes an action.

So segregate:

  • Give each agent an explicit allow-list of tools.
  • Separate read tools from write tools, and keep write tools away from agents that read untrusted content.
  • Scope credentials per agent. Let agents act with the user's permissions, never a super-user's.

The RAG assistant I designed follows this rule. Its access controls follow document permissions, so it can only retrieve what the person asking is allowed to see.

In agent design, interface segregation and least privilege are the same principle.

D: Depend on a model gateway, not a vendor

Dependency inversion says high-level policy shouldn't depend on low-level details; both should depend on abstractions. In an agent system, the model is a low-level detail, however impressive it is.

The anti-pattern: business logic calling one provider's SDK directly, vendor-specific prompt tricks scattered through the codebase, and tools written for a single agent framework. Switching providers then means a rewrite.

Invert it:

  • A model gateway you own. One internal interface for generating text, producing structured output and creating embeddings, with an adapter for each provider.
  • Prompts as versioned assets, stored and reviewed like code, not buried in string literals.
  • Tools behind a standard protocol such as MCP, so they outlive any one agent framework.
  • Rules in code you own. Approval limits, policies and guardrails belong to the application, which hands them to the agent. The agent never decides its own guardrails.

At Citi I helped design a vendor-neutral backup and recovery platform: one self-service experience across several backup products, so the bank was never locked into a single vendor. The same idea applies here.

The model is a dependency. Treat it like one.

Where SOLID can mislead you

SOLID was never a reason to add complexity, and that's even more true with agents.

  • Too many tiny agents. Every hand-off between agents adds latency, cost and a new place for context to get lost. Start with one well-designed agent and good tools. Split it when one reason to change has really become two.
  • Agents where code would do. A fixed workflow with one model call in the middle is often simpler, cheaper and more reliable than an autonomous agent. Use judgment where judgment is needed.
  • Abstractions that hide real differences. Models differ in context size, tool-use quality and cost. A good gateway exposes those differences so you can route work sensibly. It doesn't pretend all models are the same.

Five questions for your next agent design review

  1. S: Can I describe this agent's job in one sentence without "and"?
  2. O: Can I add a new tool or data source without editing the agent's core prompt or code?
  3. L: Is there an eval suite that any replacement model must pass before it goes live?
  4. I: Does each agent have only the tools and permissions it needs? Can an agent that reads untrusted content take an action that can't be undone?
  5. D: Could we switch model providers in days rather than months?

And one rule SOLID never needed, because classes don't improvise: a human approves anything that can't be undone.

SOLID has found a new kind of component

SOLID isn't outdated. The teams that succeed with agents won't be the ones with the cleverest prompts. They'll be the ones whose agents are easy to change, test, swap and trust.

Which principle do you see broken most often in agent systems? I'd like to hear your examples in the comments.