Chapter 9

SOLID Principles for Agent Systems

Why Fifty-Year-Old Rules Still Apply

Part IV — The New Architecture 8 sections

SOLID is a set of five principles for writing maintainable object-oriented software: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. It was formalized in the 1990s for Java and C#. It completely applies to agentic systems — not by analogy, but structurally.

The reason is simple: SOLID was never really about classes. It was about managing complexity and change in systems that have multiple components with different concerns. Agentic systems have all of that and more.


9.1   Why SOLID Matters for Agents

The failure trajectory of an agentic system without SOLID is predictable. Week 1: you build a working agent. Week 3: you need to add a new capability, so you extend the system prompt. Week 6: the prompt is 8,000 tokens. Behavior regresses when you make changes. Evals start failing mysteriously. You cannot tell which part of the prompt caused a bug. Week 10: you have a God Prompt, context rot, and a test suite that is effectively useless. SOLID exists to break that trajectory before it starts.


9.2   S — Single Responsibility Principle

"A class should have only one reason to change."

Applied to agents: an agent should have only one clearly defined job. An agent that handles customer service enquiries, generates marketing copy, performs data analysis, and translates documents is not a customer service agent — it is a God Prompt with a name. Each of those is a separate reason to change the prompt, and changes to one concern will degrade others through context dilution.

Fig 9.1 — Single Responsibility Principle: Before and After Decomposition
Before: SRP Violation MonolithicAnalystAgent 📊 Fetch and clean data 📈 Perform statistical analysis ✍️ Write narrative commentary 📑 Format and generate PDF 📧 Send email with report ⚠ 5 responsibilities = 5 failure modes decompose → single role After: SRP Applied DataAgent Fetch + clean only AnalysisAgent Statistics only WriterAgent Commentary only FormatterAgent PDF generation only DeliveryAgent Send email only Orchestrator Sequences agents Passes outputs ✓ Each agent testable alone ✓ Single failure = single cause ✓ Replace one without touching others

A monolithic agent fails for five different reasons and none of them produce a clear error signal. Decomposition by responsibility makes each failure localised, diagnosable, and fixable in isolation.

SRP — Decomposing a Monolithic Pipeline
# BEFORE — Single agent with multiple responsibilities
god_prompt = """
You are a customer service agent. Handle orders, refunds,
shipping inquiries, complaints, and general FAQs. For orders,
check status. For refunds, verify eligibility based on 30-day
policy. For shipping, check carrier API. For complaints...
[continues for 4,000 more tokens]
"""

# AFTER — Pipeline: each agent has one job
order_classifier    → classifies the intent
refund_specialist   → handles refund logic only
shipping_specialist → handles shipping queries only
escalation_handler  → handles complaints / complex issues
faq_responder       → handles general questions

9.3   O — Open/Closed Principle

"Open for extension, closed for modification."

Applied to agents: the system prompt is closed for modification once validated; new capabilities should be added through tool injection (Chapter 8), not by editing the core prompt. Every time you edit a working system prompt, you risk introducing regressions in all the behaviors that were already working. The Open/Closed approach keeps the prompt stable and extends capability through the tool interface.

OCP — Extending via Tools, Not Prompt Modification
# CLOSED: This system prompt is tested and validated. Do not modify.
SYSTEM_PROMPT = """
You are a customer service agent. When a user makes a request,
use the tools provided to find an answer. Tools define what you 
can do — do not speculate about capabilities you don't have tools for.
"""

# OPEN for extension: add new tools without touching the prompt
v1_tools = [get_order_status]
v2_tools = [get_order_status, process_refund]      # New capability added
v3_tools = [get_order_status, process_refund,       # Another extension
            get_shipping_status]

9.4   L — Liskov Substitution Principle

"Subtypes must be substitutable for their base types."

Applied to agents: if you design your prompts to be model-agnostic — avoiding formatting that only works on one model, avoiding reliance on specific model behaviors — then any model that supports your tool schema is a valid substitution. This is what enables model hot-swapping (Chapter 8). The key design rule: format your prompts for the interface, not for the implementation.

LSP — Model-Agnostic Prompt Design
# ANTI-PATTERN: Relies on GPT-4's specific behavior for *** delimiters
prompt = """
Use *** to start and end each section of your response.
***Summary***
[your summary here]
***Recommendations***
[your recommendations here]
"""

