Lifecycle of a plugin
The Power Outlet Analogy
Think of a power outlet in a wall. Any device - lamp, phone charger, laptop - can plug into it. The wall doesn’t care what you plug in. As long as your plug fits the shape, it works.- The outlet = the Engine
- The plug shape = the contract (
BaseAttack,BaseProvider) - The devices = 1,000+ attacks and 7 providers
The Registry
TheRegistry class in src/ai-blackteam/registry.py is a simple name-to-class mapping:
How Registration Works
When you decorate a class with@register_attack("my-attack"), the decorator calls attack_registry.register("my-attack", MyAttack). That’s it - the class is now in the registry, accessible by name.
Auto-Discovery
When ai-blackteam starts, it calls_load_plugins():
discover() method uses pkgutil.iter_modules to find every .py file in the package directory. It imports each one, which triggers the decorators, which registers the classes:
_ are skipped (like __init__.py and _base.py).
External Plugin Folder
The registry also supports discovering plugins from an external folder:.py file in the folder, and it gets loaded automatically.
Why This Matters
Programming to an interface means:- Adding an attack = one Python file. The Engine doesn’t know or care what attacks exist. It just calls
generate_prompts()on whatever it receives. - Adding a provider = one Python file. The Engine doesn’t know or care what API it’s talking to. It just calls
send_prompt(). - No coupling between attacks and providers. Any attack works with any provider. A Base64 encoding attack works the same whether it’s talking to Anthropic, OpenAI, or a local Ollama model.