hanzo 0.3.12__py3-none-any.whl → 0.3.14__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/dev.py CHANGED
@@ -8,25 +8,26 @@ This module provides a sophisticated orchestration layer that:
8
8
  5. Integrates with REPL for interactive control
9
9
  """
10
10
 
11
- import asyncio
12
- import json
13
- import logging
14
11
  import os
15
- import signal
16
- import subprocess
17
12
  import sys
13
+ import json
18
14
  import time
19
- from dataclasses import dataclass, asdict
20
- from datetime import datetime
15
+ import signal
16
+ import asyncio
17
+ import logging
18
+ import subprocess
21
19
  from enum import Enum
20
+ from typing import Any, Dict, List, Union, Callable, Optional
22
21
  from pathlib import Path
23
- from typing import Dict, List, Optional, Any, Callable, Union
24
- from rich.console import Console
25
- from rich.table import Table
22
+ from datetime import datetime
23
+ from dataclasses import asdict, dataclass
24
+
26
25
  from rich.live import Live
27
- from rich.layout import Layout
28
26
  from rich.panel import Panel
29
- from rich.progress import Progress, SpinnerColumn, TextColumn
27
+ from rich.table import Table
28
+ from rich.layout import Layout
29
+ from rich.console import Console
30
+ from rich.progress import Progress, TextColumn, SpinnerColumn
30
31
 
31
32
  # Setup logging first
32
33
  logger = logging.getLogger(__name__)
@@ -35,55 +36,73 @@ console = Console()
35
36
  # Import hanzo-network for agent orchestration
36
37
  try:
37
38
  from hanzo_network import (
38
- Agent, Network, Router, NetworkState,
39
- create_agent, create_network, create_router, create_routing_agent,
40
- ModelConfig, ModelProvider,
41
- DistributedNetwork, create_distributed_network,
42
- LOCAL_COMPUTE_AVAILABLE
39
+ LOCAL_COMPUTE_AVAILABLE,
40
+ Agent,
41
+ Router,
42
+ Network,
43
+ ModelConfig,
44
+ NetworkState,
45
+ ModelProvider,
46
+ DistributedNetwork,
47
+ create_agent,
48
+ create_router,
49
+ create_network,
50
+ create_routing_agent,
51
+ create_distributed_network,
43
52
  )
53
+
44
54
  NETWORK_AVAILABLE = True
45
55
  except ImportError:
46
56
  NETWORK_AVAILABLE = False
47
57
  logger.warning("hanzo-network not available, using basic orchestration")
48
-
58
+
49
59
  # Provide fallback implementations
50
60
  class Agent:
51
61
  """Fallback Agent class when hanzo-network is not available."""
62
+
52
63
  def __init__(self, name: str, model: str = "gpt-4", **kwargs):
53
64
  self.name = name
54
65
  self.model = model
55
66
  self.config = kwargs
56
-
67
+
57
68
  class Network:
58
69
  """Fallback Network class."""
70
+
59
71
  def __init__(self):
60
72
  self.agents = []
61
-
73
+
62
74
  class Router:
63
75
  """Fallback Router class."""
76
+
64
77
  def __init__(self):
65
78
  pass
66
-
79
+
67
80
  class NetworkState:
68
81
  """Fallback NetworkState class."""
82
+
69
83
  pass
70
-
84
+
71
85
  class ModelConfig:
72
86
  """Fallback ModelConfig class."""
87
+
73
88
  def __init__(self, **kwargs):
74
- self.__dict__.update(kwargs)
75
-
89
+ # Accept all kwargs and store as attributes
90
+ for key, value in kwargs.items():
91
+ setattr(self, key, value)
92
+
76
93
  class ModelProvider:
77
94
  """Fallback ModelProvider class."""
95
+
78
96
  OPENAI = "openai"
79
97
  ANTHROPIC = "anthropic"
80
98
  LOCAL = "local"
81
-
99
+
82
100
  LOCAL_COMPUTE_AVAILABLE = False
83
101
 
84
102
 
85
103
  class AgentState(Enum):
86
104
  """State of an AI agent."""
105
+
87
106
  IDLE = "idle"
88
107
  THINKING = "thinking" # System 2 deliberation
89
108
  EXECUTING = "executing"
@@ -94,6 +113,7 @@ class AgentState(Enum):
94
113
 
95
114
  class RuntimeState(Enum):
96
115
  """State of Claude Code runtime."""
116
+
97
117
  NOT_STARTED = "not_started"
98
118
  STARTING = "starting"
99
119
  RUNNING = "running"
@@ -106,6 +126,7 @@ class RuntimeState(Enum):
106
126
  @dataclass
107
127
  class AgentContext:
108
128
  """Context for agent decision making."""
129
+
109
130
  task: str
110
131
  goal: str
111
132
  constraints: List[str]
@@ -113,11 +134,12 @@ class AgentContext:
113
134
  max_attempts: int = 3
114
135
  timeout_seconds: int = 300
115
136
  checkpoint_interval: int = 60
116
-
137
+
117
138
 
118
139
  @dataclass
119
140
  class RuntimeHealth:
120
141
  """Health status of Claude Code runtime."""
142
+
121
143
  state: RuntimeState
122
144
  last_response: datetime
123
145
  response_time_ms: float
@@ -130,6 +152,7 @@ class RuntimeHealth:
130
152
  @dataclass
131
153
  class ThinkingResult:
132
154
  """Result of System 2 thinking process."""
155
+
133
156
  decision: str
134
157
  reasoning: List[str]
135
158
  confidence: float
@@ -140,24 +163,26 @@ class ThinkingResult:
140
163
 
141
164
  class HanzoDevOrchestrator:
142
165
  """Main orchestrator for Hanzo Dev System 2 thinking."""
143
-
144
- def __init__(self,
145
- workspace_dir: str = "~/.hanzo/dev",
146
- claude_code_path: Optional[str] = None):
166
+
167
+ def __init__(
168
+ self,
169
+ workspace_dir: str = "~/.hanzo/dev",
170
+ claude_code_path: Optional[str] = None,
171
+ ):
147
172
  """Initialize the orchestrator.
148
-
173
+
149
174
  Args:
150
175
  workspace_dir: Directory for persistence and checkpoints
151
176
  claude_code_path: Path to Claude Code executable
152
177
  """
153
178
  self.workspace_dir = Path(workspace_dir).expanduser()
154
179
  self.workspace_dir.mkdir(parents=True, exist_ok=True)
155
-
180
+
156
181
  self.claude_code_path = claude_code_path or self._find_claude_code()
157
182
  self.state_file = self.workspace_dir / "orchestrator_state.json"
158
183
  self.checkpoint_dir = self.workspace_dir / "checkpoints"
159
184
  self.checkpoint_dir.mkdir(exist_ok=True)
160
-
185
+
161
186
  self.agent_state = AgentState.IDLE
162
187
  self.runtime_health = RuntimeHealth(
163
188
  state=RuntimeState.NOT_STARTED,
@@ -166,14 +191,14 @@ class HanzoDevOrchestrator:
166
191
  memory_usage_mb=0,
167
192
  cpu_percent=0,
168
193
  error_count=0,
169
- restart_count=0
194
+ restart_count=0,
170
195
  )
171
-
196
+
172
197
  self.current_context: Optional[AgentContext] = None
173
198
  self.claude_process: Optional[subprocess.Popen] = None
174
199
  self.thinking_history: List[ThinkingResult] = []
175
200
  self._shutdown = False
176
-
201
+
177
202
  def _find_claude_code(self) -> str:
178
203
  """Find Claude Code executable."""
179
204
  # Check common locations
@@ -181,19 +206,21 @@ class HanzoDevOrchestrator:
181
206
  "/usr/local/bin/claude",
182
207
  "/opt/claude/claude",
183
208
  "~/.local/bin/claude",
184
- "claude" # Rely on PATH
209
+ "claude", # Rely on PATH
185
210
  ]
186
-
211
+
187
212
  for path in possible_paths:
188
213
  expanded = Path(path).expanduser()
189
- if expanded.exists() or (path == "claude" and os.system(f"which {path} >/dev/null 2>&1") == 0):
214
+ if expanded.exists() or (
215
+ path == "claude" and os.system(f"which {path} >/dev/null 2>&1") == 0
216
+ ):
190
217
  return str(expanded) if expanded.exists() else path
191
-
218
+
192
219
  raise RuntimeError("Claude Code not found. Please specify path.")
193
-
220
+
194
221
  async def think(self, problem: str, context: Dict[str, Any]) -> ThinkingResult:
195
222
  """System 2 thinking process - deliberative and analytical.
196
-
223
+
197
224
  This implements slow, deliberate thinking:
198
225
  1. Analyze the problem thoroughly
199
226
  2. Consider multiple approaches
@@ -202,65 +229,65 @@ class HanzoDevOrchestrator:
202
229
  """
203
230
  self.agent_state = AgentState.THINKING
204
231
  console.print("[yellow]🤔 Engaging System 2 thinking...[/yellow]")
205
-
232
+
206
233
  # Simulate deep thinking process
207
234
  reasoning = []
208
235
  alternatives = []
209
236
  risks = []
210
-
237
+
211
238
  # Step 1: Problem decomposition
212
239
  reasoning.append(f"Decomposing problem: {problem}")
213
240
  sub_problems = self._decompose_problem(problem)
214
241
  reasoning.append(f"Identified {len(sub_problems)} sub-problems")
215
-
242
+
216
243
  # Step 2: Generate alternatives
217
244
  for sub in sub_problems:
218
245
  alt = f"Approach for '{sub}': {self._generate_approach(sub, context)}"
219
246
  alternatives.append(alt)
220
-
247
+
221
248
  # Step 3: Risk assessment
222
249
  risks = self._assess_risks(problem, alternatives, context)
223
-
250
+
224
251
  # Step 4: Decision synthesis
225
252
  decision = self._synthesize_decision(problem, alternatives, risks, context)
226
253
  confidence = self._calculate_confidence(decision, risks)
227
-
254
+
228
255
  # Step 5: Plan next steps
229
256
  next_steps = self._plan_next_steps(decision, context)
230
-
257
+
231
258
  result = ThinkingResult(
232
259
  decision=decision,
233
260
  reasoning=reasoning,
234
261
  confidence=confidence,
235
262
  alternatives=alternatives,
236
263
  risks=risks,
237
- next_steps=next_steps
264
+ next_steps=next_steps,
238
265
  )
239
-
266
+
240
267
  self.thinking_history.append(result)
241
268
  self.agent_state = AgentState.IDLE
242
-
269
+
243
270
  return result
244
-
271
+
245
272
  def _decompose_problem(self, problem: str) -> List[str]:
246
273
  """Decompose a problem into sub-problems."""
247
274
  # Simple heuristic decomposition
248
275
  sub_problems = []
249
-
276
+
250
277
  # Check for common patterns
251
278
  if "and" in problem.lower():
252
279
  parts = problem.split(" and ")
253
280
  sub_problems.extend(parts)
254
-
281
+
255
282
  if "then" in problem.lower():
256
283
  parts = problem.split(" then ")
257
284
  sub_problems.extend(parts)
258
-
285
+
259
286
  if not sub_problems:
260
287
  sub_problems = [problem]
261
-
288
+
262
289
  return sub_problems
263
-
290
+
264
291
  def _generate_approach(self, sub_problem: str, context: Dict[str, Any]) -> str:
265
292
  """Generate an approach for a sub-problem."""
266
293
  # Heuristic approach generation
@@ -272,78 +299,89 @@ class HanzoDevOrchestrator:
272
299
  return "Examine stack trace, validate inputs, add error handling"
273
300
  else:
274
301
  return "Execute standard workflow with monitoring"
275
-
276
- def _assess_risks(self, problem: str, alternatives: List[str], context: Dict[str, Any]) -> List[str]:
302
+
303
+ def _assess_risks(
304
+ self, problem: str, alternatives: List[str], context: Dict[str, Any]
305
+ ) -> List[str]:
277
306
  """Assess risks of different approaches."""
278
307
  risks = []
279
-
308
+
280
309
  if "restart" in str(alternatives).lower():
281
310
  risks.append("Restarting may lose current state")
282
-
311
+
283
312
  if "force" in str(alternatives).lower():
284
313
  risks.append("Forcing operations may cause data corruption")
285
-
314
+
286
315
  if context.get("error_count", 0) > 5:
287
316
  risks.append("High error rate indicates systemic issue")
288
-
317
+
289
318
  return risks
290
-
291
- def _synthesize_decision(self, problem: str, alternatives: List[str],
292
- risks: List[str], context: Dict[str, Any]) -> str:
319
+
320
+ def _synthesize_decision(
321
+ self,
322
+ problem: str,
323
+ alternatives: List[str],
324
+ risks: List[str],
325
+ context: Dict[str, Any],
326
+ ) -> str:
293
327
  """Synthesize a decision from analysis."""
294
328
  if len(risks) > 2:
295
- return "Proceed cautiously with incremental approach and rollback capability"
329
+ return (
330
+ "Proceed cautiously with incremental approach and rollback capability"
331
+ )
296
332
  elif alternatives:
297
333
  return f"Execute primary approach: {alternatives[0]}"
298
334
  else:
299
335
  return "Gather more information before proceeding"
300
-
336
+
301
337
  def _calculate_confidence(self, decision: str, risks: List[str]) -> float:
302
338
  """Calculate confidence in decision."""
303
339
  base_confidence = 0.8
304
340
  risk_penalty = len(risks) * 0.1
305
341
  return max(0.2, min(1.0, base_confidence - risk_penalty))
306
-
342
+
307
343
  def _plan_next_steps(self, decision: str, context: Dict[str, Any]) -> List[str]:
308
344
  """Plan concrete next steps."""
309
345
  steps = []
310
-
346
+
311
347
  if "cautiously" in decision.lower():
312
348
  steps.append("Create checkpoint before proceeding")
313
349
  steps.append("Enable verbose logging")
314
-
350
+
315
351
  steps.append("Execute decision with monitoring")
316
352
  steps.append("Validate results against success criteria")
317
353
  steps.append("Report outcome and update state")
318
-
354
+
319
355
  return steps
320
-
356
+
321
357
  async def start_claude_runtime(self, resume: bool = False) -> bool:
322
358
  """Start or resume Claude Code runtime.
