zwarm 3.3.0__tar.gz → 3.4.0__tar.gz

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.
Files changed (36) hide show
  1. {zwarm-3.3.0 → zwarm-3.4.0}/PKG-INFO +1 -1
  2. {zwarm-3.3.0 → zwarm-3.4.0}/pyproject.toml +1 -1
  3. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/cli/interactive.py +1 -1
  4. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/cli/main.py +20 -0
  5. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/cli/pilot.py +54 -5
  6. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/config.py +2 -0
  7. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/orchestrator.py +11 -1
  8. {zwarm-3.3.0 → zwarm-3.4.0}/.gitignore +0 -0
  9. {zwarm-3.3.0 → zwarm-3.4.0}/README.md +0 -0
  10. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/__init__.py +0 -0
  11. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/cli/__init__.py +0 -0
  12. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/__init__.py +0 -0
  13. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/checkpoints.py +0 -0
  14. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/compact.py +0 -0
  15. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/costs.py +0 -0
  16. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/environment.py +0 -0
  17. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/models.py +0 -0
  18. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/state.py +0 -0
  19. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/test_compact.py +0 -0
  20. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/test_config.py +0 -0
  21. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/core/test_models.py +0 -0
  22. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/prompts/__init__.py +0 -0
  23. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/prompts/orchestrator.py +0 -0
  24. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/prompts/pilot.py +0 -0
  25. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/sessions/__init__.py +0 -0
  26. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/sessions/manager.py +0 -0
  27. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/test_orchestrator_watchers.py +0 -0
  28. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/tools/__init__.py +0 -0
  29. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/tools/delegation.py +0 -0
  30. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/watchers/__init__.py +0 -0
  31. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/watchers/base.py +0 -0
  32. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/watchers/builtin.py +0 -0
  33. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/watchers/llm_watcher.py +0 -0
  34. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/watchers/manager.py +0 -0
  35. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/watchers/registry.py +0 -0
  36. {zwarm-3.3.0 → zwarm-3.4.0}/src/zwarm/watchers/test_watchers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zwarm
3
- Version: 3.3.0
3
+ Version: 3.4.0
4
4
  Summary: Multi-Agent CLI Orchestration Research Platform
5
5
  Requires-Python: <3.14,>=3.13
6
6
  Requires-Dist: prompt-toolkit>=3.0.52
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "zwarm"
3
- version = "3.3.0"
3
+ version = "3.4.0"
4
4
  description = "Multi-Agent CLI Orchestration Research Platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13,<3.14"
@@ -307,7 +307,7 @@ def cmd_show(manager, session_id: str):
307
307
  icon = STATUS_ICONS.get(session.status.value, "?")
308
308
  console.print(f"\n{icon} [bold cyan]{session.short_id}[/] - {session.status.value}")
309
309
  console.print(f" [dim]Task:[/] {session.task}")
310
- console.print(f" [dim]Turn:[/] {session.turn} | [dim]Runtime:[/] {session.runtime:.1f}s")
310
+ console.print(f" [dim]Turn:[/] {session.turn} | [dim]Runtime:[/] {session.runtime}")
311
311
 
312
312
  # Token usage with cost estimate
313
313
  usage = session.token_usage
