hanzo 0.3.12__py3-none-any.whl → 0.3.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hanzo might be problematic. Click here for more details.
- hanzo/cli.py +123 -54
- hanzo/dev.py +807 -458
- hanzo/orchestrator_config.py +319 -0
- {hanzo-0.3.12.dist-info → hanzo-0.3.13.dist-info}/METADATA +3 -1
- {hanzo-0.3.12.dist-info → hanzo-0.3.13.dist-info}/RECORD +7 -6
- {hanzo-0.3.12.dist-info → hanzo-0.3.13.dist-info}/WHEEL +0 -0
- {hanzo-0.3.12.dist-info → hanzo-0.3.13.dist-info}/entry_points.txt +0 -0
hanzo/cli.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Main CLI entry point for Hanzo."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import sys
|
|
4
|
-
import asyncio
|
|
5
5
|
import signal
|
|
6
|
-
import
|
|
6
|
+
import asyncio
|
|
7
7
|
import subprocess
|
|
8
8
|
from typing import Optional
|
|
9
9
|
|
|
@@ -26,7 +26,7 @@ from .utils.output import console
|
|
|
26
26
|
from .interactive.repl import HanzoREPL
|
|
27
27
|
|
|
28
28
|
# Version
|
|
29
|
-
__version__ = "0.
|
|
29
|
+
__version__ = "0.3.13"
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@click.group(invoke_without_command=True)
|
|
@@ -149,34 +149,78 @@ def node(ctx, name: str, port: int, network: str, models: tuple, max_jobs: int):
|
|
|
149
149
|
|
|
150
150
|
@cli.command()
|
|
151
151
|
@click.option("--workspace", default="~/.hanzo/dev", help="Workspace directory")
|
|
152
|
-
@click.option(
|
|
153
|
-
|
|
152
|
+
@click.option(
|
|
153
|
+
"--orchestrator",
|
|
154
|
+
default="gpt-5",
|
|
155
|
+
help="Orchestrator: gpt-5, router:gpt-4o, direct:claude, codex, gpt-5-pro-codex, cost-optimized",
|
|
156
|
+
)
|
|
157
|
+
@click.option(
|
|
158
|
+
"--orchestrator-mode",
|
|
159
|
+
type=click.Choice(["router", "direct", "codex", "hybrid", "local"]),
|
|
160
|
+
default=None,
|
|
161
|
+
help="Force orchestrator mode (router via hanzo-router, direct API, codex, hybrid, local)",
|
|
162
|
+
)
|
|
163
|
+
@click.option(
|
|
164
|
+
"--router-endpoint",
|
|
165
|
+
default=None,
|
|
166
|
+
help="Hanzo router endpoint (default: http://localhost:4000)",
|
|
167
|
+
)
|
|
168
|
+
@click.option("--claude-path", help="Path to Claude Code executable")
|
|
154
169
|
@click.option("--monitor", is_flag=True, help="Start in monitor mode")
|
|
155
170
|
@click.option("--repl", is_flag=True, help="Start REPL interface (default)")
|
|
156
171
|
@click.option("--instances", type=int, default=2, help="Number of worker agents")
|
|
157
172
|
@click.option("--mcp-tools", is_flag=True, default=True, help="Enable all MCP tools")
|
|
158
|
-
@click.option(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
@click.option(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
@click.option(
|
|
174
|
+
"--network-mode", is_flag=True, default=True, help="Network agents together"
|
|
175
|
+
)
|
|
176
|
+
@click.option(
|
|
177
|
+
"--guardrails", is_flag=True, default=True, help="Enable code quality guardrails"
|
|
178
|
+
)
|
|
179
|
+
@click.option(
|
|
180
|
+
"--use-network/--no-network", default=True, help="Use hanzo-network if available"
|
|
181
|
+
)
|
|
182
|
+
@click.option(
|
|
183
|
+
"--use-hanzo-net",
|
|
184
|
+
is_flag=True,
|
|
185
|
+
help="Use hanzo/net for local AI (auto-enabled with local: models)",
|
|
186
|
+
)
|
|
187
|
+
@click.option(
|
|
188
|
+
"--hanzo-net-port",
|
|
189
|
+
type=int,
|
|
190
|
+
default=52415,
|
|
191
|
+
help="Port for hanzo/net (default: 52415)",
|
|
192
|
+
)
|
|
193
|
+
@click.pass_context
|
|
194
|
+
def dev(
|
|
195
|
+
ctx,
|
|
196
|
+
workspace: str,
|
|
197
|
+
orchestrator: str,
|
|
198
|
+
orchestrator_mode: str,
|
|
199
|
+
router_endpoint: str,
|
|
200
|
+
claude_path: str,
|
|
201
|
+
monitor: bool,
|
|
202
|
+
repl: bool,
|
|
203
|
+
instances: int,
|
|
204
|
+
mcp_tools: bool,
|
|
205
|
+
network_mode: bool,
|
|
206
|
+
guardrails: bool,
|
|
207
|
+
use_network: bool,
|
|
208
|
+
use_hanzo_net: bool,
|
|
209
|
+
hanzo_net_port: int,
|
|
210
|
+
):
|
|
167
211
|
"""Start Hanzo Dev - AI Coding OS with configurable orchestrator.
|
|
168
|
-
|
|
212
|
+
|
|
169
213
|
This creates a multi-agent system where:
|
|
170
214
|
- Configurable orchestrator (GPT-5, GPT-4, Claude, or LOCAL) manages the network
|
|
171
215
|
- Local AI via hanzo/net for cost-effective orchestration
|
|
172
|
-
- Worker agents (Claude + local) handle code implementation
|
|
216
|
+
- Worker agents (Claude + local) handle code implementation
|
|
173
217
|
- Critic agents review and improve code (System 2 thinking)
|
|
174
218
|
- Cost-optimized routing (local models for simple tasks)
|
|
175
219
|
- All agents can use MCP tools
|
|
176
220
|
- Agents can call each other recursively
|
|
177
221
|
- Guardrails prevent code degradation
|
|
178
222
|
- Auto-recovery from failures
|
|
179
|
-
|
|
223
|
+
|
|
180
224
|
Examples:
|
|
181
225
|
hanzo dev # GPT-5 orchestrator (default)
|
|
182
226
|
hanzo dev --orchestrator gpt-4 # GPT-4 orchestrator
|
|
@@ -187,26 +231,54 @@ def dev(ctx, workspace: str, orchestrator: str, claude_path: str, monitor: bool,
|
|
|
187
231
|
hanzo dev --monitor # Auto-monitor and restart mode
|
|
188
232
|
"""
|
|
189
233
|
from .dev import run_dev_orchestrator
|
|
190
|
-
|
|
234
|
+
from .orchestrator_config import OrchestratorMode, get_orchestrator_config
|
|
235
|
+
|
|
236
|
+
# Get orchestrator configuration
|
|
237
|
+
orch_config = get_orchestrator_config(orchestrator)
|
|
238
|
+
|
|
239
|
+
# Override mode if specified
|
|
240
|
+
if orchestrator_mode:
|
|
241
|
+
orch_config.mode = OrchestratorMode(orchestrator_mode)
|
|
242
|
+
|
|
243
|
+
# Override router endpoint if specified
|
|
244
|
+
if router_endpoint and orch_config.router:
|
|
245
|
+
orch_config.router.endpoint = router_endpoint
|
|
246
|
+
|
|
191
247
|
# Auto-enable hanzo net if using local orchestrator
|
|
192
|
-
if orchestrator.startswith("local:"):
|
|
248
|
+
if orchestrator.startswith("local:") or orch_config.mode == OrchestratorMode.LOCAL:
|
|
193
249
|
use_hanzo_net = True
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
250
|
+
|
|
251
|
+
# Show configuration
|
|
252
|
+
console.print(f"[bold cyan]Orchestrator Configuration[/bold cyan]")
|
|
253
|
+
console.print(f" Mode: {orch_config.mode.value}")
|
|
254
|
+
console.print(f" Primary Model: {orch_config.primary_model}")
|
|
255
|
+
if orch_config.router:
|
|
256
|
+
console.print(f" Router Endpoint: {orch_config.router.endpoint}")
|
|
257
|
+
if orch_config.codex:
|
|
258
|
+
console.print(f" Codex Model: {orch_config.codex.model}")
|
|
259
|
+
console.print(
|
|
260
|
+
f" Cost Optimization: {'Enabled' if orch_config.enable_cost_optimization else 'Disabled'}"
|
|
261
|
+
)
|
|
262
|
+
console.print()
|
|
263
|
+
|
|
264
|
+
asyncio.run(
|
|
265
|
+
run_dev_orchestrator(
|
|
266
|
+
workspace=workspace,
|
|
267
|
+
orchestrator_model=orchestrator,
|
|
268
|
+
orchestrator_config=orch_config, # Pass the config
|
|
269
|
+
claude_path=claude_path,
|
|
270
|
+
monitor=monitor,
|
|
271
|
+
repl=repl or not monitor, # Default to REPL if not monitoring
|
|
272
|
+
instances=instances,
|
|
273
|
+
mcp_tools=mcp_tools,
|
|
274
|
+
network_mode=network_mode,
|
|
275
|
+
guardrails=guardrails,
|
|
276
|
+
use_network=use_network,
|
|
277
|
+
use_hanzo_net=use_hanzo_net,
|
|
278
|
+
hanzo_net_port=hanzo_net_port,
|
|
279
|
+
console=ctx.obj.get("console", console),
|
|
280
|
+
)
|
|
281
|
+
)
|
|
210
282
|
|
|
211
283
|
|
|
212
284
|
async def start_compute_node(
|
|
@@ -279,25 +351,24 @@ async def start_compute_node(
|
|
|
279
351
|
|
|
280
352
|
# Set up signal handlers for async version
|
|
281
353
|
stop_event = asyncio.Event()
|
|
282
|
-
|
|
354
|
+
|
|
283
355
|
def async_signal_handler(signum, frame):
|
|
284
356
|
console.print("\n[yellow]Stopping hanzo net...[/yellow]")
|
|
285
357
|
stop_event.set()
|
|
286
|
-
|
|
358
|
+
|
|
287
359
|
signal.signal(signal.SIGINT, async_signal_handler)
|
|
288
360
|
signal.signal(signal.SIGTERM, async_signal_handler)
|
|
289
|
-
|
|
361
|
+
|
|
290
362
|
# Run net with proper signal handling
|
|
291
363
|
try:
|
|
292
364
|
net_task = asyncio.create_task(net_run())
|
|
293
365
|
stop_task = asyncio.create_task(stop_event.wait())
|
|
294
|
-
|
|
366
|
+
|
|
295
367
|
# Wait for either net to complete or stop signal
|
|
296
368
|
done, pending = await asyncio.wait(
|
|
297
|
-
[net_task, stop_task],
|
|
298
|
-
return_when=asyncio.FIRST_COMPLETED
|
|
369
|
+
[net_task, stop_task], return_when=asyncio.FIRST_COMPLETED
|
|
299
370
|
)
|
|
300
|
-
|
|
371
|
+
|
|
301
372
|
# Cancel pending tasks
|
|
302
373
|
for task in pending:
|
|
303
374
|
task.cancel()
|
|
@@ -305,7 +376,7 @@ async def start_compute_node(
|
|
|
305
376
|
await task
|
|
306
377
|
except asyncio.CancelledError:
|
|
307
378
|
pass
|
|
308
|
-
|
|
379
|
+
|
|
309
380
|
# Check if we stopped due to signal
|
|
310
381
|
if stop_task in done:
|
|
311
382
|
console.print("[green]✓[/green] Node stopped gracefully")
|
|
@@ -360,25 +431,25 @@ async def start_compute_node(
|
|
|
360
431
|
# Run net command with detected python in a more signal-friendly way
|
|
361
432
|
# Create new process group for better signal handling
|
|
362
433
|
process = subprocess.Popen(
|
|
363
|
-
cmd_args,
|
|
434
|
+
cmd_args,
|
|
364
435
|
env=env,
|
|
365
|
-
preexec_fn=os.setsid if hasattr(os,
|
|
436
|
+
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
|
366
437
|
)
|
|
367
|
-
|
|
438
|
+
|
|
368
439
|
# Set up signal handlers to forward to subprocess group
|
|
369
440
|
def signal_handler(signum, frame):
|
|
370
441
|
if process.poll() is None: # Process is still running
|
|
371
442
|
console.print("\n[yellow]Stopping hanzo net...[/yellow]")
|
|
372
443
|
try:
|
|
373
444
|
# Send signal to entire process group
|
|
374
|
-
if hasattr(os,
|
|
445
|
+
if hasattr(os, "killpg"):
|
|
375
446
|
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
376
447
|
else:
|
|
377
448
|
process.terminate()
|
|
378
449
|
process.wait(timeout=5) # Wait up to 5 seconds
|
|
379
450
|
except subprocess.TimeoutExpired:
|
|
380
451
|
console.print("[yellow]Force stopping...[/yellow]")
|
|
381
|
-
if hasattr(os,
|
|
452
|
+
if hasattr(os, "killpg"):
|
|
382
453
|
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
|
|
383
454
|
else:
|
|
384
455
|
process.kill()
|
|
@@ -386,18 +457,16 @@ async def start_compute_node(
|
|
|
386
457
|
except ProcessLookupError:
|
|
387
458
|
pass # Process already terminated
|
|
388
459
|
raise KeyboardInterrupt
|
|
389
|
-
|
|
460
|
+
|
|
390
461
|
# Register signal handlers
|
|
391
462
|
signal.signal(signal.SIGINT, signal_handler)
|
|
392
463
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
393
|
-
|
|
464
|
+
|
|
394
465
|
# Wait for process to complete
|
|
395
466
|
returncode = process.wait()
|
|
396
|
-
|
|
467
|
+
|
|
397
468
|
if returncode != 0 and returncode != -2: # -2 is Ctrl+C
|
|
398
|
-
console.print(
|
|
399
|
-
f"[red]Net exited with code {returncode}[/red]"
|
|
400
|
-
)
|
|
469
|
+
console.print(f"[red]Net exited with code {returncode}[/red]")
|
|
401
470
|
|
|
402
471
|
finally:
|
|
403
472
|
os.chdir(original_cwd)
|