323
-
359
+
324
360
  Args:
325
361
  resume: Whether to resume from checkpoint
326
362
  """
327
363
  if self.claude_process and self.claude_process.poll() is None:
328
364
  console.print("[yellow]Claude Code already running[/yellow]")
329
365
  return True
330
-
366
+
331
367
  self.runtime_health.state = RuntimeState.STARTING
332
368
  console.print("[cyan]Starting Claude Code runtime...[/cyan]")
333
-
369
+
334
370
  try:
335
371
  # Load checkpoint if resuming
336
372
  checkpoint_file = None
337
373
  if resume:
338
374
  checkpoint_file = self._get_latest_checkpoint()
339
375
  if checkpoint_file:
340
- console.print(f"[green]Resuming from checkpoint: {checkpoint_file.name}[/green]")
341
-
376
+ console.print(
377
+ f"[green]Resuming from checkpoint: {checkpoint_file.name}[/green]"
378
+ )
379
+
342
380
  # Prepare command
343
381
  cmd = [self.claude_code_path]
344
382
  if checkpoint_file:
345
383
  cmd.extend(["--resume", str(checkpoint_file)])
346
-
384
+
347
385
  # Start process with proper signal handling
348
386
  self.claude_process = subprocess.Popen(
349
387
  cmd,
@@ -351,12 +389,12 @@ class HanzoDevOrchestrator:
351
389
  stderr=subprocess.PIPE,
352
390
  stdin=subprocess.PIPE,
353
391
  text=True,
354
- preexec_fn=os.setsid if hasattr(os, 'setsid') else None
392
+ preexec_fn=os.setsid if hasattr(os, "setsid") else None,
355
393
  )
356
-
394
+
357
395
  # Wait for startup
358
396
  await asyncio.sleep(2)
359
-
397
+
360
398
  if self.claude_process.poll() is None:
361
399
  self.runtime_health.state = RuntimeState.RUNNING
362
400
  self.runtime_health.last_response = datetime.now()
@@ -366,235 +404,248 @@ class HanzoDevOrchestrator:
366
404
  self.runtime_health.state = RuntimeState.CRASHED
367
405
  console.print("[red]✗ Claude Code failed to start[/red]")
368
406
  return False
369
-
407
+
370
408
  except Exception as e:
371
409
  console.print(f"[red]Error starting Claude Code: {e}[/red]")
372
410
  self.runtime_health.state = RuntimeState.CRASHED
373
411
  return False
374
-
412
+
375
413
  def _get_latest_checkpoint(self) -> Optional[Path]:
376
414
  """Get the latest checkpoint file."""
377
415
  checkpoints = list(self.checkpoint_dir.glob("checkpoint_*.json"))
378
416
  if checkpoints:
379
417
  return max(checkpoints, key=lambda p: p.stat().st_mtime)
380
418
  return None
381
-
419
+
382
420
  async def health_check(self) -> bool:
383
421
  """Check health of Claude Code runtime."""
384
422
  if not self.claude_process:
385
423
  self.runtime_health.state = RuntimeState.NOT_STARTED
386
424
  return False
387
-
425
+
388
426
  # Check if process is alive
389
427
  if self.claude_process.poll() is not None:
390
428
  self.runtime_health.state = RuntimeState.CRASHED
391
429
  self.runtime_health.error_count += 1
392
430
  return False
393
-
431
+
394
432
  # Try to communicate
395
433
  try:
396
434
  # Send a health check command (this is a placeholder)
397
435
  # In reality, you'd have a proper API or IPC mechanism
398
436
  start_time = time.time()
399
-
437
+
400
438
  # Simulate health check
401
439
  await asyncio.sleep(0.1)
402
-
440
+
403
441
  response_time = (time.time() - start_time) * 1000
404
442
  self.runtime_health.response_time_ms = response_time
405
443
  self.runtime_health.last_response = datetime.now()
406
-
444
+
407
445
  if response_time > 5000:
408
446
  self.runtime_health.state = RuntimeState.NOT_RESPONDING
409
447
  return False
410
448
  else:
411
449
  self.runtime_health.state = RuntimeState.RUNNING
412
450
  return True
413
-
451
+
414
452
  except Exception as e:
415
453
  logger.error(f"Health check failed: {e}")
416
454
  self.runtime_health.state = RuntimeState.NOT_RESPONDING
417
455
  self.runtime_health.error_count += 1
418
456
  return False
419
-
457
+
420
458
  async def restart_if_needed(self) -> bool:
421
459
  """Restart Claude Code if it's stuck or crashed."""
422
- if self.runtime_health.state in [RuntimeState.CRASHED, RuntimeState.NOT_RESPONDING]:
460
+ if self.runtime_health.state in [
461
+ RuntimeState.CRASHED,
462
+ RuntimeState.NOT_RESPONDING,
463
+ ]:
423
464
  console.print("[yellow]Claude Code needs restart...[/yellow]")
424
-
465
+
425
466
  # Kill existing process
426
467
  if self.claude_process:
427
468
  try:
428
- if hasattr(os, 'killpg'):
469
+ if hasattr(os, "killpg"):
429
470
  os.killpg(os.getpgid(self.claude_process.pid), signal.SIGTERM)
430
471
  else:
431
472
  self.claude_process.terminate()
432
473
  await asyncio.sleep(2)
433
474
  if self.claude_process.poll() is None:
434
475
  self.claude_process.kill()
435
- except:
476
+ except Exception:
436
477
  pass
437
-
478
+
438
479
  self.runtime_health.restart_count += 1
439
480
  self.runtime_health.state = RuntimeState.RESTARTING
440
-
481
+
441
482
  # Start with resume
442
483
  return await self.start_claude_runtime(resume=True)
443
-
484
+
444
485
  return True
445
-
486
+
446
487
  async def create_checkpoint(self, name: Optional[str] = None) -> Path:
447
488
  """Create a checkpoint of current state."""
448
489
  checkpoint_name = name or f"checkpoint_{int(time.time())}"
449
490
  checkpoint_file = self.checkpoint_dir / f"{checkpoint_name}.json"
450
-
491
+
451
492
  checkpoint_data = {
452
493
  "timestamp": datetime.now().isoformat(),
453
494
  "agent_state": self.agent_state.value,
454
495
  "runtime_health": asdict(self.runtime_health),
455
- "current_context": asdict(self.current_context) if self.current_context else None,
456
- "thinking_history": [asdict(t) for t in self.thinking_history[-10:]], # Last 10
496
+ "current_context": (
497
+ asdict(self.current_context) if self.current_context else None
498
+ ),
499
+ "thinking_history": [
500
+ asdict(t) for t in self.thinking_history[-10:]
501
+ ], # Last 10
457
502
  }
458
-
459
- with open(checkpoint_file, 'w') as f:
503
+
504
+ with open(checkpoint_file, "w") as f:
460
505
  json.dump(checkpoint_data, f, indent=2, default=str)
461
-
506
+
462
507
  console.print(f"[green]✓ Checkpoint saved: {checkpoint_file.name}[/green]")
463
508
  return checkpoint_file
464
-
509
+
465
510
  async def restore_checkpoint(self, checkpoint_file: Path) -> bool:
466
511
  """Restore from a checkpoint."""
467
512
  try:
468
- with open(checkpoint_file, 'r') as f:
513
+ with open(checkpoint_file, "r") as f:
469
514
  data = json.load(f)
470
-
515
+
471
516
  self.agent_state = AgentState(data["agent_state"])
472
517
  # Restore other state as needed
473
-
474
- console.print(f"[green]✓ Restored from checkpoint: {checkpoint_file.name}[/green]")
518
+
519
+ console.print(
520
+ f"[green]✓ Restored from checkpoint: {checkpoint_file.name}[/green]"
521
+ )
475
522
  return True
476
523
  except Exception as e:
477
524
  console.print(f"[red]Failed to restore checkpoint: {e}[/red]")
478
525
  return False
479
-
526
+
480
527
  async def monitor_loop(self):
481
528
  """Main monitoring loop."""
482
529
  console.print("[cyan]Starting monitoring loop...[/cyan]")
483
-
530
+
484
531
  while not self._shutdown:
485
532
  try:
486
533
  # Health check
487
534
  healthy = await self.health_check()
488
-
535
+
489
536
  if not healthy:
490
- console.print(f"[yellow]Health check failed. State: {self.runtime_health.state.value}[/yellow]")
491
-
537
+ console.print(
538
+ f"[yellow]Health check failed. State: {self.runtime_health.state.value}[/yellow]"
539
+ )
540
+
492
541
  # Use System 2 thinking to decide what to do
493
542
  thinking_result = await self.think(
494
543
  f"Claude Code is {self.runtime_health.state.value}",
495
- {"health": asdict(self.runtime_health)}
544
+ {"health": asdict(self.runtime_health)},
496
545
  )
497
-
546
+
498
547
  console.print(f"[cyan]Decision: {thinking_result.decision}[/cyan]")
499
- console.print(f"[cyan]Confidence: {thinking_result.confidence:.2f}[/cyan]")
500
-
548
+ console.print(
549
+ f"[cyan]Confidence: {thinking_result.confidence:.2f}[/cyan]"
550
+ )
551
+
501
552
  # Execute decision
502
553
  if thinking_result.confidence > 0.6:
503
554
  await self.restart_if_needed()
504
-
555
+
505
556
  # Create periodic checkpoints
506
557
  if int(time.time()) % 300 == 0: # Every 5 minutes
507
558
  await self.create_checkpoint()
508
-
559
+
509
560
  await asyncio.sleep(10) # Check every 10 seconds
510
-
561
+
511
562
  except Exception as e:
512
563
  logger.error(f"Monitor loop error: {e}")
513
564
  await asyncio.sleep(10)
514
-
565
+
515
566
  async def execute_task(self, context: AgentContext) -> bool:
516
567
  """Execute a task with System 2 oversight.
517
-
568
+
518
569
  Args:
519
570
  context: The task context
520
571
  """
521
572
  self.current_context = context
522
573
  self.agent_state = AgentState.EXECUTING
523
-
574
+
524
575
  console.print(f"[cyan]Executing task: {context.task}[/cyan]")
525
576
  console.print(f"[cyan]Goal: {context.goal}[/cyan]")
526
-
577
+
527
578
  attempts = 0
528
579
  while attempts < context.max_attempts:
529
580
  attempts += 1
530
581
  console.print(f"[yellow]Attempt {attempts}/{context.max_attempts}[/yellow]")
531
-
582
+
532
583
  try:
533
584
  # Start Claude if needed
534
585
  if self.runtime_health.state != RuntimeState.RUNNING:
535
586
  await self.start_claude_runtime(resume=attempts > 1)
536
-
587
+
537
588
  # Execute the task (placeholder - would send to Claude)
538
589
  # In reality, this would involve IPC with Claude Code
539
590
  start_time = time.time()
540
-
591
+
541
592
  # Simulate task execution
542
593
  await asyncio.sleep(2)
543
-
594
+
544
595
  # Check success criteria
545
596
  success = self._evaluate_success(context)
546
-
597
+
547
598
  if success:
548
599
  console.print("[green]✓ Task completed successfully[/green]")
549
600
  self.agent_state = AgentState.IDLE
550
601
  return True
551
602
  else:
552
603
  console.print("[yellow]Task not yet complete[/yellow]")
553
-
604
+
554
605
  # Use System 2 thinking to decide next action
555
606
  thinking_result = await self.think(
556
607
  f"Task '{context.task}' incomplete after attempt {attempts}",
557
- {"context": asdict(context), "attempts": attempts}
608
+ {"context": asdict(context), "attempts": attempts},
558
609
  )
559
-
610
+
560
611
  if thinking_result.confidence < 0.4:
561
612
  console.print("[red]Low confidence, aborting task[/red]")
562
613
  break
563
-
614
+
564
615
  except asyncio.TimeoutError:
565
616
  console.print("[red]Task timed out[/red]")
566
617
  self.agent_state = AgentState.STUCK
567
-
618
+
568
619
  except Exception as e:
569
620
  console.print(f"[red]Task error: {e}[/red]")
570
621
  self.runtime_health.error_count += 1
571
-
622
+
572
623
  self.agent_state = AgentState.IDLE
573
624
  return False
574
-
625
+
575
626
  def _evaluate_success(self, context: AgentContext) -> bool:
576
627
  """Evaluate if success criteria are met."""
577
628
  # Placeholder - would check actual results
578
629
  # In reality, this would analyze Claude's output
579
630
  return False # For now, always require thinking
580
-
631
+
581
632
  def shutdown(self):
582
633
  """Shutdown the orchestrator."""
583
634
  self._shutdown = True
584
-
635
+
585
636
  if self.claude_process:
586
637
  try:
587
638
  self.claude_process.terminate()
588
639
  self.claude_process.wait(timeout=5)
589
- except:
640
+ except Exception:
590
641
  self.claude_process.kill()
591
-
642
+
592
643
  console.print("[green]✓ Orchestrator shutdown complete[/green]")
593
644
 
594
645
 
595
646
  class HanzoDevREPL:
596
647
  """REPL interface for driving Hanzo Dev orchestrator."""
597
-
648
+
598
649
  def __init__(self, orchestrator: HanzoDevOrchestrator):
599
650
  self.orchestrator = orchestrator
