Skip to content

Architecture for Contributors

This page supplements the Concepts Overview with contributor-focused guidance: package layout, extension points, and key design decisions to understand before making changes. Read the Concepts Overview first for the component model and execution lifecycle.


Package Layout

The rampart/ source tree is organized by concern: foundational types live in core/, while each extension point gets its own subpackage (attacks/, probes/, evaluators/, drivers/, converters/, surfaces/, payloads/, reporting/). The pyrit_bridge/ package groups common PyRIT integration code (see PyRIT Bridge below), and pytest_plugin/ provides the pytest integration.


Key Design Decisions

Protocols Over ABCs

RAMPART uses @runtime_checkable protocols for extension points that consumers implement (AgentAdapter, Session, Evaluator, Surface, PromptDriver). This means:

  • No inheritance required — any class with the right methods satisfies the protocol
  • Type-checked at development time by ty
  • Verifiable at runtime with isinstance checks

BaseExecution is the exception — it's an ABC because it owns the lifecycle skeleton and subclasses share real implementation.

Factory Classes (Attacks, Probes)

Attacks and Probes are static factory classes that construct execution objects. They:

  • Provide a clean, discoverable API: Attacks.xpia(...), Probes.behavior(...)
  • Handle input coercion (e.g., coerce_driver for flexible trigger input)
  • Return BaseExecution, hiding the concrete execution class

When adding a new attack or probe, you add a static factory method — not a new class that users instantiate directly.

Evaluator Polarity

Evaluators are polarity-free. They report whether a condition was detected, not whether it's good or bad. The attack/probe factory applies the correct polarity:

  • resolve_as_attack: detected → UNSAFE
  • resolve_as_probe: detected → SAFE

This allows the same evaluator (e.g., ToolCalled) to be used in both attack and probe contexts.

Execution Lifecycle Ownership

BaseExecution owns all cross-cutting concerns:

  • Event dispatch — ON_PRE_EXECUTE, ON_POST_EXECUTE, ON_ERROR
  • Timing — wall-clock duration
  • Error handling — all exceptions from _execute_async are caught and converted to ERROR results
  • Handler registration — framework-level handlers (result collection) are injected automatically

Subclasses implement only _execute_async and strategy_name. They should not catch InfrastructureError — the base class handles it.

Pytest Plugin

pytest_plugin/ integrates RAMPART with pytest:

  • plugin.py — hook registrations (configure, collection, sessionfinish, terminal summary, optional pytest_testnodedown).
  • _session.py — session-scoped state container (RampartSession), trial-group aggregates, sink registry, idempotency and incomplete-run flags.
  • _collection.py — per-test ResultCollector and the ContextVar-based handler that captures results from executions.
  • _xdist.py — pytest-xdist support: detection helpers, JSON-safe serialization of Result objects, controller-side merge, and conftest-scanning sink discovery. Workers serialize their results into config.workeroutput; the controller deserializes via pytest_testnodedown and emits a single unified report. See Parallel Execution for the data flow and trust boundary.

PyRIT Bridge

PyRIT is RAMPART's upstream dependency for converters and prompt generation. Its import chain is heavy, so:

  • PyRIT-related logic is grouped under rampart/pyrit_bridge/
  • Lazy imports inside functions are used to defer the cost
  • This boundary keeps RAMPART's core import fast

Extension Points

Extension Point Protocol/ABC Where to add
New attack strategy Subclass BaseExecution rampart/attacks/
New probe strategy Subclass BaseExecution rampart/probes/
New evaluator Implement Evaluator protocol rampart/evaluators/
New prompt driver Implement PromptDriver protocol rampart/drivers/
New attack surface Implement Surface protocol rampart/surfaces/
New converter Implement PayloadConverter protocol rampart/converters/
New report sink Implement ReportSink protocol rampart/reporting/
New payload format Extend payload system rampart/payloads/

See Extending RAMPART for step-by-step guides.


Module Import Conventions

  • Import from the package root (rampart.core, rampart.attacks) when the symbol is exported in __init__.py
  • Within the same package, import from the specific module to avoid circular imports
  • Internal modules are prefixed with _ (e.g., _xpia.py, _single_turn.py) — they are not part of the public API