hanzo 0.3.11__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 +153 -22
- hanzo/dev.py +1970 -0
- hanzo/orchestrator_config.py +319 -0
- {hanzo-0.3.11.dist-info → hanzo-0.3.13.dist-info}/METADATA +3 -1
- {hanzo-0.3.11.dist-info → hanzo-0.3.13.dist-info}/RECORD +7 -5
- {hanzo-0.3.11.dist-info → hanzo-0.3.13.dist-info}/WHEEL +0 -0
- {hanzo-0.3.11.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)
|
|
@@ -147,6 +147,140 @@ def node(ctx, name: str, port: int, network: str, models: tuple, max_jobs: int):
|
|
|
147
147
|
pass
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
@cli.command()
|
|
151
|
+
@click.option("--workspace", default="~/.hanzo/dev", help="Workspace directory")
|
|
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")
|
|
169
|
+
@click.option("--monitor", is_flag=True, help="Start in monitor mode")
|
|
170
|
+
@click.option("--repl", is_flag=True, help="Start REPL interface (default)")
|
|
171
|
+
@click.option("--instances", type=int, default=2, help="Number of worker agents")
|
|
172
|
+
@click.option("--mcp-tools", is_flag=True, default=True, help="Enable all MCP tools")
|
|
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
|
+
):
|
|
211
|
+
"""Start Hanzo Dev - AI Coding OS with configurable orchestrator.
|
|
212
|
+
|
|
213
|
+
This creates a multi-agent system where:
|
|
214
|
+
- Configurable orchestrator (GPT-5, GPT-4, Claude, or LOCAL) manages the network
|
|
215
|
+
- Local AI via hanzo/net for cost-effective orchestration
|
|
216
|
+
- Worker agents (Claude + local) handle code implementation
|
|
217
|
+
- Critic agents review and improve code (System 2 thinking)
|
|
218
|
+
- Cost-optimized routing (local models for simple tasks)
|
|
219
|
+
- All agents can use MCP tools
|
|
220
|
+
- Agents can call each other recursively
|
|
221
|
+
- Guardrails prevent code degradation
|
|
222
|
+
- Auto-recovery from failures
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
hanzo dev # GPT-5 orchestrator (default)
|
|
226
|
+
hanzo dev --orchestrator gpt-4 # GPT-4 orchestrator
|
|
227
|
+
hanzo dev --orchestrator claude-3-5-sonnet # Claude orchestrator
|
|
228
|
+
hanzo dev --orchestrator local:llama3.2 # Local Llama 3.2 via hanzo/net
|
|
229
|
+
hanzo dev --use-hanzo-net # Enable local AI workers
|
|
230
|
+
hanzo dev --instances 4 # More worker agents
|
|
231
|
+
hanzo dev --monitor # Auto-monitor and restart mode
|
|
232
|
+
"""
|
|
233
|
+
from .dev import run_dev_orchestrator
|
|
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
|
+
|
|
247
|
+
# Auto-enable hanzo net if using local orchestrator
|
|
248
|
+
if orchestrator.startswith("local:") or orch_config.mode == OrchestratorMode.LOCAL:
|
|
249
|
+
use_hanzo_net = True
|
|
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
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
150
284
|
async def start_compute_node(
|
|
151
285
|
ctx,
|
|
152
286
|
name: str = None,
|
|
@@ -217,25 +351,24 @@ async def start_compute_node(
|
|
|
217
351
|
|
|
218
352
|
# Set up signal handlers for async version
|
|
219
353
|
stop_event = asyncio.Event()
|
|
220
|
-
|
|
354
|
+
|
|
221
355
|
def async_signal_handler(signum, frame):
|
|
222
356
|
console.print("\n[yellow]Stopping hanzo net...[/yellow]")
|
|
223
357
|
stop_event.set()
|
|
224
|
-
|
|
358
|
+
|
|
225
359
|
signal.signal(signal.SIGINT, async_signal_handler)
|
|
226
360
|
signal.signal(signal.SIGTERM, async_signal_handler)
|
|
227
|
-
|
|
361
|
+
|
|
228
362
|
# Run net with proper signal handling
|
|
229
363
|
try:
|
|
230
364
|
net_task = asyncio.create_task(net_run())
|
|
231
365
|
stop_task = asyncio.create_task(stop_event.wait())
|
|
232
|
-
|
|
366
|
+
|
|
233
367
|
# Wait for either net to complete or stop signal
|
|
234
368
|
done, pending = await asyncio.wait(
|
|
235
|
-
[net_task, stop_task],
|
|
236
|
-
return_when=asyncio.FIRST_COMPLETED
|
|
369
|
+
[net_task, stop_task], return_when=asyncio.FIRST_COMPLETED
|
|
237
370
|
)
|
|
238
|
-
|
|
371
|
+
|
|
239
372
|
# Cancel pending tasks
|
|
240
373
|
for task in pending:
|
|
241
374
|
task.cancel()
|
|
@@ -243,7 +376,7 @@ async def start_compute_node(
|
|
|
243
376
|
await task
|
|
244
377
|
except asyncio.CancelledError:
|
|
245
378
|
pass
|
|
246
|
-
|
|
379
|
+
|
|
247
380
|
# Check if we stopped due to signal
|
|
248
381
|
if stop_task in done:
|
|
249
382
|
console.print("[green]✓[/green] Node stopped gracefully")
|
|
@@ -298,25 +431,25 @@ async def start_compute_node(
|
|
|
298
431
|
# Run net command with detected python in a more signal-friendly way
|
|
299
432
|
# Create new process group for better signal handling
|
|
300
433
|
process = subprocess.Popen(
|
|
301
|
-
cmd_args,
|
|
434
|
+
cmd_args,
|
|
302
435
|
env=env,
|
|
303
|
-
preexec_fn=os.setsid if hasattr(os,
|
|
436
|
+
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
|
304
437
|
)
|
|
305
|
-
|
|
438
|
+
|
|
306
439
|
# Set up signal handlers to forward to subprocess group
|
|
307
440
|
def signal_handler(signum, frame):
|
|
308
441
|
if process.poll() is None: # Process is still running
|
|
309
442
|
console.print("\n[yellow]Stopping hanzo net...[/yellow]")
|
|
310
443
|
try:
|
|
311
444
|
# Send signal to entire process group
|
|
312
|
-
if hasattr(os,
|
|
445
|
+
if hasattr(os, "killpg"):
|
|
313
446
|
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
314
447
|
else:
|
|
315
448
|
process.terminate()
|
|
316
449
|
process.wait(timeout=5) # Wait up to 5 seconds
|
|
317
450
|
except subprocess.TimeoutExpired:
|
|
318
451
|
console.print("[yellow]Force stopping...[/yellow]")
|
|
319
|
-
if hasattr(os,
|
|
452
|
+
if hasattr(os, "killpg"):
|
|
320
453
|
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
|
|
321
454
|
else:
|
|
322
455
|
process.kill()
|
|
@@ -324,18 +457,16 @@ async def start_compute_node(
|
|
|
324
457
|
except ProcessLookupError:
|
|
325
458
|
pass # Process already terminated
|
|
326
459
|
raise KeyboardInterrupt
|
|
327
|
-
|
|
460
|
+
|
|
328
461
|
# Register signal handlers
|
|
329
462
|
signal.signal(signal.SIGINT, signal_handler)
|
|
330
463
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
331
|
-
|
|
464
|
+
|
|
332
465
|
# Wait for process to complete
|
|
333
466
|
returncode = process.wait()
|
|
334
|
-
|
|
467
|
+
|
|
335
468
|
if returncode != 0 and returncode != -2: # -2 is Ctrl+C
|
|
336
|
-
console.print(
|
|
337
|
-
f"[red]Net exited with code {returncode}[/red]"
|
|
338
|
-
)
|
|
469
|
+
console.print(f"[red]Net exited with code {returncode}[/red]")
|
|
339
470
|
|
|
340
471
|
finally:
|
|
341
472
|
os.chdir(original_cwd)
|