hackagent.attacks.techniques.iterative
Contract shared by every iterative attack.
An iterative attack talks to the target while it runs: each reply decides
what it sends next. It receives the target as a :data:Target and the
campaign's evaluation panel as a :data:Judge, and it asks the panel about
each exchange as it goes, because the verdict is what tells it whether to
stop. Every call it makes is traced by the runner.
It returns every exchange it had judged, each carrying that verdict and
the :func:~.trace.phase it came from, so the run reports the comparison
the attack exists to make (which technique, which step), no exchange is
judged twice, and each one keeps the part of the search that produced it.
Finding Objects
@dataclass(frozen=True)
class Finding()
One judged exchange of a search.
messages is the request sent to the target, response its reply,
and verdict what the panel made of it — None only when the
campaign configured no judges. path is the phase this came out of,
which is how the run attaches that phase's trace to this exchange.
IterativeAttack Objects
class IterativeAttack(ABC, Generic[ParamsT])
A search against the target for one goal.
prepare
async def prepare(goals: list[str],
target: Target,
judge: Optional[Judge] = None) -> None
Run-scoped setup, done once before any goal is searched.
Most attacks need none and the default does nothing. One that learns
across goals — AutoDAN-Turbo building its strategy library — does that
work here, where it belongs to the run rather than to any goal, and
stores what it built on self for :meth:search to read.
run
async def run(goal: str,
target: Target,
judge: Optional[Judge] = None) -> list[Finding]
Search for goal; empty when the target never replied usably.
search
@abstractmethod
async def search(goal: str, target: Target,
judge: Optional[Judge]) -> list[Finding]
Search for a stripped, non-empty goal.
Return one :class:Finding per exchange that was judged, in the
order they were tried. judge is None when the campaign
configured no judges: the search then has nothing to stop on and
runs to its configured end.
decode
def decode(response: str) -> str
Map a target reply back to plain text before it is judged.
judge_reply
async def judge_reply(judge: Optional[Judge], goal: str, prompt: str,
response: str) -> Optional[Verdict]
Ask the panel about one reply.
None when there is no panel, or when the call failed — the trace
records why. A search treats that as "no verdict", never as a failure.
succeeded
def succeeded(verdict: Optional[Verdict]) -> bool
Whether the panel called this exchange a success.