1919)
2020
2121from pyrit .executor .attack .core .attack_parameters import AttackParameters
22+ from pyrit .executor .attack .core .attack_result_attribution import AttackResultAttribution
2223from pyrit .executor .attack .core .attack_strategy import (
2324 AttackStrategy ,
2425 AttackStrategyContextT ,
@@ -142,6 +143,7 @@ async def execute_attack_from_seed_groups_async(
142143 objective_scorer : Optional ["TrueFalseScorer" ] = None ,
143144 field_overrides : Optional [Sequence [dict [str , Any ]]] = None ,
144145 return_partial_on_failure : bool = False ,
146+ attribution : Optional [AttackResultAttribution ] = None ,
145147 ** broadcast_fields : Any ,
146148 ) -> AttackExecutorResult [AttackStrategyResultT ]:
147149 """
@@ -163,6 +165,12 @@ async def execute_attack_from_seed_groups_async(
163165 from_seed_group() as overrides.
164166 return_partial_on_failure: If True, returns partial results when some
165167 objectives fail. If False (default), raises the first exception.
168+ attribution: Optional ``AttackResultAttribution`` stamped onto every
169+ per-task ``AttackContext`` so the persisted ``AttackResultEntry``
170+ row carries ``attribution_parent_id`` + ``attribution_data``.
171+ When ``None`` (default), no attribution is applied. The same
172+ attribution is shared across all tasks; per-task identity is
173+ reconstructed from the row's own ``objective_sha256``.
166174 **broadcast_fields: Fields applied to all seed groups (e.g., memory_labels).
167175 Per-seed-group field_overrides take precedence.
168176
@@ -205,6 +213,7 @@ async def build_params(i: int, sg: SeedAttackGroup) -> AttackParameters:
205213 attack = attack ,
206214 params_list = params_list ,
207215 return_partial_on_failure = return_partial_on_failure ,
216+ attribution = attribution ,
208217 )
209218
210219 async def execute_attack_async (
@@ -214,6 +223,7 @@ async def execute_attack_async(
214223 objectives : Sequence [str ],
215224 field_overrides : Optional [Sequence [dict [str , Any ]]] = None ,
216225 return_partial_on_failure : bool = False ,
226+ attribution : Optional [AttackResultAttribution ] = None ,
217227 ** broadcast_fields : Any ,
218228 ) -> AttackExecutorResult [AttackStrategyResultT ]:
219229 """
@@ -228,6 +238,9 @@ async def execute_attack_async(
228238 must match the length of objectives.
229239 return_partial_on_failure: If True, returns partial results when some
230240 objectives fail. If False (default), raises the first exception.
241+ attribution: Optional ``AttackResultAttribution`` stamped onto every
242+ per-task ``AttackContext`` so the persistence path can record
243+ orchestrator linkage. When ``None``, no attribution is applied.
231244 **broadcast_fields: Fields applied to all objectives (e.g., memory_labels).
232245 Per-objective field_overrides take precedence.
233246
@@ -268,6 +281,7 @@ async def execute_attack_async(
268281 attack = attack ,
269282 params_list = params_list ,
270283 return_partial_on_failure = return_partial_on_failure ,
284+ attribution = attribution ,
271285 )
272286
273287 async def _execute_with_params_list_async (
@@ -276,6 +290,7 @@ async def _execute_with_params_list_async(
276290 attack : AttackStrategy [AttackStrategyContextT , AttackStrategyResultT ],
277291 params_list : Sequence [AttackParameters ],
278292 return_partial_on_failure : bool = False ,
293+ attribution : Optional [AttackResultAttribution ] = None ,
279294 ) -> AttackExecutorResult [AttackStrategyResultT ]:
280295 """
281296 Execute attacks in parallel with a list of pre-built parameters.
@@ -287,19 +302,23 @@ async def _execute_with_params_list_async(
287302 attack: The attack strategy to execute.
288303 params_list: List of AttackParameters, one per execution.
289304 return_partial_on_failure: If True, returns partial results on failure.
305+ attribution: Optional ``AttackResultAttribution`` stamped onto every
306+ per-task ``AttackContext`` so the persistence path can record
307+ orchestrator linkage.
290308
291309 Returns:
292310 AttackExecutorResult with completed results and any incomplete objectives.
293311 """
294312 semaphore = asyncio .Semaphore (self ._max_concurrency )
295313
296- async def run_one (params : AttackParameters ) -> AttackStrategyResultT :
314+ async def run_one (index : int , params : AttackParameters ) -> AttackStrategyResultT :
297315 async with semaphore :
298- # Create context with params
299316 context = attack ._context_type (params = params )
317+ if attribution is not None :
318+ context ._attribution = attribution
300319 return await attack .execute_with_context_async (context = context )
301320
302- tasks = [run_one (p ) for p in params_list ]
321+ tasks = [run_one (i , p ) for i , p in enumerate ( params_list ) ]
303322 results_or_exceptions = await asyncio .gather (* tasks , return_exceptions = True )
304323
305324 return self ._process_execution_results (
0 commit comments