feat(operator): harness-mode operator — drive a room with any function-calling model (P2)

The operator brain was Claude-only: spawn launched the claude CLI and let its
own loop drive the room. This adds the other first-class brain — a native
tool-calling loop (cmd_chat/operator/harness.py) that runs ANY function-calling
Provider (cmd_chat.ai) against an OPERATOR_TOOLS schema wired to the existing
bridge control verbs (say/exec/write/get/keys/screen/watch/manifest). No CLI
install, no creds carry.

The harness is a driver, not a new side-effect surface: every tool handler sends
the same control-socket request the hh-bridge CLI already sends, so the bridge
keeps enforcing grant-before-drive, the sandbox blast radius, and the recursion
budget. Layer-1 capabilities come from CAPABILITIES.md (the same portable
contract), and a per-agent token ceiling is enforced as a hard stop. Providers
that can't function-call are refused with a clear message rather than degrading
to prose.

New `operate` verb joins via the existing daemon socket and runs the loop with a
--profile or --provider/--model brain. Chat's native loop is left untouched
(self-contained harness over the shared Provider core, not a risky extraction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-06-28 23:23:17 -07:00
parent 2091bcefed
commit 0798db7941
3 changed files with 770 additions and 0 deletions
+102
View File
@@ -109,6 +109,33 @@ def _build_parser() -> argparse.ArgumentParser:
help="actually launch (default is a dry-run plan)")
sp.add_argument("--session", default=None)
# ── harness-mode operator (drive the room with any function-calling model) ──
op = sub.add_parser("operate",
help="run a function-calling model as the operator brain "
"(native loop over the bridge verbs)")
op.add_argument("--objective", required=True,
help="what this operator should achieve in the room")
op.add_argument("--profile", default=None,
help="named models.toml profile (e.g. local, groq-llama)")
op.add_argument("--provider", default=None,
help="provider spec when not using --profile "
"(ollama|anthropic|openai|module:Class)")
op.add_argument("--model", default=None, help="model name/id")
op.add_argument("--base-url", default=None, help="OpenAI-compatible base URL")
op.add_argument("--host", default=None, help="Ollama host URL")
op.add_argument("--stop", action="append", default=None,
help="a stop condition (repeatable)")
op.add_argument("--max-turns", type=int, default=None,
help="ceiling on model turns (default 12)")
op.add_argument("--token-ceiling", type=int, default=None,
help="cumulative prompt+completion token hard stop (default 20000)")
op.add_argument("--leave-on-done", action="store_true",
help="`down` the daemon when the objective is met")
op.add_argument("--depth", type=int, default=1)
op.add_argument("--fanout", type=int, default=2)
op.add_argument("--cost", type=float, default=5.0)
op.add_argument("--session", default=None)
sc = sub.add_parser("screen", help="print the relayed sandbox terminal buffer")
sc.add_argument("--tail", type=int, default=None, help="last N bytes only")
sc.add_argument("--ansi", action="store_true", help="keep ANSI codes (raw)")
@@ -378,6 +405,79 @@ def _run_spawn(args) -> int:
return 0 if resp.get("ok") else 1
def _build_operate_provider(args):
"""Resolve a Provider from --profile OR --provider/--model (+ endpoint flags).
Returns the provider or exits with a clear message."""
from cmd_chat.ai import load_profiles, make_provider, provider_from_profile, preflight
if args.profile:
profs = load_profiles()
prof = profs.get(args.profile)
if prof is None:
avail = ", ".join(sorted(profs)) or "(none found)"
print(f"no profile '{args.profile}' — available: {avail}", file=sys.stderr)
sys.exit(2)
provider = provider_from_profile(prof, name=args.profile, model=args.model,
base_url=args.base_url)
elif args.provider:
opts = {}
if args.base_url:
opts["base_url"] = args.base_url
if args.host:
opts["host"] = args.host
provider = make_provider(args.provider, model=args.model, **opts)
else:
print("operate needs --profile NAME or --provider SPEC --model M",
file=sys.stderr)
sys.exit(2)
ok, msg = preflight(provider)
if not ok:
print(f"preflight failed: {msg}", file=sys.stderr)
sys.exit(2)
return provider
def _run_operate(args) -> int:
from .bootstrap import Budget
from .harness import OperatorHarness
sess = resolve(getattr(args, "session", None))
if not sess.sock_path.exists():
print(f"no live bridge for session '{sess.name}' (run `up` first)",
file=sys.stderr)
return 2
# Confirm we're connected and learn our own room name (used to filter echoes).
try:
st = request(sess.sock_path, {"op": "status"}, read_timeout=5)
except BridgeUnreachable as e:
print(str(e), file=sys.stderr)
return 2
if not st.get("connected"):
print("bridge is up but not connected to the room yet", file=sys.stderr)
return 2
provider = _build_operate_provider(args)
budget = Budget(depth=args.depth, fanout=args.fanout, cost_usd=args.cost)
kw = {}
if args.max_turns is not None:
kw["max_turns"] = args.max_turns
if args.token_ceiling is not None:
kw["token_ceiling"] = args.token_ceiling
harness = OperatorHarness(
provider=provider, sock_path=str(sess.sock_path),
objective=args.objective, stop=args.stop, budget=budget,
me=st.get("name", "operator"), **kw)
result = harness.run()
print(json.dumps({"final": result.final, "reason": result.reason,
"turns": result.turns, "tokens": result.tokens,
"tool_calls": result.tool_calls}, indent=2))
if args.leave_on_done and result.reason == "done":
try:
request(sess.sock_path, {"op": "down"}, read_timeout=5)
except BridgeUnreachable:
pass
return 0 if result.reason in ("done", "token-ceiling", "turn-cap") else 1
def _run_keys(args) -> int:
from . import sandbox as sbx
if args.help_keys:
@@ -489,6 +589,8 @@ def main(argv: list[str] | None = None) -> int:
return _run_keys(args)
if verb == "spawn":
return _run_spawn(args)
if verb == "operate":
return _run_operate(args)
if verb == "manifest":
return _run_manifest(args)
if verb == "registry":