M
MeshWorld.
AI Agents Skills Rules Architecture Fundamentals 7 min read

Agents vs Skills vs Rules: Understanding AI System Layers

Vishnu
By Vishnu
| Updated: Mar 21, 2026

Agents vs Skills vs Rules: Understanding AI System Layers

Building effective AI systems requires understanding three distinct layers that work together: Agents (the thinkers), Skills (the doers), and Rules (the guardrails). Confusing these layers leads to brittle, hard-to-maintain systems.

The Three-Layer Architecture

┌─────────────────────────────────────┐
│              AGENTS                  │  ← The Thinkers
│  • Planning & Decision Making       │
│  • Coordination & Orchestration     │
│  • Context Management               │
├─────────────────────────────────────┤
│              SKILLS                  │  ← The Doers
│  • Capabilities & Actions           │
│  • API Integrations                 │
│  • Data Processing                  │
├─────────────────────────────────────┤
│              RULES                   │  ← The Guardrails
│  • Safety & Security                │
│  • Business Logic                   │
│  • Compliance & Governance          │
└─────────────────────────────────────┘

Each layer has a specific responsibility and shouldn’t overlap with others.

Agents: The Orchestration Layer

What agents do:

  • Receive user requests and understand intent
  • Plan multi-step workflows
  • Decide which skills to use and when
  • Handle errors and retry logic
  • Manage conversation context
  • Coordinate multiple skills together

What agents DON’T do:

  • Implement actual business logic (that’s skills)
  • Enforce security policies (that’s rules)
  • Directly access external APIs (through skills only)

Agent Example

class EmailAgent:
    def __init__(self):
        self.skills = {
            "search_emails": EmailSearchSkill(),
            "compose_email": ComposeSkill(),
            "send_email": SendSkill()
        }
        self.rules = EmailRules()
    
    async def handle_request(self, user_input):
        # Agent thinking/planning
        if "find emails from" in user_input:
            return await self.skills["search_emails"].search(user_input)
        elif "send email to" in user_input:
            # Check rules before acting
            if self.rules.can_send_to(user_input.recipient):
                return await self.skills["send_email"].send(user_input)
            else:
                return "Cannot send: Security policy violation"

Skills: The Capability Layer

What skills do:

  • Implement specific business capabilities
  • Interface with external systems (APIs, databases)
  • Process and transform data
  • Execute domain-specific logic
  • Return structured results

What skills DON’T do:

  • Make decisions about which actions to take
  • Handle user interaction directly
  • Enforce business rules (they can check rules, but not define them)

Skill Example

class SendEmailSkill:
    def __init__(self):
        self.email_client = EmailClient()
        self.rules = EmailRules()  # Can reference rules
    
    async def send(self, to, subject, body):
        # Skill implementation
        try:
            # Reference rules for validation
            if not self.rules.is_valid_email(to):
                return {"success": False, "error": "Invalid recipient"}
            
            result = await self.email_client.send(to, subject, body)
            return {"success": True, "message_id": result.id}
        except Exception as e:
            return {"success": False, "error": str(e)}

Rules: The Governance Layer

What rules do:

  • Define security policies and constraints
  • Enforce business logic and compliance
  • Validate inputs and outputs
  • Set rate limits and quotas
  • Audit and log actions
  • Handle authorization and permissions

What rules DON’T do:

  • Implement any business logic
  • Make decisions about workflow
  • Directly interact with external systems

Rules Example

class EmailRules:
    def __init__(self):
        self.blocked_domains = ["spam.com", "malicious.net"]
        self.rate_limits = {"hourly": 100, "daily": 1000}
        self.usage_log = []
    
    def can_send_to(self, recipient):
        # Rule: Check blocked domains
        domain = recipient.split("@")[1]
        if domain in self.blocked_domains:
            return False
        
        # Rule: Check rate limits
        if self.check_rate_limit():
            return False
        
        # Rule: Log for audit
        self.log_action("send_attempt", recipient)
        return True
    
    def is_valid_email(self, email):
        # Rule: Email format validation
        return "@" in email and "." in email.split("@")[1]

Why Separation Matters

1. Reusability

# Skills can be reused across agents
email_skill = SendEmailSkill()
customer_agent = CustomerServiceAgent(email_skill)
marketing_agent = MarketingAgent(email_skill)

2. Testability

# Each layer can be tested independently
def test_skill():
    skill = SendEmailSkill()
    result = skill.send("[email protected]", "Subject", "Body")
    assert result["success"] == True

def test_rules():
    rules = EmailRules()
    assert rules.can_send_to("[email protected]") == True
    assert rules.can_send_to("[email protected]") == False

3. Maintainability

  • Bug in email sending? Fix the skill, not the agent
  • New security policy? Update rules, not skills
  • Change workflow logic? Modify agent, not skills

4. Security

  • Skills can’t bypass security (rules enforce it)
  • Agents can’t directly access external systems
  • Clear audit trail through rule layer

Common Anti-Patterns

❌ Mixing Responsibilities

