4141# re-implementing any parsing. Importing these also fails fast with a clear message if the script
4242# is not run inside an environment where hackingBuddyGPT is importable.
4343try :
44+ import hackingBuddyGPT .usecases # noqa: F401 - importing the package registers every use-case
4445 from hackingBuddyGPT .analysis .log_model import RunSummary , load_run , load_spans
46+ from hackingBuddyGPT .usecases .usecase import AutonomousUseCase , use_cases
4547 from hackingBuddyGPT .utils .log_storage import (
4648 GEN_AI_OPERATION_NAME ,
4749 GEN_AI_TOOL_CALL_ARGUMENTS ,
6062# Map the friendly use-case module name to the actual wintermute command (the class name).
6163USE_CASE_ALIASES = {
6264 "minimal_linux_privesc" : "MinimalPrivEscLinux" ,
65+ "minimal_linux_privesc_tool_calling" : "MinimalToolCallPrivEscLinux" ,
6366 "linux_privesc" : "PrivEscLinux" ,
6467}
6568
6871 "openrouter" : "openrouter/anthropic/claude-3.5-sonnet" ,
6972}
7073
74+ # The two use-case families count "rounds" through different CLI flags: the strategy-based ones
75+ # (CommandStrategy/SimpleStrategy, e.g. MinimalPrivEscLinux) loop on --max_turns, while the
76+ # autonomous agents (AutonomousUseCase, e.g. the tool-calling MinimalToolCallPrivEscLinux) loop on
77+ # --limits.max_rounds. resolve_rounds_flag() picks the right one from the registered class.
78+ ROUNDS_FLAG_MAX_TURNS = "--max_turns"
79+ ROUNDS_FLAG_MAX_ROUNDS = "--limits.max_rounds"
80+
81+
82+ def resolve_rounds_flag (use_case_name : str , override : str = "auto" ) -> str :
83+ if override == "max_turns" :
84+ return ROUNDS_FLAG_MAX_TURNS
85+ if override == "max_rounds" :
86+ return ROUNDS_FLAG_MAX_ROUNDS
87+
88+ cls = use_cases .get (use_case_name )
89+ if cls is not None and isinstance (cls , type ) and issubclass (cls , AutonomousUseCase ):
90+ return ROUNDS_FLAG_MAX_ROUNDS
91+ # default / strategy-based use-cases (and unknown names) loop on --max_turns
92+ return ROUNDS_FLAG_MAX_TURNS
93+
7194IMAGE_PREFIX = "privesc_"
7295# host-port that is forwarded to the container's SSH port (22). docker ps prints entries like
7396# "0.0.0.0:5013->22/tcp, [::]:5013->22/tcp"; capture the IPv4 host port.
@@ -219,7 +242,7 @@ def build_wintermute_argv(args: argparse.Namespace, container: Container, trace_
219242 f"--conn.username={ args .username } " ,
220243 f"--conn.password={ args .password } " ,
221244 f"--conn.hostname={ container .hostname } " ,
222- f"--max_turns ={ args .rounds } " ,
245+ f"{ args . rounds_flag } ={ args .rounds } " ,
223246 f"--log.log_dir={ trace_dir } " ,
224247 f"--log.tag={ tag } " ,
225248 ]
@@ -393,7 +416,7 @@ def write_markdown_report(report_path: Path, args: argparse.Namespace, results:
393416 lines .append (f"- **Date:** { datetime .datetime .now ().isoformat (timespec = 'seconds' )} " )
394417 lines .append (f"- **Use-case:** `{ args .use_case } `" )
395418 lines .append (f"- **LLM:** `{ args .model } ` (provider: `{ args .provider } `)" )
396- lines .append (f"- **Rounds (max_turns) :** { args .rounds } " )
419+ lines .append (f"- **Rounds:** { args .rounds } (via ` { args . rounds_flag } `) " )
397420 if args .trials > 1 :
398421 lines .append (f"- **Trials per container:** { args .trials } " )
399422 lines .append (f"- **SSH host:** `{ args .ssh_host } ` (user `{ args .username } `)" )
@@ -476,7 +499,11 @@ def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace:
476499 p .add_argument ("--or-provider" , default = None ,
477500 help = "optional OpenRouter provider routing (--llm.provider)" )
478501 p .add_argument ("--context-size" , type = int , default = 8192 , help = "model context size for prompt trimming" )
479- p .add_argument ("--rounds" , type = int , default = 20 , help = "per-run turn budget (mapped to --max_turns)" )
502+ p .add_argument ("--rounds" , type = int , default = 20 ,
503+ help = "per-run turn budget (mapped to --max_turns or --limits.max_rounds per use-case)" )
504+ p .add_argument ("--rounds-flag" , choices = ["auto" , "max_turns" , "max_rounds" ], default = "auto" ,
505+ help = "which CLI flag --rounds maps to; 'auto' picks per use-case "
506+ "(strategy=--max_turns, autonomous agent=--limits.max_rounds)" )
480507 p .add_argument ("--trials" , type = int , default = 1 , help = "how many times to run each container" )
481508 p .add_argument ("--filter" , default = None , help = "only run containers whose name/image contains this substring" )
482509 p .add_argument ("--username" , default = "lowpriv" , help = "SSH username on the target containers" )
@@ -489,6 +516,7 @@ def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace:
489516 args = p .parse_args (argv )
490517
491518 args .use_case = USE_CASE_ALIASES .get (args .use_case , args .use_case )
519+ args .rounds_flag = resolve_rounds_flag (args .use_case , args .rounds_flag )
492520 if args .model is None :
493521 args .model = DEFAULT_MODELS [args .provider ]
494522 if args .api_key is None :
@@ -524,7 +552,7 @@ def main(argv: Optional[list[str]] = None) -> int:
524552
525553 total_runs = len (containers ) * args .trials
526554 print (f"Found { len (containers )} container(s); running { total_runs } run(s) with use-case "
527- f"'{ args .use_case } ', model '{ args .model } ', rounds={ args .rounds } ." )
555+ f"'{ args .use_case } ', model '{ args .model } ', rounds={ args .rounds } ( { args . rounds_flag } ) ." )
528556 print (f"Output: { output_dir } " )
529557 print ()
530558
0 commit comments