Status: Active Last Updated: October 4, 2025 Author: Piper Morgan Team
Piper Morgan’s plugin system uses the Adapter/Wrapper pattern where plugins are thin wrappers (~100 lines) around integration routers that contain business logic.
graph TD
A[Plugin Wrapper] -->|wraps| B[Integration Router]
B -->|uses| C[Config Service]
A -->|registers with| D[PluginRegistry]
D -->|discovers| A
D -->|initializes| A
Layers:
Each integration follows this pattern:
services/integrations/[name]/
├── __init__.py
├── [name]_integration_router.py # Business logic (~300-500 lines)
├── [name]_plugin.py # Thin wrapper (~100 lines)
├── config_service.py # Config management (~150 lines)
└── tests/
Advantages:
Disadvantages:
Router (slack_integration_router.py):
class SlackIntegrationRouter:
"""Business logic for Slack integration"""
def __init__(self, config_service: SlackConfigService):
self.config = config_service
self.router = APIRouter(prefix="/api/integrations/slack")
self._setup_routes()
def _setup_routes(self):
@self.router.post("/webhook")
async def handle_webhook(request: Request):
# Business logic here
pass
Plugin (slack_plugin.py):
class SlackPlugin(PiperPlugin):
"""Thin wrapper implementing PiperPlugin interface"""
def __init__(self):
self.config_service = SlackConfigService()
self.router_instance = SlackIntegrationRouter(self.config_service)
def get_router(self) -> APIRouter:
return self.router_instance.router
# Other PiperPlugin interface methods...
Use this pattern when:
Don’t use this pattern for:
All plugins must specify a version using Semantic Versioning:
def get_metadata(self) -> PluginMetadata:
return PluginMetadata(
name="your_integration",
version="1.0.0", # MAJOR.MINOR.PATCH
# ...
)
See Plugin Versioning Policy for details on when to increment versions.
If future needs require moving business logic into plugins:
This migration is intentionally easy - the wrapper pattern supports it naturally.
This pattern was established in GREAT-3A (October 2025) and documented in GREAT-3C.