# LSP-COMPLIANT: Uses explicit XML tags that work across models
prompt = """
Structure your response as:
<summary>A 2-3 sentence overview of the situation.</summary>
<recommendations>A numbered list of 3-5 actionable recommendations.</recommendations>
"""

9.5   I — Interface Segregation Principle

"Clients should not be forced to depend on interfaces they do not use."

Applied to agents: do not inject tools an agent does not need. A tool is a claim on context Window attention. Injecting 20 tools into an agent that will only ever call 2 of them pollutes the context, increases hallucination risk (the model may invoke an irrelevant tool), and increases cost.

Fig 9.2 — Interface Segregation Principle: Tool Bloat vs. Tool Segregation
Tool Bloat (ISP Violation) DatabaseTool (monolithic) read_table() write_row() delete_row() ← read-only agents get this too drop_table() ← read-only agents get this too run_migration() bulk_import() All agents carry ALL methods → security risk + context cost Tool Segregation (ISP Applied) ReadOnlyDB read_table() query_filter() get_schema() WriteDB write_row() upsert() bulk_import() AdminDB delete_row() drop_table() run_migration() ✓ Inject only the interface role needs ReadOnly agent gets only ReadOnlyDB

Applied to agents: no agent should be given a tool it doesn't require. Segregated tool interfaces save context tokens and enforce security boundaries that a monolithic tool cannot provide.

ISP — Intent-Scoped Tool Injection
INTENT_TO_TOOLS = {
    "order_status":  [get_order_status, get_estimated_delivery],
    "process_refund":[get_order_status, check_refund_eligibility,
                      apply_refund, send_confirmation_email],
    "shipping_query":[get_shipping_status, get_carrier_info],
    "general_faq":   [search_knowledge_base],
}

def get_tools_for_intent(intent: str) -> list:
    return INTENT_TO_TOOLS.get(intent, [search_knowledge_base])

9.6   D — Dependency Inversion Principle

"Depend on abstractions, not concretions."

Applied to agents: the agent prompt depends on the tool schema — the abstraction — not on the underlying implementation. The system prompt describes what the tool does, in general terms. Whether that description maps to a Google Search API, a Bing Search API, or a custom internal search engine is invisible to the agent. The prompt is not coupled to the implementation.

DIP — Decoupled Tool Schema
# The tool schema is the abstraction the agent depends on
SEARCH_TOOL_SCHEMA = {
    "name": "search_web",
    "description": "Search the web for current information on a topic.",
    "parameters": {
        "query": {"type": "string", "description": "The search query"}
    }
}

# IMPLEMENTATION A — Google (production)
def google_search_impl(query: str) -> str:
    return google_api.search(query)

# IMPLEMENTATION B — Bing (fallback)
def bing_search_impl(query: str) -> str:
    return bing_api.search(query)

# IMPLEMENTATION C — Mock (testing)
def mock_search_impl(query: str) -> str:
    return GOLDEN_DATASET_RESPONSES.get(query, "")

9.7   SOLID Violations in the Wild

PrincipleCommon Agentic ViolationConsequence
SRPOne agent handles 12 different service categoriesContext rot; evals break unpredictably on changes
OCPAdding capability by editing the validated system promptRegressions in previously working behavior
LSPPrompts use GPT-4-specific markdown formattingModel migration requires full rewrite and re-eval
ISPInjecting all 20 tools into every agent callHallucinated tool calls; increased cost; reduced accuracy
DIPAPI key and endpoint hardcoded in system promptSecurity exposure; unable to change implementation without re-testing prompt

9.8   Chapter Summary

SOLID is not a checklist for Java developers. It is a framework for reasoning about change and complexity in systems composed of multiple components with different concerns. Agentic systems — with their prompts, models, tools, evaluations, and orchestrators — exhibit all of these properties. The patterns that worked in object-oriented systems work here too: for the same reasons, in the same ways, with the same payoffs.

Core Principle — Chapter 9

SOLID principles are not rules about Java. They are rules about managing complexity and change. Agentic systems are not exempt from either — they are uniquely vulnerable to both.