@@ -218,6 +218,26 @@ def orchestrate(
218
218
  if orchestrator.instance_id and not instance:
219
219
  console.print(f" [dim]Instance: {orchestrator.instance_id[:8]}[/]")
220
220
 
221
+ # Set up step callback for live progress display
222
+ def step_callback(step_num: int, tool_results: list) -> None:
223
+ """Print tool calls and results as they happen."""
224
+ if not tool_results:
225
+ return
226
+ for tool_info, result in tool_results:
227
+ name = tool_info.get("name", "?")
228
+ # Truncate args for display
229
+ args_str = str(tool_info.get("args", {}))
230
+ if len(args_str) > 80:
231
+ args_str = args_str[:77] + "..."
232
+ # Truncate result for display
233
+ result_str = str(result)
234
+ if len(result_str) > 100:
235
+ result_str = result_str[:97] + "..."
236
+ console.print(f"[dim]step {step_num}[/] → [cyan]{name}[/]({args_str})")
237
+ console.print(f" └ {result_str}")
238
+
239
+ orchestrator._step_callback = step_callback
240
+
221
241
  # Run the orchestrator loop
222
242
  console.print("[bold]--- Orchestrator running ---[/]\n")
223
243
  result = orchestrator.run(task=task)
@@ -594,6 +594,18 @@ def execute_step_with_events(
594
594
  """
595
595
  had_message = False
596
596
 
597
+ # Update environment with current progress before perceive
598
+ # This ensures the observation has fresh step/token counts
599
+ if hasattr(orchestrator, "env") and hasattr(orchestrator.env, "update_progress"):
600
+ total_tokens = getattr(orchestrator, "_total_tokens", 0)
601
+ executor_usage = orchestrator.get_executor_usage() if hasattr(orchestrator, "get_executor_usage") else {}
602
+ orchestrator.env.update_progress(
603
+ step_count=getattr(orchestrator, "_step_count", 0),
604
+ max_steps=getattr(orchestrator, "maxSteps", 50),
605
+ total_tokens=total_tokens,
606
+ executor_tokens=executor_usage.get("total_tokens", 0),
607
+ )
608
+
597
609
  # Execute perceive (updates environment observation)
598
610
  orchestrator.perceive()
599
611
 
@@ -649,7 +661,7 @@ def execute_step_with_events(
649
661
  def run_until_response(
650
662
  orchestrator: Any,
651
663
  renderer: EventRenderer,
652
- max_steps: int = 20,
664
+ max_steps: int = 60,
653
665
  ) -> List[tuple]:
654
666
  """
655
667
  Run the orchestrator until it produces a message response.
@@ -657,7 +669,7 @@ def run_until_response(
657
669
  Keeps stepping while the agent only produces tool calls.
658
670
  Stops when:
659
671
  - Agent produces a text message (returns to user)
660
- - Max steps reached
672
+ - Max steps reached (configurable via orchestrator.max_steps_per_turn)
661
673
  - Stop condition triggered
662
674
 
663
675
  This is wrapped as a weave.op to group all child calls per turn.
@@ -665,7 +677,7 @@ def run_until_response(
665
677
  Args:
666
678
  orchestrator: The orchestrator instance
667
679
  renderer: Event renderer for output
668
- max_steps: Safety limit on steps per turn
680
+ max_steps: Safety limit on steps per turn (default: 60)
669
681
 
670
682
  Returns:
671
683
  All tool results from the turn
@@ -703,6 +715,9 @@ def run_until_response(
703
715
  if not results:
704
716
  break
705
717
 
718
+ # Show session status at end of turn (if there are any sessions)
719
+ render_session_status(orchestrator, renderer)
720
+
706
721
  return all_results
707
722
 
708
723
  return _run_turn()
@@ -758,6 +773,38 @@ def get_sessions_snapshot(orchestrator: Any) -> Dict[str, Any]:
758
773
  return {"sessions": []}
759
774
 
760
775
 
776
+ def render_session_status(orchestrator: Any, renderer: EventRenderer) -> None:
777
+ """
778
+ Render a compact session status line if there are active sessions.
779
+
780
+ Shows: "Sessions: 2 running, 1 done, 0 failed"
781
+ Only displays if there are any sessions.
782
+ """
783
+ if not hasattr(orchestrator, "_session_manager"):
784
+ return
785
+
786
+ sessions = orchestrator._session_manager.list_sessions()
787
+ if not sessions:
788
+ return
789
+
790
+ running = sum(1 for s in sessions if s.status.value == "running")
791
+ completed = sum(1 for s in sessions if s.status.value == "completed")
792
+ failed = sum(1 for s in sessions if s.status.value == "failed")
793
+
794
+ # Build status line with colors
795
+ parts = []
796
+ if running > 0:
797
+ parts.append(f"[cyan]{running} running[/]")
798
+ if completed > 0:
799
+ parts.append(f"[green]{completed} done[/]")
800
+ if failed > 0:
801
+ parts.append(f"[red]{failed} failed[/]")
802
+
803
+ if parts:
804
+ status_line = ", ".join(parts)
805
+ console.print(f"[dim]Sessions:[/] {status_line}")
806
+
807
+
761
808
  def run_pilot(
762
809
  orchestrator: Any,
763
810
  *,
@@ -814,7 +861,8 @@ def _run_pilot_repl(
814
861
  })
815
862
 
816
863
  renderer.reset_turn()
817
- results = run_until_response(orchestrator, renderer)
864
+ max_steps = getattr(orchestrator.config.orchestrator, "max_steps_per_turn", 60)
865
+ results = run_until_response(orchestrator, renderer, max_steps=max_steps)
818
866
 
819
867
  # Record checkpoint
820
868
  state.record(
@@ -1103,8 +1151,9 @@ def _run_pilot_repl(
1103
1151
 
1104
1152
  # Execute steps until agent responds with a message
1105
1153
  renderer.reset_turn()
1154
+ max_steps = getattr(orchestrator.config.orchestrator, "max_steps_per_turn", 60)
1106
1155
  try:
1107
- results = run_until_response(orchestrator, renderer)
1156
+ results = run_until_response(orchestrator, renderer, max_steps=max_steps)
1108
1157
  except Exception as e:
1109
1158
  renderer.error(f"Step failed: {e}")
1110
1159
  # Remove the user message on failure
@@ -60,6 +60,7 @@ class OrchestratorConfig:
60
60
  prompt: str | None = None # path to prompt yaml
61
61
  tools: list[str] = field(default_factory=lambda: ["delegate", "converse", "check_session", "end_session", "bash"])
62
62
  max_steps: int = 50
63
+ max_steps_per_turn: int = 60 # Max tool-call steps before returning to user (pilot mode)
63
64
  parallel_delegations: int = 4
64
65
  compaction: CompactionConfig = field(default_factory=CompactionConfig)
65
66
 
@@ -172,6 +173,7 @@ class ZwarmConfig:
172
173
  "prompt": self.orchestrator.prompt,
173
174
  "tools": self.orchestrator.tools,
174
175
  "max_steps": self.orchestrator.max_steps,
176
+ "max_steps_per_turn": self.orchestrator.max_steps_per_turn,
175
177
  "parallel_delegations": self.orchestrator.parallel_delegations,
176
178
  "compaction": {
177
179
  "enabled": self.orchestrator.compaction.enabled,
@@ -81,6 +81,8 @@ class Orchestrator(YamlAgent):
81
81
  "total_tokens": 0,
82
82
  }
83
83
  )
84
+ # Callback for step progress (used by CLI to print tool calls)
85
+ _step_callback: Callable[[int, list[tuple[dict[str, Any], Any]]], None] | None = PrivateAttr(default=None)
84
86
 
85
87
  def model_post_init(self, __context: Any) -> None:
86
88
  """Initialize state after model creation."""
@@ -149,6 +151,10 @@ class Orchestrator(YamlAgent):
149
151
  """Access state manager."""
150
152
  return self._state
151
153
 
154
+ def get_executor_usage(self) -> dict[str, int]:
155
+ """Get aggregated token usage from executor sessions."""
156
+ return self._executor_usage
157
+
152
158
  def save_state(self) -> None:
153
159
  """Save orchestrator state for resume."""
154
160
  self._state.save_orchestrator_messages(self.messages)
@@ -550,7 +556,11 @@ Review what was accomplished in the previous session and delegate new tasks as n
550
556
  }
551
557
  # NUDGE and CONTINUE just continue
552
558
 
553
- self.step()
559
+ tool_results = self.step()
560
+
561
+ # Call step callback if registered (for CLI progress display)
562
+ if self._step_callback:
563
+ self._step_callback(self._step_count, tool_results)
554
564
 
555
565
  if self.stopCondition:
556
566
  break
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes