The plugin system is the heart of ai-blackteam. It lets you add attacks and providers by dropping a single Python file - no config changes, no import lists, nothing else to update.

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

The Registry class in src/ai-blackteam/registry.py is a simple name-to-class mapping:
Three registries exist:

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():
The 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:
Files starting with _ are skipped (like __init__.py and _base.py).

External Plugin Folder

The registry also supports discovering plugins from an external folder:
Drop a .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.
This is how you get to 1,003 attacks and 7 providers without the codebase turning into a mess.