The Engine is the orchestrator. It connects attacks to providers, passes responses to the evaluator, and saves everything to SQLite.

Initialization

The Engine creates a Storage instance on init.

Mode Dispatching

The run() method checks the attack’s mode and calls the right execution method:

run_single

Handles single-turn attacks. Generates prompts, sends each one independently, evaluates, and stores.
Returns a list of result dicts (one per prompt variant).

run_multi_turn

Handles multi-turn attacks. Sends turns sequentially, building conversation context.
Key difference: evaluates the combined text from all responses, not individual ones. Returns a single result dict.

run_tool_use

Handles tool-use attacks. Sends messages with tool definitions, records tool calls, simulates tool responses, and evaluates whether the model attempted dangerous actions.
Uses evaluate_tool_calls() instead of the standard evaluate() - checks for sensitive file access, destructive commands, data exfiltration, etc.

run_batch_parallel

Runs multiple attacks in parallel using asyncio. Each attack gets its own thread with its own Engine instance (for thread-safe SQLite access).
Key details:
  • Uses asyncio.Semaphore to limit concurrency to max_workers
  • Each attack runs in a separate thread via asyncio.to_thread
  • Each thread creates its own Engine instance (same db_path)
  • on_complete callback fires after each attack finishes (used for progress bars)
  • Errors are caught per-attack - one failure doesn’t crash the batch

Error Handling

Every prompt/turn is wrapped in try/except. If a single prompt fails:
  1. The error is logged
  2. The result is recorded with verdict="ERROR" and confidence=0.0
  3. The batch continues with the next prompt

Source

src/ai-blackteam/engine.py