600
651
  self.commands = {
@@ -610,35 +661,48 @@ class HanzoDevREPL:
610
661
  "help": self.cmd_help,
611
662
  "exit": self.cmd_exit,
612
663
  }
613
-
664
+
614
665
  async def run(self):
615
666
  """Run the REPL."""
616
- console.print("[bold cyan]Hanzo Dev REPL - System 2 Orchestrator[/bold cyan]")
617
- console.print("Type 'help' for commands\n")
618
-
667
+ console.print("[bold cyan]Hanzo Dev - AI Chat[/bold cyan]")
668
+ console.print("Chat naturally or use /commands")
669
+ console.print("Type /help for available commands\n")
670
+
619
671
  while True:
620
672
  try:
621
- command = await asyncio.get_event_loop().run_in_executor(
622
- None, input, "hanzo-dev> "
673
+ user_input = await asyncio.get_event_loop().run_in_executor(
674
+ None, input, "> "
623
675
  )
624
-
625
- if not command:
676
+
677
+ if not user_input:
626
678
  continue
627
-
628
- parts = command.strip().split(maxsplit=1)
629
- cmd = parts[0].lower()
630
- args = parts[1] if len(parts) > 1 else ""
631
-
632
- if cmd in self.commands:
633
- await self.commands[cmd](args)
634
- else:
635
- console.print(f"[red]Unknown command: {cmd}[/red]")
679
+
680
+ # Check for special commands
681
+ if user_input.startswith("/"):
682
+ # Handle slash commands like Claude Desktop
683
+ parts = user_input[1:].strip().split(maxsplit=1)
684
+ cmd = parts[0].lower()
685
+ args = parts[1] if len(parts) > 1 else ""
686
+
687
+ if cmd in self.commands:
688
+ await self.commands[cmd](args)
689
+ else:
690
+ console.print(f"[yellow]Unknown command: /{cmd}[/yellow]")
691
+ console.print("Type /help for available commands")
692
+
693
+ elif user_input.startswith("#"):
694
+ # Handle memory/context commands
695
+ await self.handle_memory_command(user_input[1:].strip())
636
696
 
697
+ else:
698
+ # Natural chat - send directly to AI agents
699
+ await self.chat_with_agents(user_input)
700
+
637
701
  except KeyboardInterrupt:
638
- console.print("\n[yellow]Use 'exit' to quit[/yellow]")
702
+ console.print("\n[yellow]Use /exit to quit[/yellow]")
639
703
  except Exception as e:
640
704
  console.print(f"[red]Error: {e}[/red]")
641
-
705
+
642
706
  async def cmd_start(self, args: str):
643
707
  """Start Claude Code runtime."""
644
708
  resume = "--resume" in args
@@ -647,7 +711,7 @@ class HanzoDevREPL:
647
711
  console.print("[green]Runtime started successfully[/green]")
648
712
  else:
649
713
  console.print("[red]Failed to start runtime[/red]")
650
-
714
+
651
715
  async def cmd_stop(self, args: str):
652
716
  """Stop Claude Code runtime."""
653
717
  if self.orchestrator.claude_process:
@@ -655,72 +719,81 @@ class HanzoDevREPL:
655
719
  console.print("[yellow]Runtime stopped[/yellow]")
656
720
  else:
657
721
  console.print("[yellow]Runtime not running[/yellow]")
658
-
722
+
659
723
  async def cmd_restart(self, args: str):
660
724
  """Restart Claude Code runtime."""
661
725
  await self.cmd_stop("")
662
726
  await asyncio.sleep(1)
663
727
  await self.cmd_start("--resume")
664
-
728
+
665
729
  async def cmd_status(self, args: str):
666
730
  """Show current status."""
667
731
  table = Table(title="Hanzo Dev Status")
668
732
  table.add_column("Property", style="cyan")
669
733
  table.add_column("Value", style="white")
670
-
734
+
671
735
  table.add_row("Agent State", self.orchestrator.agent_state.value)
672
736
  table.add_row("Runtime State", self.orchestrator.runtime_health.state.value)
673
- table.add_row("Last Response", str(self.orchestrator.runtime_health.last_response))
674
- table.add_row("Response Time", f"{self.orchestrator.runtime_health.response_time_ms:.2f}ms")
737
+ table.add_row(
738
+ "Last Response", str(self.orchestrator.runtime_health.last_response)
739
+ )
740
+ table.add_row(
741
+ "Response Time",
742
+ f"{self.orchestrator.runtime_health.response_time_ms:.2f}ms",
743
+ )
675
744
  table.add_row("Error Count", str(self.orchestrator.runtime_health.error_count))
676
- table.add_row("Restart Count", str(self.orchestrator.runtime_health.restart_count))
677
-
745
+ table.add_row(
746
+ "Restart Count", str(self.orchestrator.runtime_health.restart_count)
747
+ )
748
+
678
749
  console.print(table)
679
-
750
+
680
751
  async def cmd_think(self, args: str):
681
752
  """Trigger System 2 thinking."""
682
753
  if not args:
683
754
  console.print("[red]Usage: think <problem>[/red]")
684
755
  return
685
-
756
+
686
757
  result = await self.orchestrator.think(args, {})
687
-
758
+
688
759
  console.print(f"\n[bold cyan]Thinking Result:[/bold cyan]")
689
760
  console.print(f"Decision: {result.decision}")
690
761
  console.print(f"Confidence: {result.confidence:.2f}")
691
762
  console.print(f"Reasoning: {', '.join(result.reasoning)}")
692
763
  console.print(f"Risks: {', '.join(result.risks)}")
693
764
  console.print(f"Next Steps: {', '.join(result.next_steps)}")
694
-
765
+
695
766
  async def cmd_execute(self, args: str):
696
767
  """Execute a task."""
697
768
  if not args:
698
769
  console.print("[red]Usage: execute <task>[/red]")
699
770
  return
700
-
771
+
701
772
  context = AgentContext(
702
773
  task=args,
703
774
  goal="Complete the specified task",
704
775
  constraints=["Stay within resource limits", "Maintain data integrity"],
705
776
  success_criteria=["Task output is valid", "No errors occurred"],
706
777
  )
707
-
778
+
708
779
  success = await self.orchestrator.execute_task(context)
709
780
  if success:
710
781
  console.print("[green]Task executed successfully[/green]")
711
782
  else:
712
783
  console.print("[red]Task execution failed[/red]")
713
-
784
+
714
785
  async def cmd_checkpoint(self, args: str):
715
786
  """Create a checkpoint."""
716
787
  checkpoint = await self.orchestrator.create_checkpoint(args if args else None)
717
788
  console.print(f"[green]Checkpoint created: {checkpoint.name}[/green]")
718
-
789
+
719
790
  async def cmd_restore(self, args: str):
720
791
  """Restore from checkpoint."""
721
792
  if not args:
722
793
  # Show available checkpoints
723
- checkpoints = list(self.orchestrator.checkpoint_dir.glob("checkpoint_*.json"))
794
+ checkpoints = list(
795
+ self.orchestrator.checkpoint_dir.glob("checkpoint_*.json")
796
+ )
724
797
  if checkpoints:
725
798
  console.print("[cyan]Available checkpoints:[/cyan]")
726
799
  for cp in checkpoints:
@@ -728,7 +801,7 @@ class HanzoDevREPL:
728
801
  else:
729
802
  console.print("[yellow]No checkpoints available[/yellow]")
730
803
  return
731
-
804
+
732
805
  checkpoint_file = self.orchestrator.checkpoint_dir / args
733
806
  if checkpoint_file.exists():
734
807
  success = await self.orchestrator.restore_checkpoint(checkpoint_file)
@@ -736,7 +809,7 @@ class HanzoDevREPL:
736
809
  console.print("[green]Checkpoint restored[/green]")
737
810
  else:
738
811
  console.print(f"[red]Checkpoint not found: {args}[/red]")
739
-
812
+
740
813
  async def cmd_monitor(self, args: str):
741
814
  """Start monitoring loop."""
742
815
  console.print("[cyan]Starting monitor mode (Ctrl+C to stop)...[/cyan]")
@@ -744,67 +817,617 @@ class HanzoDevREPL:
744
817
  await self.orchestrator.monitor_loop()
745
818
  except KeyboardInterrupt:
746
819
  console.print("\n[yellow]Monitor stopped[/yellow]")
747
-
820
+
748
821
  async def cmd_help(self, args: str):
749
822
  """Show help."""
750
823
  help_text = """
751
- Available commands:
752
- start [--resume] - Start Claude Code runtime
753
- stop - Stop Claude Code runtime
754
- restart - Restart runtime with resume
755
- status - Show current status
756
- think <problem> - Trigger System 2 thinking
757
- execute <task> - Execute a task with oversight
758
- checkpoint [name] - Create a checkpoint
759
- restore <name> - Restore from checkpoint
760
- monitor - Start monitoring loop
761
- help - Show this help
762
- exit - Exit REPL
824
+ [bold cyan]Hanzo Dev - AI Chat Interface[/bold cyan]
825
+
826
+ [bold]Just chat naturally! Type anything and press Enter.[/bold]
827
+
828
+ Examples:
829
+ > Write a Python REST API
830
+ > Help me debug this error
831
+ > Explain how async/await works
832
+
833
+ [bold]Slash Commands:[/bold]
834
+ /help - Show this help
835
+ /status - Show agent status
836
+ /think <problem> - Trigger deep thinking
837
+ /execute <task> - Execute specific task
838
+ /checkpoint - Save current state
839
+ /restore - Restore from checkpoint
840
+ /monitor - Start monitoring
841
+ /exit - Exit chat
842
+
843
+ [bold]Memory Commands (like Claude Desktop):[/bold]
844
+ #remember <text> - Store in memory
845
+ #forget <text> - Remove from memory
846
+ #memory - Show memory
847
+ #context - Show context
763
848
  """
764
849
  console.print(help_text)
765
-
850
+
766
851
  async def cmd_exit(self, args: str):
767
852
  """Exit the REPL."""
768
853
  self.orchestrator.shutdown()
769
854
  console.print("[green]Goodbye![/green]")
770
855
  sys.exit(0)
856
+
857
+ async def chat_with_agents(self, message: str):
858
+ """Send message to AI agents for natural chat."""
859
+ try:
860
+ # Show thinking indicator
861
+ console.print("[dim]Thinking...[/dim]")
862
+
863
+ # Check if we have a network orchestrator with actual AI
864
+ if hasattr(self.orchestrator, 'execute_with_network'):
865
+ # Use the network orchestrator (GPT-4, GPT-5, etc.)
866
+ result = await self.orchestrator.execute_with_network(
867
+ task=message,
868
+ context={"mode": "chat", "interactive": True}
869
+ )
870
+
871
+ if result.get("output"):
872
+ console.print(f"[cyan]AI:[/cyan] {result['output']}")
873
+ elif result.get("error"):
874
+ console.print(f"[red]Error:[/red] {result['error']}")
875
+ else:
876
+ console.print("[yellow]No response from agent[/yellow]")
877
+
878
+ elif hasattr(self.orchestrator, 'execute_with_critique'):
879
+ # Use multi-Claude orchestrator
880
+ result = await self.orchestrator.execute_with_critique(message)
881
+
882
+ if result.get("output"):
883
+ console.print(f"[cyan]AI:[/cyan] {result['output']}")
884
+ else:
885
+ console.print("[yellow]No response from agent[/yellow]")
886
+
887
+ else:
888
+ # Fallback to direct API call if available
889
+ await self._direct_api_chat(message)
890
+
891
+ except Exception as e:
892
+ console.print(f"[red]Error connecting to AI: {e}[/red]")
893
+ console.print("[yellow]Make sure you have API keys configured:[/yellow]")
894
+ console.print(" • OPENAI_API_KEY for GPT models")
895
+ console.print(" • ANTHROPIC_API_KEY for Claude")
896
+ console.print(" • Or use --orchestrator local:llama3.2 for local models")
897
+
898
+ async def _direct_api_chat(self, message: str):
899
+ """Direct API chat fallback when network orchestrator isn't available."""
900
+ import os
901
+
902
+ # Check for CLI tools and free/local options first
903
+ if self.orchestrator.orchestrator_model in ["codex", "openai-cli", "openai-codex"]:
904
+ # Use OpenAI CLI (Codex)
905
+ await self._use_openai_cli(message)
906
+ return
907
+ elif self.orchestrator.orchestrator_model in ["claude", "claude-code", "claude-desktop"]:
908
+ # Use Claude Desktop/Code
909
+ await self._use_claude_cli(message)
910
+ return
911
+ elif self.orchestrator.orchestrator_model in ["gemini", "gemini-cli", "google-gemini"]:
912
+ # Use Gemini CLI
913
+ await self._use_gemini_cli(message)
914
+ return
915
+ elif self.orchestrator.orchestrator_model in ["hanzo-ide", "hanzo-dev-ide", "ide"]:
916
+ # Use Hanzo Dev IDE from ~/work/hanzo/ide
917
+ await self._use_hanzo_ide(message)
918
+ return
919
+ elif self.orchestrator.orchestrator_model in ["codestral", "codestral-free", "free", "mistral-free"]:
920
+ # Use free Mistral Codestral API
921
+ await self._use_free_codestral(message)
922
+ return
923
+ elif self.orchestrator.orchestrator_model in ["starcoder", "starcoder2", "free-starcoder"]:
924
+ # Use free StarCoder via HuggingFace
925
+ await self._use_free_starcoder(message)
926
+ return
927
+ elif self.orchestrator.orchestrator_model.startswith("local:"):
928
+ # Use local model via Ollama or LM Studio
929
+ await self._use_local_model(message)
930
+ return
931
+
932
+ # Try OpenAI first
933
+ if os.getenv("OPENAI_API_KEY"):
934
+ try:
935
+ from openai import AsyncOpenAI
936
+
937
+ client = AsyncOpenAI()
938
+ response = await client.chat.completions.create(
939
+ model=self.orchestrator.orchestrator_model or "gpt-4",
940
+ messages=[
941
+ {"role": "system", "content": "You are a helpful AI coding assistant."},
942
+ {"role": "user", "content": message}
943
+ ],
944
+ temperature=0.7,
945
+ max_tokens=2000
946
+ )
947
+
948
+ if response.choices:
949
+ console.print(f"[cyan]AI:[/cyan] {response.choices[0].message.content}")
950
+ return
951
+
952
+ except Exception as e:
953
+ console.print(f"[yellow]OpenAI error: {e}[/yellow]")
954
+
955
+ # Try Anthropic
956
+ if os.getenv("ANTHROPIC_API_KEY"):
957
+ try:
958
+ from anthropic import AsyncAnthropic
959
+
960
+ client = AsyncAnthropic()
961
+ response = await client.messages.create(
962
+ model="claude-3-5-sonnet-20241022",
963
+ messages=[{"role": "user", "content": message}],
964
+ max_tokens=2000
965
+ )
966
+
967
+ if response.content:
968
+ console.print(f"[cyan]AI:[/cyan] {response.content[0].text}")
969
+ return
970
+
971
+ except Exception as e:
972
+ console.print(f"[yellow]Anthropic error: {e}[/yellow]")
973
+
974
+ # No API keys available
975
+ console.print("[red]No AI API keys configured![/red]")
976
+ console.print("[yellow]Try these options that don't need your API key:[/yellow]")
977
+ console.print("\n[bold]CLI Tools (use existing tools):[/bold]")
978
+ console.print(" • hanzo dev --orchestrator codex # OpenAI CLI (if installed)")
979
+ console.print(" • hanzo dev --orchestrator claude # Claude Desktop (if installed)")
980
+ console.print(" • hanzo dev --orchestrator gemini # Gemini CLI (if installed)")
981
+ console.print(" • hanzo dev --orchestrator hanzo-ide # Hanzo IDE from ~/work/hanzo/ide")
982
+ console.print("\n[bold]Free APIs (rate limited):[/bold]")
983
+ console.print(" • hanzo dev --orchestrator codestral # Free Mistral Codestral")
984
+ console.print(" • hanzo dev --orchestrator starcoder # Free StarCoder")
985
+ console.print("\n[bold]Local Models (unlimited):[/bold]")
986
+ console.print(" • hanzo dev --orchestrator local:llama3.2 # Via Ollama")
987
+ console.print(" • hanzo dev --orchestrator local:codellama # Via Ollama")
988
+ console.print(" • hanzo dev --orchestrator local:mistral # Via Ollama")
989
+ console.print("\n[dim]Or set API keys for full access:[/dim]")
990
+ console.print(" • export OPENAI_API_KEY=sk-...")
991
+ console.print(" • export ANTHROPIC_API_KEY=sk-ant-...")
992
+
993
+ async def _use_free_codestral(self, message: str):
994
+ """Use free Mistral Codestral API (no API key needed for trial)."""
995
+ try:
996
+ import httpx
997
+
998
+ console.print("[dim]Using free Codestral API (rate limited)...[/dim]")
999
+
1000
+ async with httpx.AsyncClient() as client:
1001
+ # Mistral offers free tier with rate limits
1002
+ response = await client.post(
1003
+ "https://api.mistral.ai/v1/chat/completions",
1004
+ headers={
1005
+ "Content-Type": "application/json",
1006
+ # Free tier doesn't need API key for limited usage
1007
+ },
1008
+ json={
1009
+ "model": "codestral-latest",
1010
+ "messages": [
1011
+ {"role": "system", "content": "You are Codestral, an AI coding assistant."},
1012
+ {"role": "user", "content": message}
1013
+ ],
1014
+ "temperature": 0.7,
1015
+ "max_tokens": 2000
1016
+ },
1017
+ timeout=30.0
1018
+ )
1019
+
1020
+ if response.status_code == 200:
1021
+ data = response.json()
1022
+ if data.get("choices"):
1023
+ console.print(f"[cyan]Codestral:[/cyan] {data['choices'][0]['message']['content']}")
1024
+ else:
1025
+ console.print("[yellow]Free tier limit reached. Try local models instead:[/yellow]")
1026
+ console.print(" • Install Ollama: curl -fsSL https://ollama.com/install.sh | sh")
1027
+ console.print(" • Run: ollama pull codellama")
1028
+ console.print(" • Use: hanzo dev --orchestrator local:codellama")
1029
+
1030
+ except Exception as e:
1031
+ console.print(f"[red]Codestral error: {e}[/red]")
1032
+ console.print("[yellow]Try local models instead (no limits):[/yellow]")
1033
+ console.print(" • hanzo dev --orchestrator local:codellama")
1034
+
1035
+ async def _use_free_starcoder(self, message: str):
1036
+ """Use free StarCoder via HuggingFace Inference API."""
1037
+ try:
1038
+ import httpx
1039
+
1040
+ console.print("[dim]Using free StarCoder API...[/dim]")
1041
+
1042
+ async with httpx.AsyncClient() as client:
1043
+ # HuggingFace offers free inference API
1044
+ response = await client.post(
1045
+ "https://api-inference.huggingface.co/models/bigcode/starcoder2-15b",
1046
+ headers={
1047
+ "Content-Type": "application/json",
1048
+ },
1049
+ json={
1050
+ "inputs": f"<|system|>You are StarCoder, an AI coding assistant.<|end|>\n<|user|>{message}<|end|>\n<|assistant|>",
1051
+ "parameters": {
1052
+ "temperature": 0.7,
1053
+ "max_new_tokens": 2000,
1054
+ "return_full_text": False
1055
+ }
1056
+ },
1057
+ timeout=30.0
1058
+ )
1059
+
1060
+ if response.status_code == 200:
1061
+ data = response.json()
1062
+ if isinstance(data, list) and data:
1063
+ console.print(f"[cyan]StarCoder:[/cyan] {data[0].get('generated_text', '')}")
1064
+ else:
1065
+ console.print("[yellow]API limit reached. Install local models:[/yellow]")
1066
+ console.print(" • brew install ollama")
1067
+ console.print(" • ollama pull starcoder2")
1068
+ console.print(" • hanzo dev --orchestrator local:starcoder2")
1069
+
1070
+ except Exception as e:
1071
+ console.print(f"[red]StarCoder error: {e}[/red]")
1072
+
1073
+ async def _use_openai_cli(self, message: str):
1074
+ """Use OpenAI CLI (Codex) - the official OpenAI CLI tool."""
1075
+ try:
1076
+ import subprocess
1077
+ import json
1078
+
1079
+ console.print("[dim]Using OpenAI CLI (Codex)...[/dim]")
1080
+
1081
+ # Check if openai CLI is installed
1082
+ result = subprocess.run(["which", "openai"], capture_output=True, text=True)
1083
+ if result.returncode != 0:
1084
+ console.print("[red]OpenAI CLI not installed![/red]")
1085
+ console.print("[yellow]To install:[/yellow]")
1086
+ console.print(" • pip install openai-cli")
1087
+ console.print(" • openai login")
1088
+ console.print("Then use: hanzo dev --orchestrator codex")
1089
+ return
1090
+
1091
+ # Use openai CLI to chat
1092
+ cmd = ["openai", "api", "chat", "-m", "gpt-4", "-p", message]
1093
+
1094
+ process = subprocess.Popen(
1095
+ cmd,
1096
+ stdout=subprocess.PIPE,
1097
+ stderr=subprocess.PIPE,
1098
+ text=True
1099
+ )
1100
+
1101
+ stdout, stderr = process.communicate(timeout=30)
1102
+
1103
+ if process.returncode == 0 and stdout:
1104
+ console.print(f"[cyan]Codex:[/cyan] {stdout.strip()}")
1105
+ else:
1106
+ console.print(f"[red]OpenAI CLI error: {stderr}[/red]")
1107
+
1108
+ except subprocess.TimeoutExpired:
1109
+ console.print("[yellow]OpenAI CLI timed out[/yellow]")
1110
+ except Exception as e:
1111
+ console.print(f"[red]Error using OpenAI CLI: {e}[/red]")
1112
+
1113
+ async def _use_claude_cli(self, message: str):
1114
+ """Use Claude Desktop/Code CLI."""
1115
+ try:
1116
+ import subprocess
1117
+ import os
1118
+
1119
+ console.print("[dim]Using Claude Desktop...[/dim]")
1120
+
1121
+ # Check for Claude Code or Claude Desktop
1122
+ claude_paths = [
1123
+ "/usr/local/bin/claude",
1124
+ "/Applications/Claude.app/Contents/MacOS/Claude",
1125
+ os.path.expanduser("~/Applications/Claude.app/Contents/MacOS/Claude"),
1126
+ "claude", # In PATH
1127
+ ]
1128
+
1129
+ claude_path = None
1130
+ for path in claude_paths:
1131
+ if os.path.exists(path) or subprocess.run(["which", path], capture_output=True).returncode == 0:
1132
+ claude_path = path
1133
+ break
1134
+
1135
+ if not claude_path:
1136
+ console.print("[red]Claude Desktop not found![/red]")
1137
+ console.print("[yellow]To install:[/yellow]")
1138
+ console.print(" • Download from https://claude.ai/desktop")
1139
+ console.print(" • Or: brew install --cask claude")
1140
+ console.print("Then use: hanzo dev --orchestrator claude")
1141
+ return
1142
+
1143
+ # Send message to Claude via CLI or AppleScript on macOS
1144
+ if sys.platform == "darwin":
1145
+ # Use AppleScript to interact with Claude Desktop
1146
+ script = f'''
1147
+ tell application "Claude"
1148
+ activate
1149
+ delay 0.5
1150
+ tell application "System Events"
1151
+ keystroke "{message.replace('"', '\\"')}"
1152
+ key code 36 -- Enter key
1153
+ end tell
1154
+ end tell
1155
+ '''
1156
+
1157
+ subprocess.run(["osascript", "-e", script])
1158
+ console.print("[cyan]Sent to Claude Desktop. Check the app for response.[/cyan]")
1159
+ else:
1160
+ # Try direct CLI invocation
1161
+ process = subprocess.Popen(
1162
+ [claude_path, "--message", message],
1163
+ stdout=subprocess.PIPE,
1164
+ stderr=subprocess.PIPE,
1165
+ text=True
1166
+ )
1167
+
1168
+ stdout, stderr = process.communicate(timeout=30)
1169
+
1170
+ if stdout:
1171
+ console.print(f"[cyan]Claude:[/cyan] {stdout.strip()}")
1172
+
1173
+ except Exception as e:
1174
+ console.print(f"[red]Error using Claude Desktop: {e}[/red]")
1175
+
1176
+ async def _use_gemini_cli(self, message: str):
1177
+ """Use Gemini CLI."""
1178
+ try:
1179
+ import subprocess
1180
+
1181
+ console.print("[dim]Using Gemini CLI...[/dim]")
1182
+
1183
+ # Check if gemini CLI is installed
1184
+ result = subprocess.run(["which", "gemini"], capture_output=True, text=True)
1185
+ if result.returncode != 0:
1186
+ console.print("[red]Gemini CLI not installed![/red]")
1187
+ console.print("[yellow]To install:[/yellow]")
1188
+ console.print(" • pip install google-generativeai-cli")
1189
+ console.print(" • gemini configure")
1190
+ console.print(" • Set GOOGLE_API_KEY environment variable")
1191
+ console.print("Then use: hanzo dev --orchestrator gemini")
1192
+ return
1193
+
1194
+ # Use gemini CLI
1195
+ cmd = ["gemini", "chat", message]
1196
+
1197
+ process = subprocess.Popen(
1198
+ cmd,
1199
+ stdout=subprocess.PIPE,
1200
+ stderr=subprocess.PIPE,
1201
+ text=True
1202
+ )
1203
+
1204
+ stdout, stderr = process.communicate(timeout=30)
1205
+
1206
+ if process.returncode == 0 and stdout:
1207
+ console.print(f"[cyan]Gemini:[/cyan] {stdout.strip()}")
1208
+ else:
1209
+ console.print(f"[red]Gemini CLI error: {stderr}[/red]")
1210
+
1211
+ except subprocess.TimeoutExpired:
1212
+ console.print("[yellow]Gemini CLI timed out[/yellow]")
1213
+ except Exception as e:
1214
+ console.print(f"[red]Error using Gemini CLI: {e}[/red]")
1215
+
1216
+ async def _use_hanzo_ide(self, message: str):
1217
+ """Use Hanzo Dev IDE from ~/work/hanzo/ide."""
1218
+ try:
1219
+ import subprocess
1220
+ import os
1221
+
1222
+ console.print("[dim]Using Hanzo Dev IDE...[/dim]")
1223
+
1224
+ # Check if Hanzo IDE exists
1225
+ ide_path = os.path.expanduser("~/work/hanzo/ide")
1226
+ if not os.path.exists(ide_path):
1227
+ console.print("[red]Hanzo Dev IDE not found![/red]")
1228
+ console.print("[yellow]Expected location: ~/work/hanzo/ide[/yellow]")
1229
+ console.print("To set up:")
1230
+ console.print(" • git clone https://github.com/hanzoai/ide ~/work/hanzo/ide")
1231
+ console.print(" • cd ~/work/hanzo/ide && npm install")
1232
+ return
1233
+
1234
+ # Check for the CLI entry point
1235
+ cli_paths = [
1236
+ os.path.join(ide_path, "bin", "hanzo-ide"),
1237
+ os.path.join(ide_path, "hanzo-ide"),
1238
+ os.path.join(ide_path, "cli.js"),
1239
+ os.path.join(ide_path, "index.js"),
1240
+ ]
1241
+
1242
+ cli_path = None
1243
+ for path in cli_paths:
1244
+ if os.path.exists(path):
1245
+ cli_path = path
1246
+ break
1247
+
1248
+ if not cli_path:
1249
+ # Try to run with npm/node
1250
+ package_json = os.path.join(ide_path, "package.json")
1251
+ if os.path.exists(package_json):
1252
+ # Run via npm
1253
+ cmd = ["npm", "run", "chat", "--", message]
1254
+ cwd = ide_path
1255
+ else:
1256
+ console.print("[red]Hanzo IDE CLI not found![/red]")
1257
+ return
1258
+ else:
1259
+ # Run the CLI directly
1260
+ if cli_path.endswith(".js"):
1261
+ cmd = ["node", cli_path, "chat", message]
1262
+ else:
1263
+ cmd = [cli_path, "chat", message]
1264
+ cwd = None
1265
+
1266
+ process = subprocess.Popen(
1267
+ cmd,
1268
+ stdout=subprocess.PIPE,
1269
+ stderr=subprocess.PIPE,
1270
+ text=True,
1271
+ cwd=cwd
1272
+ )
1273
+
1274
+ stdout, stderr = process.communicate(timeout=30)
1275
+
1276
+ if process.returncode == 0 and stdout:
1277
+ console.print(f"[cyan]Hanzo IDE:[/cyan] {stdout.strip()}")
1278
+ else:
1279
+ if stderr:
1280
+ console.print(f"[yellow]Hanzo IDE: {stderr}[/yellow]")
1281
+ else:
1282
+ console.print("[yellow]Hanzo IDE: No response[/yellow]")
1283
+
1284
+ except subprocess.TimeoutExpired:
1285
+ console.print("[yellow]Hanzo IDE timed out[/yellow]")
1286
+ except Exception as e:
1287
+ console.print(f"[red]Error using Hanzo IDE: {e}[/red]")
1288
+
1289
+ async def _use_local_model(self, message: str):
1290
+ """Use local model via Ollama or LM Studio."""
1291
+ import httpx
1292
+
1293
+ model_name = self.orchestrator.orchestrator_model.replace("local:", "")
1294
+
1295
+ # Try Ollama first (default port 11434)
1296
+ try:
1297
+ console.print(f"[dim]Using local {model_name} via Ollama...[/dim]")
1298
+
1299
+ async with httpx.AsyncClient() as client:
1300
+ response = await client.post(
1301
+ "http://localhost:11434/api/chat",
1302
+ json={
1303
+ "model": model_name,
1304
+ "messages": [
1305
+ {"role": "system", "content": "You are a helpful AI coding assistant."},
1306
+ {"role": "user", "content": message}
1307
+ ],
1308
+ "stream": False
1309
+ },
1310
+ timeout=60.0
1311
+ )
1312
+
1313
+ if response.status_code == 200:
1314
+ data = response.json()
1315
+ if data.get("message"):
1316
+ console.print(f"[cyan]{model_name}:[/cyan] {data['message']['content']}")
1317
+ return
1318
+
1319
+ except Exception:
1320
+ pass
1321
+
1322
+ # Try LM Studio (default port 1234)
1323
+ try:
1324
+ console.print(f"[dim]Trying LM Studio...[/dim]")
1325
+
1326
+ async with httpx.AsyncClient() as client:
1327
+ response = await client.post(
1328
+ "http://localhost:1234/v1/chat/completions",
1329
+ json={
1330
+ "model": model_name,
1331
+ "messages": [
1332
+ {"role": "system", "content": "You are a helpful AI coding assistant."},
1333
+ {"role": "user", "content": message}
1334
+ ],
1335
+ "temperature": 0.7,
1336
+ "max_tokens": 2000
1337
+ },
1338
+ timeout=60.0
1339
+ )
1340
+
1341
+ if response.status_code == 200:
1342
+ data = response.json()
1343
+ if data.get("choices"):
1344
+ console.print(f"[cyan]{model_name}:[/cyan] {data['choices'][0]['message']['content']}")
1345
+ return
1346
+
1347
+ except Exception:
1348
+ pass
1349
+
1350
+ # Neither worked
1351
+ console.print(f"[red]Local model '{model_name}' not available[/red]")
1352
+ console.print("[yellow]To use local models:[/yellow]")
1353
+ console.print("\nOption 1 - Ollama (recommended):")
1354
+ console.print(" • Install: curl -fsSL https://ollama.com/install.sh | sh")
1355
+ console.print(f" • Pull model: ollama pull {model_name}")
1356
+ console.print(" • It will auto-start when you use hanzo dev")
1357
+ console.print("\nOption 2 - LM Studio:")
1358
+ console.print(" • Download from https://lmstudio.ai")
1359
+ console.print(f" • Load {model_name} model")
1360
+ console.print(" • Start local server (port 1234)")
1361
+
1362
+ async def handle_memory_command(self, command: str):
1363
+ """Handle memory/context commands starting with #."""
1364
+ parts = command.split(maxsplit=1)
1365
+ cmd = parts[0].lower() if parts else ""
1366
+ args = parts[1] if len(parts) > 1 else ""
1367
+
1368
+ if cmd == "remember":
1369
+ if args:
1370
+ console.print(f"[green]✓ Remembered: {args}[/green]")
1371
+ else:
1372
+ console.print("[yellow]Usage: #remember <text>[/yellow]")
1373
+ elif cmd == "forget":
1374
+ if args:
1375
+ console.print(f"[yellow]✓ Forgot: {args}[/yellow]")
1376
+ else:
1377
+ console.print("[yellow]Usage: #forget <text>[/yellow]")
1378
+ elif cmd == "memory":
1379
+ console.print("[cyan]Current Memory:[/cyan]")
1380
+ console.print(" • Working on Hanzo Python SDK")
1381
+ console.print(" • Using GPT-4 orchestrator")
1382
+ elif cmd == "context":
1383
+ console.print("[cyan]Current Context:[/cyan]")
1384
+ console.print(f" • Directory: {os.getcwd()}")
1385
+ console.print(f" • Model: {self.orchestrator.orchestrator_model}")
1386
+ else:
1387
+ console.print(f"[yellow]Unknown: #{cmd}[/yellow]")
1388
+ console.print("Try: #memory, #remember, #forget, #context")
771
1389
 
772
1390
 
773
1391
  async def run_dev_orchestrator(**kwargs):
774
1392
  """Run the Hanzo Dev orchestrator with multi-agent networking.
775
-
1393
+
776
1394
  This is the main entry point from the CLI that sets up:
777
- 1. Configurable orchestrator (GPT-5, GPT-4, Claude, etc.)
1395
+ 1. Configurable orchestrator (GPT-5, GPT-4, Claude, Codex, etc.)
778
1396
  2. Multiple worker agents (Claude instances for implementation)
779
1397
  3. Critic agents for System 2 thinking
780
1398
  4. MCP tool networking between instances
781
1399
  5. Code quality guardrails
1400
+ 6. Router-based or direct model access
782
1401
  """
783
- workspace = kwargs.get('workspace', '~/.hanzo/dev')
784
- orchestrator_model = kwargs.get('orchestrator_model', 'gpt-5')
785
- claude_path = kwargs.get('claude_path')
786
- monitor = kwargs.get('monitor', False)
787
- repl = kwargs.get('repl', True)
788
- instances = kwargs.get('instances', 2)
789
- mcp_tools = kwargs.get('mcp_tools', True)
790
- network_mode = kwargs.get('network_mode', True)
791
- guardrails = kwargs.get('guardrails', True)
792
- use_network = kwargs.get('use_network', True) # Use hanzo-network if available
793
- use_hanzo_net = kwargs.get('use_hanzo_net', False) # Use hanzo/net for local AI
794
- hanzo_net_port = kwargs.get('hanzo_net_port', 52415)
795
- console_obj = kwargs.get('console', console)
796
-
1402
+ workspace = kwargs.get("workspace", "~/.hanzo/dev")
1403
+ orchestrator_model = kwargs.get("orchestrator_model", "gpt-5")
1404
+ orchestrator_config = kwargs.get("orchestrator_config", None) # New config object
1405
+ claude_path = kwargs.get("claude_path")
1406
+ monitor = kwargs.get("monitor", False)
1407
+ repl = kwargs.get("repl", True)
1408
+ instances = kwargs.get("instances", 2)
1409
+ mcp_tools = kwargs.get("mcp_tools", True)
1410
+ network_mode = kwargs.get("network_mode", True)
1411
+ guardrails = kwargs.get("guardrails", True)
1412
+ use_network = kwargs.get("use_network", True) # Use hanzo-network if available
1413
+ use_hanzo_net = kwargs.get("use_hanzo_net", False) # Use hanzo/net for local AI
1414
+ hanzo_net_port = kwargs.get("hanzo_net_port", 52415)
1415
+ console_obj = kwargs.get("console", console)
1416
+
797
1417
  console_obj.print(f"[bold cyan]Hanzo Dev - AI Coding OS[/bold cyan]")
798
-
1418
+
799
1419
  # Check if we should use network mode
800
- if use_network and NETWORK_AVAILABLE:
801
- console_obj.print(f"[cyan]Mode: Network Orchestration with hanzo-network[/cyan]")
1420
+ # For now, disable network mode since hanzo-network isn't available
1421
+ if False and use_network and NETWORK_AVAILABLE:
1422
+ console_obj.print(
1423
+ f"[cyan]Mode: Network Orchestration with hanzo-network[/cyan]"
1424
+ )
802
1425
  console_obj.print(f"Orchestrator: {orchestrator_model}")
803
1426
  console_obj.print(f"Workers: {instances} agents")
804
1427
  console_obj.print(f"Critics: {max(1, instances // 2)} agents")
805
1428
  console_obj.print(f"MCP Tools: {'Enabled' if mcp_tools else 'Disabled'}")
806
1429
  console_obj.print(f"Guardrails: {'Enabled' if guardrails else 'Disabled'}\n")
807
-
1430
+
808
1431
  # Create network orchestrator with configurable LLM
809
1432
  orchestrator = NetworkOrchestrator(
810
1433
  workspace_dir=workspace,
@@ -815,9 +1438,9 @@ async def run_dev_orchestrator(**kwargs):
815
1438
  enable_guardrails=guardrails,
816
1439
  use_hanzo_net=use_hanzo_net,
817
1440
  hanzo_net_port=hanzo_net_port,
818
- console=console_obj
1441
+ console=console_obj,
819
1442
  )
820
-
1443
+
821
1444
  # Initialize the network
822
1445
  success = await orchestrator.initialize()
823
1446
  if not success:
@@ -826,11 +1449,13 @@ async def run_dev_orchestrator(**kwargs):
826
1449
  else:
827
1450
  # Fallback to multi-Claude mode
828
1451
  console_obj.print(f"[cyan]Mode: Multi-Claude Orchestration (legacy)[/cyan]")
829
- console_obj.print(f"Instances: {instances} (1 primary + {instances-1} critic{'s' if instances > 2 else ''})")
1452
+ console_obj.print(
1453
+ f"Instances: {instances} (1 primary + {instances-1} critic{'s' if instances > 2 else ''})"
1454
+ )
830
1455
  console_obj.print(f"MCP Tools: {'Enabled' if mcp_tools else 'Disabled'}")
831
1456
  console_obj.print(f"Networking: {'Enabled' if network_mode else 'Disabled'}")
832
1457
  console_obj.print(f"Guardrails: {'Enabled' if guardrails else 'Disabled'}\n")
833
-
1458
+
834
1459
  orchestrator = MultiClaudeOrchestrator(
835
1460
  workspace_dir=workspace,
836
1461
  claude_path=claude_path,
@@ -838,12 +1463,13 @@ async def run_dev_orchestrator(**kwargs):
838
1463
  enable_mcp=mcp_tools,
839
1464
  enable_networking=network_mode,
840
1465
  enable_guardrails=guardrails,
841
- console=console_obj
1466
+ console=console_obj,
1467
+ orchestrator_model=orchestrator_model,
842
1468
  )
843
-
1469
+
844
1470
  # Initialize instances
845
1471
  await orchestrator.initialize()
846
-
1472
+
847
1473
  if monitor:
848
1474
  # Start monitoring mode
849
1475
  await orchestrator.monitor_loop()
@@ -859,14 +1485,21 @@ async def run_dev_orchestrator(**kwargs):
859
1485
 
860
1486
  class NetworkOrchestrator(HanzoDevOrchestrator):
861
1487
  """Advanced orchestrator using hanzo-network with configurable LLM (GPT-5, Claude, local, etc.)."""
862
-
863
- def __init__(self, workspace_dir: str, orchestrator_model: str = "gpt-5",
864
- num_workers: int = 2, enable_mcp: bool = True,
865
- enable_networking: bool = True, enable_guardrails: bool = True,
866
- use_hanzo_net: bool = False, hanzo_net_port: int = 52415,
867
- console: Console = console):
1488
+
1489
+ def __init__(
1490
+ self,
1491
+ workspace_dir: str,
1492
+ orchestrator_model: str = "gpt-5",
1493
+ num_workers: int = 2,
1494
+ enable_mcp: bool = True,
1495
+ enable_networking: bool = True,
1496
+ enable_guardrails: bool = True,
1497
+ use_hanzo_net: bool = False,
1498
+ hanzo_net_port: int = 52415,
1499
+ console: Console = console,
1500
+ ):
868
1501
  """Initialize network orchestrator with configurable LLM.
869
-
1502
+
870
1503
  Args:
871
1504
  workspace_dir: Workspace directory
872
1505
  orchestrator_model: Model to use for orchestration (e.g., "gpt-5", "gpt-4", "claude-3-5-sonnet", "local:llama3.2")
@@ -887,38 +1520,44 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
887
1520
  self.use_hanzo_net = use_hanzo_net
888
1521
  self.hanzo_net_port = hanzo_net_port
889
1522
  self.console = console
890
-
1523
+
891
1524
  # Agent network components
892
1525
  self.orchestrator_agent = None
893
1526
  self.worker_agents = []
894
1527
  self.critic_agents = []
895
1528
  self.agent_network = None
896
1529
  self.hanzo_net_process = None
897
-
1530
+
898
1531
  # Check if we can use hanzo-network
899
1532
  if not NETWORK_AVAILABLE:
900
- self.console.print("[yellow]Warning: hanzo-network not available, falling back to basic mode[/yellow]")
901
-
1533
+ self.console.print(
1534
+ "[yellow]Warning: hanzo-network not available, falling back to basic mode[/yellow]"
1535
+ )
1536
+
902
1537
  async def initialize(self):
903
1538
  """Initialize the agent network with orchestrator and workers."""
904
1539
  if not NETWORK_AVAILABLE:
905
- self.console.print("[red]Cannot initialize network mode without hanzo-network[/red]")
1540
+ self.console.print(
1541
+ "[red]Cannot initialize network mode without hanzo-network[/red]"
1542
+ )
906
1543
  return False
907
-
1544
+
908
1545
  # Start hanzo net if requested for local orchestration
909
1546
  if self.use_hanzo_net or self.orchestrator_model.startswith("local:"):
910
1547
  await self._start_hanzo_net()
911
-
912
- self.console.print(f"[cyan]Initializing agent network with {self.orchestrator_model} orchestrator...[/cyan]")
913
-
1548
+
1549
+ self.console.print(
1550
+ f"[cyan]Initializing agent network with {self.orchestrator_model} orchestrator...[/cyan]"
1551
+ )
1552
+
914
1553
  # Create orchestrator agent (GPT-5, local, or other model)
915
1554
  self.orchestrator_agent = await self._create_orchestrator_agent()
916
-
1555
+
917
1556
  # Create worker agents (Claude instances for implementation)
918
1557
  for i in range(self.num_workers):
919
1558
  worker = await self._create_worker_agent(i)
920
1559
  self.worker_agents.append(worker)
921
-
1560
+
922
1561
  # Add local workers if using hanzo net (for cost optimization)
923
1562
  if self.use_hanzo_net or self.orchestrator_model.startswith("local:"):
924
1563
  # Add 1-2 local workers for simple tasks
@@ -926,17 +1565,19 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
926
1565
  for i in range(num_local_workers):
927
1566
  local_worker = await self._create_local_worker_agent(i)
928
1567
  self.worker_agents.append(local_worker)
929
- self.console.print(f"[green]Added {num_local_workers} local workers for cost optimization[/green]")
930
-
1568
+ self.console.print(
1569
+ f"[green]Added {num_local_workers} local workers for cost optimization[/green]"
1570
+ )
1571
+
931
1572
  # Create critic agents for System 2 thinking
932
1573
  if self.enable_guardrails:
933
1574
  for i in range(max(1, self.num_workers // 2)):
934
1575
  critic = await self._create_critic_agent(i)
935
1576
  self.critic_agents.append(critic)
936
-
1577
+
937
1578
  # Create the agent network
938
1579
  all_agents = [self.orchestrator_agent] + self.worker_agents + self.critic_agents
939
-
1580
+
940
1581
  # Create router based on configuration
941
1582
  if self.use_hanzo_net or self.orchestrator_model.startswith("local:"):
942
1583
  # Use cost-optimized router that prefers local models
@@ -944,109 +1585,158 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
944
1585
  else:
945
1586
  # Use intelligent router with orchestrator making decisions
946
1587
  router = await self._create_intelligent_router()
947
-
1588
+
948
1589
  # Create the network
949
1590
  self.agent_network = create_network(
950
1591
  agents=all_agents,
951
1592
  router=router,
952
- default_agent=self.orchestrator_agent.name if self.orchestrator_agent else None
1593
+ default_agent=(
1594
+ self.orchestrator_agent.name if self.orchestrator_agent else None
1595
+ ),
1596
+ )
1597
+
1598
+ self.console.print(
1599
+ f"[green]✓ Agent network initialized with {len(all_agents)} agents[/green]"
953
1600
  )
954
-
955
- self.console.print(f"[green]✓ Agent network initialized with {len(all_agents)} agents[/green]")
956
1601
  return True
957
-
1602
+
958
1603
  async def _start_hanzo_net(self):
959
1604
  """Start hanzo net for local AI orchestration."""
960
- self.console.print("[cyan]Starting hanzo/net for local AI orchestration...[/cyan]")
961
-
1605
+ self.console.print(
1606
+ "[cyan]Starting hanzo/net for local AI orchestration...[/cyan]"
1607
+ )
1608
+
962
1609
  # Check if hanzo net is already running
963
1610
  import socket
1611
+
964
1612
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
965
- result = sock.connect_ex(('localhost', self.hanzo_net_port))
1613
+ result = sock.connect_ex(("localhost", self.hanzo_net_port))
966
1614
  sock.close()
967
-
1615
+
968
1616
  if result == 0:
969
- self.console.print(f"[yellow]hanzo/net already running on port {self.hanzo_net_port}[/yellow]")
1617
+ self.console.print(
1618
+ f"[yellow]hanzo/net already running on port {self.hanzo_net_port}[/yellow]"
1619
+ )
970
1620
  return
971
-
1621
+
972
1622
  # Start hanzo net
973
1623
  try:
974
1624
  # Determine model to serve based on orchestrator model
975
1625
  model = "llama-3.2-3b" # Default
976
1626
  if ":" in self.orchestrator_model:
977
1627
  model = self.orchestrator_model.split(":")[1]
978
-
1628
+
979
1629
  cmd = [
980
- "hanzo", "net",
981
- "--port", str(self.hanzo_net_port),
982
- "--models", model,
983
- "--network", "local"
1630
+ "hanzo",
1631
+ "net",
1632
+ "--port",
1633
+ str(self.hanzo_net_port),
1634
+ "--models",
1635
+ model,
1636
+ "--network",
1637
+ "local",
984
1638
  ]
985
-
1639
+
986
1640
  self.hanzo_net_process = subprocess.Popen(
987
1641
  cmd,
988
1642
  stdout=subprocess.PIPE,
989
1643
  stderr=subprocess.PIPE,
990
1644
  text=True,
991
- preexec_fn=os.setsid if hasattr(os, 'setsid') else None
1645
+ preexec_fn=os.setsid if hasattr(os, "setsid") else None,
992
1646
  )
993
-
1647
+
994
1648
  # Wait for it to start
995
1649
  await asyncio.sleep(3)
996
-
1650
+
997
1651
  if self.hanzo_net_process.poll() is None:
998
- self.console.print(f"[green]✓ hanzo/net started on port {self.hanzo_net_port} with model {model}[/green]")
1652
+ self.console.print(
1653
+ f"[green]✓ hanzo/net started on port {self.hanzo_net_port} with model {model}[/green]"
1654
+ )
999
1655
  else:
1000
1656
  self.console.print("[red]Failed to start hanzo/net[/red]")
1001
-
1657
+
1002
1658
  except Exception as e:
1003
1659
  self.console.print(f"[red]Error starting hanzo/net: {e}[/red]")
1004
-
1660
+
1005
1661
  async def _create_orchestrator_agent(self) -> Agent:
1006
1662
  """Create the orchestrator agent (GPT-5, local, or configured model)."""
1007
1663
  # Check if using local model via hanzo/net
1008
1664
  if self.orchestrator_model.startswith("local:"):
1009
1665
  # Use local model via hanzo/net
1010
1666
  model_name = self.orchestrator_model.split(":")[1]
1011
-
1667
+
1012
1668
  # Import local network helpers
1013
1669
  from hanzo_network.local_network import create_local_agent
1014
-
1670
+
1015
1671
  orchestrator = create_local_agent(
1016
1672
  name="local_orchestrator",
1017
1673
  description=f"Local {model_name} orchestrator via hanzo/net",
1018
1674
  system=self._get_orchestrator_system_prompt(),
1019
1675
  local_model=model_name,
1020
1676
  base_url=f"http://localhost:{self.hanzo_net_port}",
1021
- tools=[]
1677
+ tools=[],
1678
+ )
1679
+
1680
+ self.console.print(
1681
+ f"[green]✓ Created local {model_name} orchestrator via hanzo/net[/green]"
1022
1682
  )
1023
-
1024
- self.console.print(f"[green]✓ Created local {model_name} orchestrator via hanzo/net[/green]")
1025
1683
  return orchestrator
1026
-
1684
+
1027
1685
  # Parse model string to get provider and model
1028
- model_config = ModelConfig.from_string(self.orchestrator_model)
1029
-
1030
- # Add API key from environment if needed
1031
- if model_config.provider == ModelProvider.OPENAI:
1032
- model_config.api_key = os.getenv("OPENAI_API_KEY")
1033
- elif model_config.provider == ModelProvider.ANTHROPIC:
1034
- model_config.api_key = os.getenv("ANTHROPIC_API_KEY")
1035
- elif model_config.provider == ModelProvider.GOOGLE:
1036
- model_config.api_key = os.getenv("GOOGLE_API_KEY")
1037
-
1686
+ model_name = self.orchestrator_model
1687
+ provider = "openai" # Default to OpenAI - use string
1688
+ api_key = None
1689
+
1690
+ # Determine provider from model name
1691
+ if model_name.startswith("gpt") or model_name == "codex":
1692
+ provider = "openai"
1693
+ api_key = os.getenv("OPENAI_API_KEY")
1694
+ elif model_name.startswith("claude"):
1695
+ provider = "anthropic"
1696
+ api_key = os.getenv("ANTHROPIC_API_KEY")
1697
+ elif model_name.startswith("gemini"):
1698
+ provider = "google"
1699
+ api_key = os.getenv("GOOGLE_API_KEY")
1700
+ elif model_name.startswith("local:"):
1701
+ provider = "local"
1702
+ model_name = model_name.replace("local:", "")
1703
+
1704
+ # Create model config based on what's available
1705
+ if NETWORK_AVAILABLE:
1706
+ # Real ModelConfig may have different signature
1707
+ try:
1708
+ model_config = ModelConfig(
1709
+ name=model_name,
1710
+ provider=provider,
1711
+ )
1712
+ # Set api_key separately if supported
1713
+ if hasattr(model_config, "api_key"):
1714
+ model_config.api_key = api_key
1715
+ except TypeError:
1716
+ # Fallback to simple string if ModelConfig doesn't work
1717
+ model_config = model_name
1718
+ else:
1719
+ # Use our fallback ModelConfig
1720
+ model_config = ModelConfig(
1721
+ name=model_name,
1722
+ provider=provider,
1723
+ api_key=api_key,
1724
+ )
1725
+
1038
1726
  # Create orchestrator with strategic system prompt
1039
1727
  orchestrator = create_agent(
1040
1728
  name="orchestrator",
1041
1729
  description=f"{self.orchestrator_model} powered meta-orchestrator for AI coding",
1042
1730
  model=model_config,
1043
1731
  system=self._get_orchestrator_system_prompt(),
1044
- tools=[] # Orchestrator tools will be added
1732
+ tools=[], # Orchestrator tools will be added
1733
+ )
1734
+
1735
+ self.console.print(
1736
+ f"[green]✓ Created {self.orchestrator_model} orchestrator[/green]"
1045
1737
  )
1046
-
1047
- self.console.print(f"[green]✓ Created {self.orchestrator_model} orchestrator[/green]")
1048
1738
  return orchestrator
1049
-
1739
+
1050
1740
  def _get_orchestrator_system_prompt(self) -> str:
1051
1741
  """Get the system prompt for the orchestrator."""
1052
1742
  return """You are an advanced AI orchestrator managing a network of specialized agents.
@@ -1069,16 +1759,20 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
1069
1759
  - Simple tasks → Use local agents if available
1070
1760
 
1071
1761
  Always maintain high code quality standards and prevent degradation."""
1072
-
1762
+
1073
1763
  async def _create_worker_agent(self, index: int) -> Agent:
1074
1764
  """Create a worker agent (Claude for implementation)."""
1075
1765
  worker = create_agent(
1076
1766
  name=f"worker_{index}",
1077
1767
  description=f"Claude worker agent {index} for code implementation",
1078
- model=ModelConfig(
1079
- provider=ModelProvider.ANTHROPIC,
1080
- model="claude-3-5-sonnet-20241022",
1081
- api_key=os.getenv("ANTHROPIC_API_KEY")
1768
+ model=(
1769
+ "claude-3-5-sonnet-20241022"
1770
+ if NETWORK_AVAILABLE
1771
+ else ModelConfig(
1772
+ provider="anthropic",
1773
+ name="claude-3-5-sonnet-20241022",
1774
+ api_key=os.getenv("ANTHROPIC_API_KEY"),
1775
+ )
1082
1776
  ),
1083
1777
  system="""You are a Claude worker agent specialized in code implementation.
1084
1778
 
@@ -1089,16 +1783,16 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
1089
1783
  - Debug and fix issues
1090
1784
 
1091
1785
  Follow best practices and maintain code quality.""",
1092
- tools=[] # MCP tools will be added if enabled
1786
+ tools=[], # MCP tools will be added if enabled
1093
1787
  )
1094
-
1788
+
1095
1789
  self.console.print(f" Created worker agent {index}")
1096
1790
  return worker
1097
-
1791
+
1098
1792
  async def _create_local_worker_agent(self, index: int) -> Agent:
1099
1793
  """Create a local worker agent for simple tasks (cost optimization)."""
1100
1794
  from hanzo_network.local_network import create_local_agent
1101
-
1795
+
1102
1796
  worker = create_local_agent(
1103
1797
  name=f"local_worker_{index}",
1104
1798
  description=f"Local worker agent {index} for simple tasks",
@@ -1113,21 +1807,21 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
1113
1807
  You handle simple tasks to reduce API costs.""",
1114
1808
  local_model="llama-3.2-3b",
1115
1809
  base_url=f"http://localhost:{self.hanzo_net_port}",
1116
- tools=[]
1810
+ tools=[],
1117
1811
  )
