feat(operator): runner registry — spawn any agent CLI, not just Claude (P3)

Generalize the three Claude-hardcoded spots in bootstrap (launch argv,
install plan, creds/config-dir) behind a Runner registry so a nested
operator can be spawned with any agent CLI. The Claude path stays the
default and byte-identical.

- bootstrap.Runner + RUNNERS{claude,codex,gemini,cmd} + get_runner/runner_present
- build_run_argv / install_plan / creds_source / plan_creds / child_env all
  take an optional `runner` (default claude → unchanged behaviour)
- claude validated end-to-end; codex/gemini are best-effort defaults
  overridable via $HH_<RUNNER>_INSTALL etc.; generic `cmd` runner reads a
  full launch template from $HH_OPERATOR_CMD ({directive} placeholder)
- bridge _op_spawn resolves req["runner"], rejects unknown; plan now carries
  runner/runner_present (was claude_present)
- CLI: `spawn --runner {claude|codex|gemini|cmd}`
- 7 new unit tests; full suite 132 passed

Note: compose_directive still emits the Claude-flavoured ("hh-operator
skill") directive — the portable Layer-1 prompt is P4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-06-28 23:11:41 -07:00
parent 300e33980a
commit 09cea40494
4 changed files with 247 additions and 41 deletions
+6 -1
View File
@@ -87,11 +87,15 @@ def _build_parser() -> argparse.ArgumentParser:
gt.add_argument("--out", default=None, help="write to this local path (else stdout)")
gt.add_argument("--session", default=None)
sp = sub.add_parser("spawn", help="spawn a nested Claude operator (budgeted)")
sp = sub.add_parser("spawn", help="spawn a nested operator (budgeted)")
sp.add_argument("objective", help="what the nested operator should achieve")
sp.add_argument("--room-host", required=True)
sp.add_argument("--room-port", type=int, required=True)
sp.add_argument("--room-name", default="operator")
sp.add_argument("--runner", default="claude",
choices=["claude", "codex", "gemini", "cmd"],
help="which agent CLI to spawn as the child operator "
"(default: claude; 'cmd' reads $HH_OPERATOR_CMD)")
sp.add_argument("--stop", action="append", default=None,
help="a stop condition (repeatable)")
sp.add_argument("--target", choices=["host"], default="host")
@@ -366,6 +370,7 @@ def _run_spawn(args) -> int:
"op": "spawn", "objective": args.objective,
"room": {"host": args.room_host, "port": args.room_port,
"name": args.room_name},
"runner": args.runner,
"stop": args.stop, "target": args.target,
"config_dir": args.config_dir, "allow_creds": args.allow_creds,
"skip_permissions": args.skip_permissions, "dry_run": not args.go})