Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Executor

An executor is an algorithm for interacting with an objective target. You give it an objective and some configuration, it drives the target, and it hands back a result. That’s the whole job.

The important thing to notice up front is that not every executor is an attack. Sending a single adversarial prompt is an executor, but so is running a Q&A benchmark over a dataset, fuzzing to generate new prompts, or orchestrating a cross-domain injection workflow. Attacks are the largest and most familiar family, but every category in this section — attacks, workflows, benchmarks, and prompt generators — is the same kind of object running the same lifecycle.

Executor vs. attack

An executor is the algorithm — for attacks, the attack strategy (e.g. PromptSendingAttack, CrescendoAttack, TreeOfAttacksWithPruningAttack). It knows how to drive the objective target. An attack is just the most common kind of executor.

Don’t confuse the executor with an attack technique — a configured recipe (a role-play framing, a many-shot priming set) that a scenario selects by name. The technique is the recipe; the executor is the engine that runs it. Techniques are defined fully in the scenarios docs.

Executor categories

PyRIT ships several families of executor — attacks are the largest, alongside workflows, benchmarks, and prompt generators. Attacks themselves split by a simple rule: count requests to the objective target — a single-turn attack sends exactly one; a multi-turn attack sends more than one and adapts as it goes.

Attack Configuration isn’t an executor — it’s the cross-cutting inputs every attack accepts (objective vs. adversarial target, prepended conversations, multimodal seeds, next-turn messages, memory labels). It has its own page.

The shape of an attack

Attacks — the most common executors — share a 4-component shape:

To run one:

  1. Initialize a strategy with optional configurations (converters, scorers, adversarial target).

  2. Call execute_async(...) with an objective (and optional prepended conversation / next message).

  3. Receive an AttackResult describing what happened and whether the objective was met.

The context is created for you from the execute_async arguments — you rarely build one by hand. See Attack Configuration for what you can put in the context and configs (prepended conversations, multimodal seeds, next-turn messages, memory labels).

The category pages above each walk through their executors with short runnable examples.

When do you actually need a new executor class?

Most of an executor’s behavior comes from its configuration and data, not from new code. So before writing a new executor class, ask whether the algorithm is genuinely new — or whether an existing executor with different primitives would do.

For attacks specifically, the durable value of a new class is adaptive decision-making: branching and backtracking based on the objective target’s feedback, like searching a graph for a path that works. Crescendo and TAP are the clearest examples — and you can reshape them substantially just by swapping their primitives (system prompt, converters, scorers, prepended/simulated conversations) rather than writing a new class.

A lot of what looks like a distinct executor isn’t a new algorithm at all:

Several of the single-turn attacks in this section predate this guidance and remain as classes for compatibility. When you are building something new, prefer configuration, a converter, or a technique — reach for a new executor class only when you genuinely need a new algorithm (most often a feedback-driven loop).