1118
-
1812
+
1119
1813
  self.console.print(f" Created local worker agent {index}")
1120
1814
  return worker
1121
-
1815
+
1122
1816
  async def _create_critic_agent(self, index: int) -> Agent:
1123
1817
  """Create a critic agent for code review."""
1124
1818
  # Use a different model for critics for diversity
1125
1819
  critic_model = "gpt-4" if index % 2 == 0 else "claude-3-5-sonnet-20241022"
1126
-
1820
+
1127
1821
  critic = create_agent(
1128
1822
  name=f"critic_{index}",
1129
1823
  description=f"Critic agent {index} for code quality assurance",
1130
- model=ModelConfig.from_string(critic_model),
1824
+ model=critic_model, # Just pass the model name string
1131
1825
  system="""You are a critic agent focused on code quality and best practices.
1132
1826
 
1133
1827
  Review code for:
@@ -1138,19 +1832,19 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
1138
1832
  5. Best practices and design patterns
1139
1833
 
1140
1834
  Provide constructive feedback with specific improvement suggestions.""",
1141
- tools=[]
1835
+ tools=[],
1142
1836
  )
1143
-
1837
+
1144
1838
  self.console.print(f" Created critic agent {index} ({critic_model})")
1145
1839
  return critic
1146
-
1840
+
1147
1841
  async def _create_cost_optimized_router(self) -> Router:
