Plugin Architecture: Making Korben Truly Extensible
The Extensibility Problem
When we first built Korben, adding new functionality required editing multiple files:
- Create your task implementation
- Import it in
registry.py - Add it to the
TASKSdictionary - Remember to do this for flows too
- Update documentation
Every. Single. Time.
This worked fine for the core team, but it created friction for:
- Contributors - Making a simple addition required understanding the registry system
- Personal extensions - Your custom tasks got mixed with core code
- Maintenance - Import statements and dictionaries grew unwieldy
- Discoverability - No way to see what’s available without reading
registry.py
We needed something better. Something that let you just write code and go.
Enter: Auto-Discovery Plugins
The new plugin architecture is dead simple:
- Create a folder in
src/plugins/ - Write a
tasks.pyorflows.pyfile - Done. Your functions are automatically discovered and available.
No imports. No registry edits. No configuration files. It just works.
Example: Adding a Weather Plugin
Let’s say you want to add weather forecasts to Korben. Here’s the entire process:
# Create plugin directory
mkdir -p src/plugins/weather
# Create tasks.py
cat > src/plugins/weather/tasks.py << 'EOF'
"""Weather plugin tasks."""
import requests
def get_forecast(**kwargs):
"""Get weather forecast for a location."""
location = kwargs.get('location', 'San Francisco')
api_key = os.environ.get('WEATHER_API_KEY')
response = requests.get(
f"https://api.weather.com/forecast/{location}",
headers={'Authorization': f'Bearer {api_key}'}
)
data = response.json()
return f"Weather for {location}: {data['temp']}°F, {data['conditions']}"
def _api_helper(endpoint):
"""Private helper - not registered (starts with _)."""
pass
EOF
That’s it! Now run:
python korben.py --list
# Shows: get_forecast (automatically discovered!)
python korben.py --task get_forecast --location "Paris"
# Weather for Paris: 18°C, Partly Cloudy
No registry edits. No imports. Just code.
How It Works: Convention Over Configuration
The auto-discovery system uses conventions instead of configuration:
File Structure Convention
src/plugins/
└── weather/
├── __init__.py # Plugin marker (can be empty)
├── tasks.py # ✅ All public functions → tasks
├── flows.py # ✅ All public functions → flows
├── lib.py # Helper code (not registered)
├── config.yml.example # Configuration template
└── README.md # Documentation
Naming Conventions
- ✅ Public functions (no
_prefix) → Registered automatically - ✅ Flow names ending in
_workflow→ Suffix removed (e.g.,daily_weather_workflow→daily_weather) - ❌ Private functions (starting with
_) → Not registered - ❌ Files other than
tasks.pyandflows.py→ Not scanned
Discovery Process
At startup, Korben automatically:
- Scans
src/plugins/for directories with__init__.py - Imports
tasks.pyandflows.pyfrom each plugin - Introspects to find all public functions
- Registers them in global
TASKSandFLOWSdictionaries - Validates plugin dependencies (more on this below)
All of this happens transparently - you just write code.
Building Flows with Plugins
Let’s extend our weather plugin with a flow:
# src/plugins/weather/flows.py
"""Weather plugin flows."""
import controlflow as cf
from src.plugins.weather import tasks as weather_tasks
from src.plugins.utilities import tasks as utility_tasks
@cf.flow
def daily_weather_workflow(**kwargs):
"""Daily weather report flow.
Auto-registered as 'daily_weather' (removes _workflow suffix).
"""
# Get forecast
forecast = weather_tasks.get_forecast(**kwargs)
# Format as HTML
html = utility_tasks.markdown_to_html(text=f"# Daily Weather\n\n{forecast}")
# Email it
utility_tasks.send_email(
subject="Daily Weather Report",
content=html,
**kwargs
)
return forecast
Now you can run:
python korben.py --flow daily_weather
# Automatically emails you the weather forecast!
The flow composes tasks from multiple plugins (weather + utilities), demonstrating the power of the composable architecture.
Plugin Dependencies
What if your plugin depends on another plugin? Declare it:
# src/plugins/weather/tasks.py
"""Weather plugin tasks."""
# Declare dependencies
__dependencies__ = ['utilities', 'email']
def get_forecast(**kwargs):
# Implementation...
pass
Korben validates dependencies at startup:
- ✅ Dependencies exist → Plugin loads normally
- ❌ Dependencies missing → Plugin disabled with clear warning
- 🔒 Prevents runtime errors → No “module not found” surprises
This ensures your plugin fails fast with helpful error messages, not cryptic runtime failures.
Real-World Example: The Movies Plugin
Let’s look at how the built-in movies plugin is structured:
src/plugins/movies/
├── __init__.py
├── tasks.py # discover_movies, get_movie_details
├── flows.py # trending_movies_workflow, popular_movies_workflow
├── lib.py # TMDB API client
├── config.yml.example # Configuration template
└── README.md # Documentation
tasks.py - Core functionality:
"""Movie discovery tasks."""
from src.plugins.movies.lib import tmdb_client
def discover_movies(**kwargs):
"""Query TMDB for movies matching criteria."""
genres = kwargs.get('genres', [])
min_rating = kwargs.get('min_rating', 7.0)
return tmdb_client.discover(genres=genres, min_rating=min_rating)
flows.py - Workflow orchestration:
"""Movie workflow orchestrations."""
import controlflow as cf
from src.plugins.movies import tasks as movie_tasks
from src.plugins.utilities import tasks as utility_tasks
@cf.flow
def trending_movies_workflow(**kwargs):
"""Find trending movies and email recommendations."""
movies = movie_tasks.discover_movies(**kwargs)
html = format_movies_html(movies)
utility_tasks.send_email(subject="Trending Movies", content=html)
return movies
Everything the plugin needs is self-contained. If you want to remove movies functionality, just delete the movies/ folder. If you want to share it, zip up the folder and send it. Clean separation.
Benefits of the Plugin Architecture
1. Zero Friction for Contributors
Before:
# registry.py
from src.core.tasks import weather_tasks
...
TASKS = {
...
+ "get_forecast": weather_tasks.get_forecast,
}
After:
# Just create src/plugins/weather/tasks.py
# Done!
2. True Pluggability
Plugins are completely self-contained:
- ✅ Add plugin → works immediately
- ✅ Remove plugin → no broken imports
- ✅ Share plugin → just share the folder
- ✅ Version plugin → standard Python packaging
3. Discoverability
python korben.py --list
Shows everything available across all plugins. No need to read code or documentation to discover features.
4. Separation of Concerns
plugins/
├── movies/ # TMDB integration
├── books/ # ISBNdb integration
├── podcasts/ # Audio processing
├── mallory/ # Security news
├── utilities/ # Generic tools
└── your_plugin/ # Your custom stuff
Each plugin has a clear scope. No cross-contamination of concerns.
5. Easier Testing
Each plugin can be tested in isolation:
# tests/plugins/test_weather.py
from src.plugins.weather import tasks
def test_get_forecast():
result = tasks.get_forecast(location="Paris")
assert "Paris" in result
No need to mock the entire registry or worry about other plugins.
Migration: Before and After
Before: Manual Registry
# registry.py - manually maintained
from src.core.tasks import podcast_tasks
from src.core.tasks import email_tasks
from src.core.tasks import utility_tasks
from src.core.flows import podcast_flows
# ... 20+ import lines
TASKS = {
"download_podcasts": podcast_tasks.download_podcasts,
"transcribe_podcasts": podcast_tasks.transcribe_podcasts,
"send_email": email_tasks.send_email,
# ... 30+ manual entries
}
FLOWS = {
"podcasts": podcast_flows.podcast_workflow,
# ... 10+ manual entries
}
After: Auto-Discovery
# registry.py - auto-populated
from src.lib.core_utils import discover_plugins
# That's it! Everything else is automatic
TASKS, FLOWS = discover_plugins('src/plugins/')
The registry shrunk from 100+ lines to 3 lines. Everything else happens automatically.
Best Practices for Plugin Development
1. Self-Contained Design
Everything the plugin needs should live in its directory:
my_plugin/
├── tasks.py # Task implementations
├── flows.py # Workflows
├── lib.py # Plugin-specific helpers
└── README.md # Documentation
2. Clear Naming
Function names become CLI commands:
def get_forecast(**kwargs): # korben.py --task get_forecast
def daily_weather_workflow(): # korben.py --flow daily_weather
Choose names that are clear and action-oriented.
3. Use Private Helpers
Keep the CLI clean by prefixing internal functions:
def get_forecast(**kwargs): # ✅ Public - registered
return _api_call('forecast')
def _api_call(endpoint): # ❌ Private - not registered
return requests.get(endpoint)
4. Document Your Plugin
Include a README.md explaining:
- What the plugin does
- Required environment variables
- Configuration options
- Usage examples
5. Declare Dependencies
Be explicit about what your plugin needs:
__dependencies__ = ['utilities', 'email']
This prevents confusing runtime errors and makes requirements clear.
Building Your First Plugin
Ready to extend Korben? Here’s a template:
# 1. Create plugin directory
mkdir -p src/plugins/my_plugin
# 2. Create __init__.py (can be empty)
touch src/plugins/my_plugin/__init__.py
# 3. Create tasks.py
cat > src/plugins/my_plugin/tasks.py << 'EOF'
"""My plugin tasks."""
def my_task(**kwargs):
"""Task description here."""
# Your implementation
return "Task completed!"
def another_task(**kwargs):
"""Another task."""
# More implementation
return "Done!"
# Private helper - not registered
def _helper_function():
pass
EOF
# 4. Create README.md
cat > src/plugins/my_plugin/README.md << 'EOF'
# My Plugin
Description of what this plugin does.
## Usage
```bash
python korben.py --task my_task
EOF
5. Test it!
python korben.py –list # Should show my_task python korben.py –task my_task
That's all it takes. You just extended Korben!
## What's Next?
The plugin architecture opens up exciting possibilities:
- **Plugin marketplace** - Share plugins with the community
- **Remote plugins** - Load plugins from git URLs
- **Plugin configuration UI** - Visual plugin management
- **Hot reloading** - Update plugins without restarting
- **Plugin versioning** - Dependency resolution for plugin versions
## Try It Yourself
```bash
# Clone Korben
git clone https://github.com/korbenai/korben
cd korben/local/korben
# List all available plugins and tasks
python korben.py --list
# Create your own plugin
mkdir -p src/plugins/demo
echo 'def hello(**kwargs): return "Hello from my plugin!"' > src/plugins/demo/tasks.py
echo '' > src/plugins/demo/__init__.py
# Run it!
python korben.py --task hello
Conclusion
The plugin architecture transformed Korben from a framework with extension capabilities to a true platform for personal automation.
By embracing conventions over configuration and automatic discovery, we removed the friction that made extension painful. Now, adding functionality is as simple as creating a folder and writing Python code.
Want to add weather forecasts? Create a plugin. Want to integrate with Notion? Create a plugin. Want to automate your smart home? Create a plugin.
Just create a folder with tasks.py and go.
That’s the power of auto-discovery: no config files, no headaches, no tears—just pure plugin joy. Big bada boom, multipass approved!
Check out the GitHub repo to explore the plugin architecture and build your own plugins!