# BAD: Agent implementing business logic
class BadAgent:
    async def send_email(self, to, subject):
        # Agent shouldn't implement email logic
        if "spam" in subject.lower():
            return "Cannot send spam"  # This is a rule!
        
        # Agent shouldn't call APIs directly
        smtp = SMTPClient()
        return smtp.send(to, subject)  # This is a skill!

✅ Proper Separation

# GOOD: Clear separation of concerns
class GoodAgent:
    def __init__(self):
        self.skills = {"email": SendEmailSkill()}
        self.rules = EmailRules()
    
    async def handle_request(self, request):
        if self.rules.validate(request):
            return await self.skills["email"].send(request)
        else:
            return "Request blocked by policy"

Interaction Patterns

Pattern 1: Agent → Skill → Rules

Agent decides to send email

Skill implements email sending

Skill checks rules before sending

Rules validate and allow/deny

Pattern 2: Agent → Rules → Skill

Agent wants to send email

Agent checks rules first

Rules validate the request

If allowed, agent calls skill

Pattern 3: Skill → Rules (Internal Validation)

Skill receives request from agent

Skill internally validates against rules

Skill executes or rejects based on rules

Real-World Example: Document Processing Agent

# Agent Layer
class DocumentAgent:
    def __init__(self):
        self.skills = {
            "ocr": OCRSkill(),
            "summarize": SummarySkill(),
            "classify": ClassificationSkill(),
            "store": StorageSkill()
        }
        self.rules = DocumentRules()
    
    async def process_document(self, file_path):
        # Agent: Plan the workflow
        if not self.rules.can_process(file_path):
            return "File rejected by policy"
        
        # Agent: Coordinate skills
        text = await self.skills["ocr"].extract(file_path)
        summary = await self.skills["summarize"].create(text)
        category = await self.skills["classify"].determine(text)
        
        # Agent: Store results
        result = await self.skills["store"].save({
            "summary": summary,
            "category": category,
            "original": file_path
        })
        
        return result

# Skill Layer
class OCRSkill:
    async def extract(self, file_path):
        # Skill: Implement OCR logic
        ocr_service = OCRService()
        return await ocr_service.extract_text(file_path)

# Rules Layer
class DocumentRules:
    def __init__(self):
        self.allowed_formats = ["pdf", "docx", "jpg"]
        self.max_size_mb = 50
    
    def can_process(self, file_path):
        # Rules: Validate file
        if not self.is_allowed_format(file_path):
            return False
        if not self.is_under_size_limit(file_path):
            return False
        return True

Implementation Best Practices

1. Clear Interfaces

# Define interfaces between layers
class AgentInterface:
    async def process(self, request: Request) -> Response:
        pass

class SkillInterface:
    async def execute(self, params: dict) -> Result:
        pass

class RulesInterface:
    def validate(self, action: Action) -> bool:
        pass

2. Dependency Injection

# Inject dependencies, don't create them
class Agent:
    def __init__(self, skills: dict, rules: RulesInterface):
        self.skills = skills
        self.rules = rules

3. Configuration-Driven Rules

# rules.yaml
email:
  blocked_domains: ["spam.com"]
  rate_limits:
    hourly: 100
    daily: 1000
  allowed_senders: ["@company.com"]

4. Observability

# Log at each layer
class Skill:
    async def execute(self, params):
        logger.info(f"Skill executing: {self.__class__.__name__}")
        result = await self._do_work(params)
        logger.info(f"Skill completed: {result}")
        return result

Testing Strategy

Unit Tests (Per Layer)

# Test agent logic
def test_agent_planning():
    agent = Agent(mock_skills, mock_rules)
    plan = agent.plan_workflow("send email to user")
    assert plan.steps == ["validate", "compose", "send"]

# Test skill implementation
def test_skill_execution():
    skill = EmailSkill()
    result = skill.send("[email protected]", "Subject", "Body")
    assert result.success == True

# Test rule validation
def test_rule_validation():
    rules = EmailRules()
    assert rules.can_send_to("[email protected]") == True
    assert rules.can_send_to("[email protected]") == False

Integration Tests (Cross-Layer)

def test_agent_to_skill_integration():
    # Test agent correctly calls skill
    agent = Agent(real_skills, mock_rules)
    result = agent.handle_request("send email to [email protected]")
    assert "message_id" in result

Evolution of the Architecture

As your system grows, you might add:

Additional Layers

  • Monitoring Layer: Track performance, errors, usage
  • Caching Layer: Improve performance with smart caching
  • Security Layer: Advanced threat detection and response

Advanced Patterns

  • Skill Composition: Combine multiple skills into meta-skills
  • Rule Hierarchies: Global, team, and individual rule sets
  • Agent Networks: Multiple agents coordinating across systems

Key Takeaway: The separation of agents, skills, and rules isn’t just architecture — it’s about creating maintainable, secure, and scalable AI systems. Each layer has a clear responsibility, and respecting those boundaries is key to success.

Next: Learn about common architecture patterns for implementing these layers effectively.