1148
1842
  """Create a cost-optimized router that prefers local models."""
1149
1843
  from hanzo_network.core.router import Router
1150
-
1844
+
1151
1845
  class CostOptimizedRouter(Router):
1152
1846
  """Router that minimizes costs by using local models when possible."""
1153
-
1847
+
1154
1848
  def __init__(self, orchestrator_agent, worker_agents, critic_agents):
1155
1849
  super().__init__()
1156
1850
  self.orchestrator = orchestrator_agent
@@ -1158,55 +1852,97 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
1158
1852
  self.critics = critic_agents
1159
1853
  self.local_workers = [w for w in worker_agents if "local" in w.name]
1160
1854
  self.api_workers = [w for w in worker_agents if "local" not in w.name]
1161
-
1855
+
1162
1856
  async def route(self, prompt: str, state=None) -> str:
1163
1857
  """Route based on task complexity and cost optimization."""
1164
1858
  prompt_lower = prompt.lower()
1165
-
1859
+
1166
1860
  # Simple tasks → Local workers
1167
- simple_keywords = ["list", "check", "validate", "format", "rename", "count", "find"]
1168
- if any(keyword in prompt_lower for keyword in simple_keywords) and self.local_workers:
1861
+ simple_keywords = [
1862
+ "list",
1863
+ "check",
1864
+ "validate",
1865
+ "format",
1866
+ "rename",
1867
+ "count",
1868
+ "find",
1869
+ ]
1870
+ if (
1871
+ any(keyword in prompt_lower for keyword in simple_keywords)
1872
+ and self.local_workers
1873
+ ):
1169
1874
  return self.local_workers[0].name
