RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Custom Agent Frameworks
  6. /Ch. 22
Custom Agent Frameworks

22. Plugin Architecture

Chapter 22 of 24 · 20 min
KEY INSIGHT

Plugins are untrusted code with your privileges. Sandbox them from day one. Resource limits, hook auditing, and version pinning prevent plugins from becoming attack vectors.

Plugins let users extend your framework without modifying core code. Built well, they enable customization. Built poorly, they create maintenance nightmares.

Plugin Interface Definition

Define clear contracts:

from abc import ABC, abstractmethod
from typing import Any, Protocol

class AgentPlugin(Protocol):
    """Protocol that all plugins must implement."""
    
    @property
    def name(self) -> str:
        """Unique identifier for this plugin."""
        ...
    
    @property
    def version(self) -> str:
        """Semantic version string."""
        ...
    
    async def initialize(self, config: dict[str, Any]) -> None:
        """Called once when plugin is loaded."""
        ...
    
    async def process(self, input_data: Any) -> Any:
        """Process input and return output."""
        ...

Plugin Lifecycle

class PluginManager:
    def __init__(self):
        self._plugins: dict[str, AgentPlugin] = {}
        self._hooks: dict[str, list[callable]] = {
            "pre_process": [],
            "post_process": [],
        }
    
    def register(self, plugin: AgentPlugin):
        if plugin.name in self._plugins:
            raise ValueError(f"Plugin {plugin.name} already registered")
        
        self._plugins[plugin.name] = plugin
        # Run plugin initialization
        asyncio.create_task(plugin.initialize({}))
    
    def register_hook(self, hook_name: str, callback: callable):
        if hook_name not in self._hooks:
            raise ValueError(f"Unknown hook: {hook_name}")
        self._hooks[hook_name].append(callback)
    
    async def run_hooks(self, hook_name: str, *args, **kwargs):
        for callback in self._hooks.get(hook_name, []):
            await callback(*args, **kwargs)

Sandboxing Plugins

Plugins run with the same privileges as your framework unless sandboxed:

import sys
import tracemalloc

class SandboxedPlugin:
    """Wrapper that monitors plugin resource usage."""
    
    def __init__(self, plugin: AgentPlugin, memory_limit_mb: int = 512):
        self.plugin = plugin
        self.memory_limit = memory_limit_mb * 1024 * 1024
    
    async def process(self, input_data: Any) -> Any:
        tracemalloc.start()
        try:
            result = await self.plugin.process(input_data)
            
            current, peak = tracemalloc.get_traced_memory()
            if peak > self.memory_limit:
                raise RuntimeError(
                    f"Plugin {self.plugin.name} exceeded memory limit: "
                    f"{peak / 1024 / 1024:.1f}MB > {self.memory_limit / 1024 / 1024}MB"
                )
            return result
        finally:
            tracemalloc.stop()

Plugin Loading

import importlib

def load_plugin_from_module(module_path: str) -> AgentPlugin:
    """Load a plugin from a dotted module path."""
    module = importlib.import_module(module_path)
    
    # Find the plugin class (convention: class name ends with Plugin)
    for name in dir(module):
        obj = getattr(module, name)
        if isinstance(obj, type) and name.endswith('Plugin'):
            return obj()
    
    raise ValueError(f"No Plugin class found in {module_path}")
EXERCISE

Implement a plugin that intercepts all agent messages and logs them. Add memory and CPU limits. Verify that a misbehaving plugin can't crash the host process.

← Chapter 21
Package Distribution
Chapter 23 →
Custom Framework