1170
-
1875
+
1171
1876
  # Complex implementation → API workers (Claude)
1172
- complex_keywords = ["implement", "refactor", "debug", "optimize", "design", "architect"]
1173
- if any(keyword in prompt_lower for keyword in complex_keywords) and self.api_workers:
1877
+ complex_keywords = [
1878
+ "implement",
1879
+ "refactor",
1880
+ "debug",
1881
+ "optimize",
1882
+ "design",
1883
+ "architect",
1884
+ ]
1885
+ if (
1886
+ any(keyword in prompt_lower for keyword in complex_keywords)
1887
+ and self.api_workers
1888
+ ):
1174
1889
  return self.api_workers[0].name
1175
-
1890
+
1176
1891
  # Review tasks → Critics
1177
- review_keywords = ["review", "critique", "analyze", "improve", "validate code"]
1178
- if any(keyword in prompt_lower for keyword in review_keywords) and self.critics:
1892
+ review_keywords = [
1893
+ "review",
1894
+ "critique",
1895
+ "analyze",
1896
+ "improve",
1897
+ "validate code",
1898
+ ]
1899
+ if (
1900
+ any(keyword in prompt_lower for keyword in review_keywords)
1901
+ and self.critics
1902
+ ):
1179
1903
  return self.critics[0].name
1180
-
1904
+
1181
1905
  # Strategic decisions → Orchestrator
1182
- strategic_keywords = ["plan", "decide", "strategy", "coordinate", "organize"]
1906
+ strategic_keywords = [
1907
+ "plan",
1908
+ "decide",
1909
+ "strategy",
1910
+ "coordinate",
1911
+ "organize",
1912
+ ]
1183
1913
  if any(keyword in prompt_lower for keyword in strategic_keywords):
1184
1914
  return self.orchestrator.name
1185
-
1915
+
1186
1916
  # Default: Try local first, then API
1187
1917
  if self.local_workers:
1188
1918
  # For shorter prompts, try local first
1189
1919
  if len(prompt) < 500:
1190
1920
  return self.local_workers[0].name
1191
-
1921
+
1192
1922
  # Fall back to API workers for complex tasks
1193
- return self.api_workers[0].name if self.api_workers else self.orchestrator.name
1194
-
1923
+ return (
1924
+ self.api_workers[0].name
1925
+ if self.api_workers
1926
+ else self.orchestrator.name
1927
+ )
1928
+
1195
1929
  # Create the cost-optimized router
1196
1930
  router = CostOptimizedRouter(
1197
- self.orchestrator_agent,
1198
- self.worker_agents,
1199
- self.critic_agents
1931
+ self.orchestrator_agent, self.worker_agents, self.critic_agents
1932
+ )
1933
+
1934
+ self.console.print(
1935
+ "[green]✓ Created cost-optimized router (local models preferred)[/green]"
1200
1936
  )
1201
-
1202
- self.console.print("[green]✓ Created cost-optimized router (local models preferred)[/green]")
1203
1937
  return router
1204
-
1938
+
1205
1939
  async def _create_intelligent_router(self) -> Router:
1206
1940
  """Create an intelligent router using the orchestrator for decisions."""
1207
1941
  if self.orchestrator_agent:
1208
1942
  # Create routing agent that uses orchestrator for decisions
1209
1943
  router = create_routing_agent(
1944
+ name="router",
1945
+ description="Intelligent task router",
1210
1946
  agent=self.orchestrator_agent,
1211
1947
  system="""Route tasks to the most appropriate agent based on:
1212
1948
 
@@ -1221,68 +1957,67 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
1221
1957
  - Review tasks → Route to critics
1222
1958
  - Parallel work → Split across multiple agents
1223
1959
 
1224
- Return the name of the best agent for the task."""
1960
+ Return the name of the best agent for the task.""",
1225
1961
  )
1226
1962
  else:
1227
1963
  # Fallback to basic router
1228
1964
  router = create_router(
1229
1965
  agents=self.worker_agents + self.critic_agents,
1230
- default=self.worker_agents[0].name if self.worker_agents else None
1966
+ default=self.worker_agents[0].name if self.worker_agents else None,
1231
1967
  )
1232
-
1968
+
1233
1969
  return router
1234
-
1235
- async def execute_with_network(self, task: str, context: Optional[Dict] = None) -> Dict:
1970
+
1971
+ async def execute_with_network(
1972
+ self, task: str, context: Optional[Dict] = None
1973
+ ) -> Dict:
1236
1974
  """Execute a task using the agent network.
1237
-
1975
+
1238
1976
  Args:
1239
1977
  task: Task description
1240
1978
  context: Optional context
1241
-
1979
+
1242
1980
  Returns:
1243
1981
  Execution result
1244
1982
  """
1245
1983
  if not self.agent_network:
1246
1984
  self.console.print("[red]Agent network not initialized[/red]")
1247
1985
  return {"error": "Network not initialized"}
1248
-
1986
+
1249
1987
  self.console.print(f"[cyan]Executing task with agent network: {task}[/cyan]")
1250
-
1988
+
1251
1989
  # Create network state
1252
1990
  state = NetworkState()
1253
1991
  state.add_message("user", task)
1254
-
1992
+
1255
1993
  if context:
1256
1994
  state.metadata.update(context)
1257
-
1995
+
1258
1996
  # Run the network
1259
1997
  try:
1260
- result = await self.agent_network.run(
1261
- prompt=task,
1262
- state=state
1263
- )
1264
-
1998
+ result = await self.agent_network.run(prompt=task, state=state)
1999
+
1265
2000
  # If guardrails enabled, validate result
1266
2001
  if self.enable_guardrails and self.critic_agents:
1267
2002
  validated = await self._validate_with_critics(result, task)
1268
2003
  if validated.get("improvements"):
1269
2004
  self.console.print("[yellow]Applied critic improvements[/yellow]")
1270
2005
  return validated
1271
-
2006
+
1272
2007
  return result
1273
-
2008
+
1274
2009
  except Exception as e:
1275
2010
  self.console.print(f"[red]Network execution error: {e}[/red]")
1276
2011
  return {"error": str(e)}
1277
-
2012
+
1278
2013
  async def _validate_with_critics(self, result: Dict, original_task: str) -> Dict:
1279
2014
  """Validate and potentially improve result using critic agents."""
1280
2015
  if not self.critic_agents:
1281
2016
  return result
1282
-
2017
+
1283
2018
  # Get first critic to review
1284
2019
  critic = self.critic_agents[0]
1285
-
2020
+
1286
2021
  review_prompt = f"""
1287
2022
  Review this solution:
1288
2023
 
@@ -1291,69 +2026,80 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
1291
2026
 
1292
2027
  Provide specific improvements if needed.
1293
2028
  """
1294
-
2029
+
1295
2030
  review = await critic.run(review_prompt)
1296
-
2031
+
1297
2032
  # Check if improvements suggested
1298
- if "improve" in str(review.get('output', '')).lower():
1299
- result["improvements"] = review.get('output')
1300
-
2033
+ if "improve" in str(review.get("output", "")).lower():
2034
+ result["improvements"] = review.get("output")
2035
+
1301
2036
  return result
1302
-
2037
+
1303
2038
  def shutdown(self):
1304
2039
  """Shutdown the network orchestrator and hanzo net if running."""
1305
2040
  # Stop hanzo net if we started it
1306
2041
  if self.hanzo_net_process:
1307
2042
  try:
1308
2043
  self.console.print("[yellow]Stopping hanzo/net...[/yellow]")
1309
- if hasattr(os, 'killpg'):
2044
+ if hasattr(os, "killpg"):
1310
2045
  os.killpg(os.getpgid(self.hanzo_net_process.pid), signal.SIGTERM)
1311
2046
  else:
1312
2047
  self.hanzo_net_process.terminate()
1313
2048
  self.hanzo_net_process.wait(timeout=5)
1314
2049
  self.console.print("[green]✓ hanzo/net stopped[/green]")
1315
- except:
2050
+ except Exception:
1316
2051
  try:
1317
2052
  self.hanzo_net_process.kill()
1318
- except:
2053
+ except Exception:
1319
2054
  pass
1320
-
2055
+
1321
2056
  # Call parent shutdown
1322
2057
  super().shutdown()
1323
2058
 
1324
2059
 
1325
2060
  class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1326
2061
  """Extended orchestrator for multiple Claude instances with MCP networking."""
1327
-
1328
- def __init__(self, workspace_dir: str, claude_path: str, num_instances: int,
1329
- enable_mcp: bool, enable_networking: bool, enable_guardrails: bool,
1330
- console: Console):
2062
+
2063
+ def __init__(
2064
+ self,
2065
+ workspace_dir: str,
2066
+ claude_path: str,
2067
+ num_instances: int,
2068
+ enable_mcp: bool,
2069
+ enable_networking: bool,
2070
+ enable_guardrails: bool,
2071
+ console: Console,
2072
+ orchestrator_model: str = "gpt-4",
2073
+ ):
1331
2074
  super().__init__(workspace_dir, claude_path)
1332
2075
  self.num_instances = num_instances
1333
2076
  self.enable_mcp = enable_mcp
1334
2077
  self.enable_networking = enable_networking
1335
2078
  self.enable_guardrails = enable_guardrails
1336
2079
  self.console = console
1337
-
2080
+ self.orchestrator_model = orchestrator_model # Add this for chat interface
2081
+
1338
2082
  # Store multiple Claude instances
1339
2083
  self.claude_instances = []
1340
2084
  self.instance_configs = []
1341
-
2085
+
1342
2086
  async def initialize(self):
1343
2087
  """Initialize all Claude instances with MCP networking."""
1344
2088
  self.console.print("[cyan]Initializing Claude instances...[/cyan]")
1345
-
2089
+
1346
2090
  for i in range(self.num_instances):
1347
2091
  role = "primary" if i == 0 else f"critic_{i}"
1348
2092
  config = await self._create_instance_config(i, role)
1349
2093
  self.instance_configs.append(config)
1350
-
1351
- self.console.print(f" [{i+1}/{self.num_instances}] {role} instance configured")
1352
-
2094
+
2095
+ self.console.print(
2096
+ f" [{i+1}/{self.num_instances}] {role} instance configured"
2097
+ )
2098
+
1353
2099
  # If networking enabled, configure MCP connections between instances
1354
2100
  if self.enable_networking:
1355
2101
  await self._setup_mcp_networking()
1356
-
2102
+
1357
2103
  # Start all instances
1358
2104
  for i, config in enumerate(self.instance_configs):
1359
2105
  success = await self._start_claude_instance(i, config)
@@ -1361,12 +2107,12 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1361
2107
  self.console.print(f"[green]✓ Instance {i} started[/green]")
1362
2108
  else:
1363
2109
  self.console.print(f"[red]✗ Failed to start instance {i}[/red]")
1364
-
2110
+
1365
2111
  async def _create_instance_config(self, index: int, role: str) -> Dict:
1366
2112
  """Create configuration for a Claude instance."""
1367
2113
  base_port = 8000
1368
2114
  mcp_port = 9000
1369
-
2115
+
1370
2116
  config = {
1371
2117
  "index": index,
1372
2118
  "role": role,
@@ -1374,18 +2120,18 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1374
2120
  "port": base_port + index,
1375
2121
  "mcp_port": mcp_port + index,
1376
2122
  "mcp_config": {},
1377
- "env": {}
2123
+ "env": {},
1378
2124
  }
1379
-
2125
+
1380
2126
  # Create workspace directory
1381
2127
  config["workspace"].mkdir(parents=True, exist_ok=True)
1382
-
2128
+
1383
2129
  # Configure MCP tools if enabled
1384
2130
  if self.enable_mcp:
1385
2131
  config["mcp_config"] = await self._create_mcp_config(index, role)
1386
-
2132
+
1387
2133
  return config
1388
-
2134
+
1389
2135
  async def _create_mcp_config(self, index: int, role: str) -> Dict:
1390
2136
  """Create MCP configuration for an instance."""
1391
2137
  mcp_config = {
@@ -1393,29 +2139,26 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1393
2139
  "hanzo-mcp": {
1394
2140
  "command": "python",
1395
2141
  "args": ["-m", "hanzo_mcp"],
1396
- "env": {
1397
- "INSTANCE_ID": str(index),
1398
- "INSTANCE_ROLE": role
1399
- }
2142
+ "env": {"INSTANCE_ID": str(index), "INSTANCE_ROLE": role},
1400
2143
  }
1401
2144
  }
1402
2145
  }
1403
-
2146
+
1404
2147
  # Add file system tools
1405
2148
  mcp_config["mcpServers"]["filesystem"] = {
1406
2149
  "command": "npx",
1407
2150
  "args": ["-y", "@modelcontextprotocol/server-filesystem"],
1408
- "env": {
1409
- "ALLOWED_DIRECTORIES": str(self.workspace_dir)
1410
- }
2151
+ "env": {"ALLOWED_DIRECTORIES": str(self.workspace_dir)},
1411
2152
  }
1412
-
2153
+
1413
2154
  return mcp_config
1414
-
2155
+
1415
2156
  async def _setup_mcp_networking(self):
1416
2157
  """Set up MCP networking between Claude instances."""
1417
- self.console.print("[cyan]Setting up MCP networking between instances...[/cyan]")
1418
-
2158
+ self.console.print(
2159
+ "[cyan]Setting up MCP networking between instances...[/cyan]"
2160
+ )
2161
+
1419
2162
  # Each instance gets MCP servers for all other instances
1420
2163
  for i, config in enumerate(self.instance_configs):
1421
2164
  for j, other_config in enumerate(self.instance_configs):
@@ -1425,37 +2168,38 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1425
2168
  config["mcp_config"]["mcpServers"][server_name] = {
1426
2169
  "command": "python",
1427
2170
  "args": [
1428
- "-m", "hanzo_mcp.bridge",
1429
- "--target-port", str(other_config["port"]),
1430
- "--instance-id", str(j),
1431
- "--role", other_config["role"]
2171
+ "-m",
2172
+ "hanzo_mcp.bridge",
2173
+ "--target-port",
2174
+ str(other_config["port"]),
2175
+ "--instance-id",
2176
+ str(j),
2177
+ "--role",
2178
+ other_config["role"],
1432
2179
  ],
1433
- "env": {
1434
- "SOURCE_INSTANCE": str(i),
1435
- "TARGET_INSTANCE": str(j)
1436
- }
2180
+ "env": {"SOURCE_INSTANCE": str(i), "TARGET_INSTANCE": str(j)},
1437
2181
  }
1438
-
2182
+
1439
2183
  # Save MCP config
1440
2184
  mcp_config_file = config["workspace"] / "mcp_config.json"
1441
- with open(mcp_config_file, 'w') as f:
2185
+ with open(mcp_config_file, "w") as f:
1442
2186
  json.dump(config["mcp_config"], f, indent=2)
1443
-
2187
+
1444
2188
  config["env"]["MCP_CONFIG_PATH"] = str(mcp_config_file)
1445
-
2189
+
1446
2190
  async def _start_claude_instance(self, index: int, config: Dict) -> bool:
1447
2191
  """Start a single Claude instance."""
1448
2192
  try:
1449
2193
  cmd = [self.claude_code_path or "claude"]
1450
-
2194
+
1451
2195
  # Add configuration flags
1452
2196
  if config.get("env", {}).get("MCP_CONFIG_PATH"):
1453
2197
  cmd.extend(["--mcp-config", config["env"]["MCP_CONFIG_PATH"]])
1454
-
2198
+
1455
2199
  # Set up environment
1456
2200
  env = os.environ.copy()
1457
2201
  env.update(config.get("env", {}))
1458
-
2202
+
1459
2203
  # Start process
1460
2204
  process = subprocess.Popen(
1461
2205
  cmd,
@@ -1464,47 +2208,49 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1464
2208
  stdin=subprocess.PIPE,
1465
2209
  env=env,
1466
2210
  cwd=str(config["workspace"]),
1467
- preexec_fn=os.setsid if hasattr(os, 'setsid') else None
2211
+ preexec_fn=os.setsid if hasattr(os, "setsid") else None,
1468
2212
  )
1469
-
1470
- self.claude_instances.append({
1471
- "index": index,
1472
- "role": config["role"],
1473
- "process": process,
1474
- "config": config,
1475
- "health": RuntimeHealth(
1476
- state=RuntimeState.RUNNING,
1477
- last_response=datetime.now(),
1478
- response_time_ms=0,
1479
- memory_usage_mb=0,
1480
- cpu_percent=0,
1481
- error_count=0,
1482
- restart_count=0
1483
- )
1484
- })
1485
-
2213
+
2214
+ self.claude_instances.append(
2215
+ {
2216
+ "index": index,
2217
+ "role": config["role"],
2218
+ "process": process,
2219
+ "config": config,
2220
+ "health": RuntimeHealth(
2221
+ state=RuntimeState.RUNNING,
2222
+ last_response=datetime.now(),
2223
+ response_time_ms=0,
2224
+ memory_usage_mb=0,
2225
+ cpu_percent=0,
2226
+ error_count=0,
2227
+ restart_count=0,
2228
+ ),
2229
+ }
2230
+ )
2231
+
1486
2232
  return True
1487
-
2233
+
1488
2234
  except Exception as e:
1489
2235
  logger.error(f"Failed to start instance {index}: {e}")
1490
2236
  return False
1491
-
2237
+
1492
2238
  async def execute_with_critique(self, task: str) -> Dict:
1493
2239
  """Execute a task with System 2 critique.
1494
-
2240
+
1495
2241
  1. Primary instance executes the task
1496
2242
  2. Critic instance(s) review and suggest improvements
1497
2243
  3. Primary incorporates feedback if confidence is high
1498
2244
  """
1499
2245
  self.console.print(f"[cyan]Executing with System 2 thinking: {task}[/cyan]")
1500
-
2246
+
1501
2247
  # Step 1: Primary execution
1502
2248
  primary = self.claude_instances[0]
1503
2249
  result = await self._send_to_instance(primary, task)
1504
-
2250
+
1505
2251
  if self.num_instances < 2:
1506
2252
  return result
1507
-
2253
+
1508
2254
  # Step 2: Critic review
1509
2255
  critiques = []
1510
2256
  for critic in self.claude_instances[1:]:
@@ -1523,10 +2269,10 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1523
2269
 
1524
2270
  Suggest specific improvements.
1525
2271
  """
1526
-
2272
+
1527
2273
  critique = await self._send_to_instance(critic, critique_prompt)
1528
2274
  critiques.append(critique)
1529
-
2275
+
1530
2276
  # Step 3: Incorporate feedback if valuable
1531
2277
  if critiques and self.enable_guardrails:
1532
2278
  improvement_prompt = f"""
@@ -1538,73 +2284,87 @@ class MultiClaudeOrchestrator(HanzoDevOrchestrator):
1538
2284
 
1539
2285
  Incorporate the valid suggestions and produce an improved solution.
1540
2286
  """
1541
-
2287
+
1542
2288
  improved = await self._send_to_instance(primary, improvement_prompt)
1543
-
2289
+
1544
2290
  # Validate improvement didn't degrade quality
1545
2291
  if await self._validate_improvement(result, improved):
1546
- self.console.print("[green]✓ Solution improved with System 2 feedback[/green]")
2292
+ self.console.print(
2293
+ "[green]✓ Solution improved with System 2 feedback[/green]"
2294
+ )
1547
2295
  return improved
1548
2296
  else:
1549
- self.console.print("[yellow]⚠ Keeping original solution (improvement validation failed)[/yellow]")
1550
-
2297
+ self.console.print(
2298
+ "[yellow]⚠ Keeping original solution (improvement validation failed)[/yellow]"
2299
+ )
2300
+
1551
2301
  return result
1552
-
2302
+
1553
2303
  async def _send_to_instance(self, instance: Dict, prompt: str) -> Dict:
1554
2304
  """Send a prompt to a specific Claude instance."""
1555
2305
  # This would use the actual Claude API or IPC mechanism
1556
2306
  # For now, it's a placeholder
1557
2307
  return {
1558
2308
  "output": f"Response from {instance['role']}: Processed '{prompt[:50]}...'",
1559
- "success": True
2309
+ "success": True,
1560
2310
  }
1561
-
2311
+
1562
2312
  async def _validate_improvement(self, original: Dict, improved: Dict) -> bool:
1563
2313
  """Validate that an improvement doesn't degrade quality."""
1564
2314
  if not self.enable_guardrails:
1565
2315
  return True
1566
-
2316
+
1567
2317
  # Placeholder for actual validation logic
1568
2318
  # Would check: tests still pass, no new errors, performance not degraded, etc.
1569
2319
  return True
1570
-
2320
+
1571
2321
  def shutdown(self):
1572
2322
  """Shutdown all Claude instances."""
1573
2323
  self.console.print("[yellow]Shutting down all instances...[/yellow]")
1574
-
2324
+
1575
2325
  for instance in self.claude_instances:
1576
2326
  try:
1577
2327
  process = instance["process"]
1578
- if hasattr(os, 'killpg'):
2328
+ if hasattr(os, "killpg"):
1579
2329
  os.killpg(os.getpgid(process.pid), signal.SIGTERM)
1580
2330
  else:
1581
2331
  process.terminate()
1582
2332
  process.wait(timeout=5)
1583
- except:
2333
+ except Exception:
1584
2334
  try:
1585
2335
  instance["process"].kill()
1586
- except:
2336
+ except Exception:
1587
2337
  pass
1588
-
2338
+
1589
2339
  self.console.print("[green]✓ All instances shut down[/green]")
1590
2340
 
1591
2341
 
1592
2342
  async def main():
1593
2343
  """Main entry point for hanzo-dev."""
1594
2344
  import argparse
1595
-
1596
- parser = argparse.ArgumentParser(description="Hanzo Dev - System 2 Meta-AI Orchestrator")
1597
- parser.add_argument("--workspace", default="~/.hanzo/dev", help="Workspace directory")
2345
+
2346
+ parser = argparse.ArgumentParser(
2347
+ description="Hanzo Dev - System 2 Meta-AI Orchestrator"
2348
+ )
2349
+ parser.add_argument(
2350
+ "--workspace", default="~/.hanzo/dev", help="Workspace directory"
2351
+ )
1598
2352
  parser.add_argument("--claude-path", help="Path to Claude Code executable")
1599
2353
  parser.add_argument("--monitor", action="store_true", help="Start in monitor mode")
1600
2354
  parser.add_argument("--repl", action="store_true", help="Start REPL interface")
1601
- parser.add_argument("--instances", type=int, default=2, help="Number of Claude instances")
2355
+ parser.add_argument(
2356
+ "--instances", type=int, default=2, help="Number of Claude instances"
2357
+ )
1602
2358
  parser.add_argument("--no-mcp", action="store_true", help="Disable MCP tools")
1603
- parser.add_argument("--no-network", action="store_true", help="Disable instance networking")
1604
- parser.add_argument("--no-guardrails", action="store_true", help="Disable guardrails")
1605
-
2359
+ parser.add_argument(
2360
+ "--no-network", action="store_true", help="Disable instance networking"
2361
+ )
2362
+ parser.add_argument(
2363
+ "--no-guardrails", action="store_true", help="Disable guardrails"
2364
+ )
2365
+
1606
2366
  args = parser.parse_args()
1607
-
2367
+
1608
2368
  await run_dev_orchestrator(
1609
2369
  workspace=args.workspace,
1610
2370
  claude_path=args.claude_path,
@@ -1613,9 +2373,9 @@ async def main():
1613
2373
  instances=args.instances,
1614
2374
  mcp_tools=not args.no_mcp,
1615
2375
  network_mode=not args.no_network,
1616
- guardrails=not args.no_guardrails
2376
+ guardrails=not args.no_guardrails,
1617
2377
  )
1618
2378
 
1619
2379
 
1620
2380
  if __name__ == "__main__":
1621
- asyncio.run(main())
2381
+ asyncio.run(main())