zwarm 3.2.1__tar.gz → 3.3.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 (43) hide show
  1. {zwarm-3.2.1 → zwarm-3.3.0}/PKG-INFO +6 -3
  2. {zwarm-3.2.1 → zwarm-3.3.0}/README.md +5 -2
  3. {zwarm-3.2.1 → zwarm-3.3.0}/pyproject.toml +1 -1
  4. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/cli/interactive.py +2 -2
  5. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/cli/main.py +75 -77
  6. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/cli/pilot.py +3 -1
  7. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/config.py +24 -9
  8. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/test_config.py +2 -3
  9. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/orchestrator.py +8 -44
  10. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/sessions/manager.py +210 -90
  11. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/tools/delegation.py +6 -1
  12. zwarm-3.2.1/src/zwarm/adapters/__init__.py +0 -21
  13. zwarm-3.2.1/src/zwarm/adapters/base.py +0 -109
  14. zwarm-3.2.1/src/zwarm/adapters/claude_code.py +0 -357
  15. zwarm-3.2.1/src/zwarm/adapters/codex_mcp.py +0 -1262
  16. zwarm-3.2.1/src/zwarm/adapters/registry.py +0 -69
  17. zwarm-3.2.1/src/zwarm/adapters/test_codex_mcp.py +0 -274
  18. zwarm-3.2.1/src/zwarm/adapters/test_registry.py +0 -68
  19. {zwarm-3.2.1 → zwarm-3.3.0}/.gitignore +0 -0
  20. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/__init__.py +0 -0
  21. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/cli/__init__.py +0 -0
  22. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/__init__.py +0 -0
  23. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/checkpoints.py +0 -0
  24. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/compact.py +0 -0
  25. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/costs.py +0 -0
  26. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/environment.py +0 -0
  27. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/models.py +0 -0
  28. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/state.py +0 -0
  29. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/test_compact.py +0 -0
  30. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/core/test_models.py +0 -0
  31. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/prompts/__init__.py +0 -0
  32. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/prompts/orchestrator.py +0 -0
  33. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/prompts/pilot.py +0 -0
  34. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/sessions/__init__.py +0 -0
  35. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/test_orchestrator_watchers.py +0 -0
  36. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/tools/__init__.py +0 -0
  37. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/watchers/__init__.py +0 -0
  38. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/watchers/base.py +0 -0
  39. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/watchers/builtin.py +0 -0
  40. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/watchers/llm_watcher.py +0 -0
  41. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/watchers/manager.py +0 -0
  42. {zwarm-3.2.1 → zwarm-3.3.0}/src/zwarm/watchers/registry.py +0 -0
  43. {zwarm-3.2.1 → zwarm-3.3.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.2.1
3
+ Version: 3.3.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
@@ -161,7 +161,7 @@ zwarm interactive
161
161
 
162
162
  | Command | Description |
163
163
  |---------|-------------|
164
- | `spawn "task"` | Start a new session |
164
+ | `spawn "task" [--search]` | Start a new session (--search enables web) |
165
165
  | `ls` | Dashboard of all sessions (with costs) |
166
166
  | `? ID` / `peek ID` | Quick status check |
167
167
  | `show ID` | Full session details |
@@ -225,7 +225,7 @@ The orchestrator LLM has access to:
225
225
 
226
226
  | Tool | Description |
227
227
  |------|-------------|
228
- | `delegate(task)` | Start a new coding session |
228
+ | `delegate(task, web_search=False)` | Start a new coding session |
229
229
  | `converse(id, msg)` | Continue a session |
230
230
  | `check_session(id)` | Get full session details |
231
231
  | `peek_session(id)` | Quick status check |
@@ -235,6 +235,8 @@ The orchestrator LLM has access to:
235
235
 
236
236
  **Async-first**: All sessions run in the background. The orchestrator uses `sleep()` to wait, then checks on progress.
237
237
 
238
+ **Web Search**: Pass `web_search=True` to `delegate()` for tasks needing current info (API docs, latest releases, etc.).
239
+
238
240
  ### Watchers
239
241
 
240
242
  Watchers monitor the orchestrator and intervene when needed:
@@ -317,6 +319,7 @@ max_steps = 50
317
319
 
318
320
  [executor]
319
321
  adapter = "codex_mcp"
322
+ web_search = false # Enable web search for all delegated sessions
320
323
 
321
324
  [watchers]
322
325
  enabled = ["progress", "budget", "delegation", "delegation_reminder"]
@@ -148,7 +148,7 @@ zwarm interactive
148
148
 
149
149
  | Command | Description |
150
150
  |---------|-------------|
151
- | `spawn "task"` | Start a new session |
151
+ | `spawn "task" [--search]` | Start a new session (--search enables web) |
152
152
  | `ls` | Dashboard of all sessions (with costs) |
153
153
  | `? ID` / `peek ID` | Quick status check |
154
154
  | `show ID` | Full session details |
@@ -212,7 +212,7 @@ The orchestrator LLM has access to:
212
212
 
213
213
  | Tool | Description |
214
214
  |------|-------------|
215
- | `delegate(task)` | Start a new coding session |
215
+ | `delegate(task, web_search=False)` | Start a new coding session |
216
216
  | `converse(id, msg)` | Continue a session |
217
217
  | `check_session(id)` | Get full session details |
218
218
  | `peek_session(id)` | Quick status check |
@@ -222,6 +222,8 @@ The orchestrator LLM has access to:
222
222
 
223
223
  **Async-first**: All sessions run in the background. The orchestrator uses `sleep()` to wait, then checks on progress.
224
224
 
225
+ **Web Search**: Pass `web_search=True` to `delegate()` for tasks needing current info (API docs, latest releases, etc.).
226
+
225
227
  ### Watchers
226
228
 
227
229
  Watchers monitor the orchestrator and intervene when needed:
@@ -304,6 +306,7 @@ max_steps = 50
304
306
 
305
307
  [executor]
306
308
  adapter = "codex_mcp"
309
+ web_search = false # Enable web search for all delegated sessions
307
310
 
308
311
  [watchers]
309
312
  enabled = ["progress", "budget", "delegation", "delegation_reminder"]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "zwarm"
3
- version = "3.2.1"
3
+ version = "3.3.0"
4
4
  description = "Multi-Agent CLI Orchestration Research Platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13,<3.14"
@@ -157,7 +157,7 @@ def cmd_help():
157
157
  table.add_column("Description")
158
158
 
159
159
  table.add_row("[bold]Session Lifecycle[/]", "")
160
- table.add_row('spawn "task" [--dir PATH]', "Start new session")
160
+ table.add_row('spawn "task" [--dir PATH] [--model M]', "Start new session")
161
161
  table.add_row('c ID "message"', "Continue conversation")
162
162
  table.add_row("kill ID | all", "Stop session(s)")
163
163
  table.add_row("rm ID | all", "Delete session(s)")
@@ -694,7 +694,7 @@ def run_interactive(
694
694
 
695
695
  elif cmd == "spawn":
696
696
  if not args:
697
- console.print(" [red]Usage:[/] spawn \"task\" [--dir PATH]")
697
+ console.print(" [red]Usage:[/] spawn \"task\" [--dir PATH] [--search]")
698
698
  else:
699
699
  # Parse spawn args
700
700
  task_parts = []
@@ -122,16 +122,6 @@ Manage zwarm configurations.
122
122
  app.add_typer(configs_app, name="configs")
123
123
 
124
124
 
125
- class AdapterType(str, Enum):
126
- codex_mcp = "codex_mcp"
127
- claude_code = "claude_code"
128
-
129
-
130
- class ModeType(str, Enum):
131
- sync = "sync"
132
- async_ = "async"
133
-
134
-
135
125
  @app.command()
136
126
  def orchestrate(
137
127
  task: Annotated[Optional[str], typer.Option("--task", "-t", help="The task to accomplish")] = None,
@@ -384,78 +374,68 @@ def pilot(
384
374
  @app.command()
385
375
  def exec(
386
376
  task: Annotated[str, typer.Option("--task", "-t", help="Task to execute")],
387
- adapter: Annotated[AdapterType, typer.Option("--adapter", "-a", help="Executor adapter")] = AdapterType.codex_mcp,
388
- mode: Annotated[ModeType, typer.Option("--mode", "-m", help="Execution mode")] = ModeType.sync,
389
377
  working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
390
378
  model: Annotated[Optional[str], typer.Option("--model", help="Model override")] = None,
379
+ wait: Annotated[bool, typer.Option("--wait", help="Wait for completion and show result")] = False,
391
380
  ):
392
381
  """
393
- Run a single executor directly (for testing).
382
+ Run a single Codex session directly (for testing).
394
383
 
395
- Useful for testing adapters without the full orchestrator loop.
384
+ Spawns a session using CodexSessionManager - same as interactive/pilot.
385
+ Web search is always enabled via .codex/config.toml (set up by `zwarm init`).
396
386
 
397
387
  [bold]Examples:[/]
398
- [dim]# Test Codex[/]
399
- $ zwarm exec --task "What is 2+2?"
388
+ [dim]# Quick test[/]
389
+ $ zwarm exec --task "What is 2+2?" --wait
400
390
 
401
- [dim]# Test Claude Code[/]
402
- $ zwarm exec -a claude_code --task "List files in current dir"
391
+ [dim]# Run in background[/]
392
+ $ zwarm exec --task "Build feature"
403
393
 
404
- [dim]# Async mode[/]
405
- $ zwarm exec --task "Build feature" --mode async
394
+ [dim]# Web search is always available[/]
395
+ $ zwarm exec --task "Find latest FastAPI docs" --wait
406
396
  """
407
- from zwarm.adapters import get_adapter
408
-
409
- console.print(f"[bold]Running executor directly...[/]")
410
- console.print(f" Adapter: [cyan]{adapter.value}[/]")
411
- console.print(f" Mode: {mode.value}")
412
- console.print(f" Task: {task}")
413
-
414
- # Use isolated codex config if available
415
- config_path = working_dir / ".zwarm" / "codex.toml"
416
- if not config_path.exists():
417
- config_path = None
418
-
419
- try:
420
- executor = get_adapter(adapter.value, model=model, config_path=config_path)
421
- except ValueError as e:
422
- console.print(f"[red]Error:[/] {e}")
423
- sys.exit(1)
424
-
425
- async def run():
426
- try:
427
- session = await executor.start_session(
428
- task=task,
429
- working_dir=working_dir.absolute(),
430
- mode=mode.value,
431
- model=model,
432
- )
433
-
434
- console.print(f"\n[green]Session started:[/] {session.id[:8]}")
435
-
436
- if mode == ModeType.sync:
437
- response = session.messages[-1].content if session.messages else "(no response)"
438
- console.print(f"\n[bold]Response:[/]\n{response}")
397
+ import time
398
+ from zwarm.sessions import CodexSessionManager, SessionStatus
439
399
 
440
- # Interactive loop for sync mode
441
- while True:
442
- try:
443
- user_input = console.input("\n[dim]> (type message or 'exit')[/] ")
444
- if user_input.lower() == "exit" or not user_input:
445
- break
400
+ console.print(f"[bold]Running Codex session...[/]")
401
+ console.print(f" Task: {task[:60]}{'...' if len(task) > 60 else ''}")
402
+ if model:
403
+ console.print(f" Model: {model}")
446
404
 
447
- response = await executor.send_message(session, user_input)
448
- console.print(f"\n[bold]Response:[/]\n{response}")
449
- except KeyboardInterrupt:
450
- break
451
- else:
452
- console.print("[dim]Async mode - session running in background.[/]")
453
- console.print("Use 'zwarm status' to check progress.")
405
+ manager = CodexSessionManager(working_dir / ".zwarm")
406
+ effective_model = model or "gpt-5.1-codex-mini"
454
407
 
455
- finally:
456
- await executor.cleanup()
408
+ session = manager.start_session(
409
+ task=task,
410
+ working_dir=working_dir.absolute(),
411
+ model=effective_model,
412
+ )
457
413
 
458
- asyncio.run(run())
414
+ console.print(f"\n[green]Session started:[/] {session.short_id}")
415
+
416
+ if wait:
417
+ console.print("[dim]Waiting for completion...[/]")
418
+ while True:
419
+ time.sleep(2)
420
+ session = manager.get_session(session.id)
421
+ if session.status != SessionStatus.RUNNING:
422
+ break
423
+
424
+ if session.status == SessionStatus.COMPLETED:
425
+ console.print(f"\n[green]✓ Completed[/]")
426
+ # Show last assistant message
427
+ for msg in reversed(session.messages):
428
+ if msg.role == "assistant":
429
+ console.print(f"\n[bold]Response:[/]\n{msg.content}")
430
+ break
431
+ else:
432
+ console.print(f"\n[red]Status:[/] {session.status.value}")
433
+ if session.error:
434
+ console.print(f"[red]Error:[/] {session.error}")
435
+ else:
436
+ console.print("[dim]Running in background. Check with:[/]")
437
+ console.print(f" zwarm sessions")
438
+ console.print(f" zwarm session show {session.short_id}")
459
439
 
460
440
 
461
441
  @app.command()
@@ -740,12 +720,12 @@ def init(
740
720
  [bold]Creates:[/]
741
721
  [cyan].zwarm/[/] State directory for sessions and events
742
722
  [cyan].zwarm/config.toml[/] Runtime settings (weave, adapter, watchers)
743
- [cyan].zwarm/codex.toml[/] Codex CLI settings (model, reasoning effort)
723
+ [cyan].zwarm/codex.toml[/] Codex CLI settings (model, web search, etc.)
744
724
  [cyan]zwarm.yaml[/] Project config (optional, with --with-project)
745
725
 
746
726
  [bold]Configuration relationship:[/]
747
727
  config.toml → Controls zwarm itself (tracing, which watchers run)
748
- codex.toml → Controls the Codex CLI that runs executor sessions
728
+ codex.toml → Codex settings, parsed by zwarm and passed via -c overrides
749
729
  zwarm.yaml → Project-specific context injected into orchestrator
750
730
 
751
731
  [bold]Examples:[/]
@@ -939,7 +919,7 @@ def init(
939
919
  # Explain config files
940
920
  console.print("[bold]Configuration files:[/]")
941
921
  console.print(" [cyan].zwarm/config.toml[/] - Runtime settings (Weave tracing, watchers)")
942
- console.print(" [cyan].zwarm/codex.toml[/] - Codex CLI settings (model, reasoning effort)")
922
+ console.print(" [cyan].zwarm/codex.toml[/] - Codex CLI settings (model, web search, sandbox)")
943
923
  if create_project_config:
944
924
  console.print(" [cyan]zwarm.yaml[/] - Project context and constraints")
945
925
  console.print()
@@ -982,6 +962,7 @@ def _generate_config_toml(
982
962
  "[executor]",
983
963
  f'adapter = "{adapter}"',
984
964
  "# model = \"\" # Optional model override",
965
+ "# web_search = false # Enable web search for delegated sessions",
985
966
  "",
986
967
  "[watchers]",
987
968
  f"enabled = {watchers}",
@@ -1006,22 +987,35 @@ def _generate_codex_toml(
1006
987
  """
1007
988
  Generate codex.toml for isolated codex configuration.
1008
989
 
1009
- This file is used by zwarm instead of ~/.codex/config.toml to ensure
1010
- consistent behavior across different environments.
990
+ This file is parsed by zwarm and settings are passed to codex via -c overrides.
991
+ Each .zwarm directory has its own codex config, independent of ~/.codex/config.toml.
1011
992
  """
1012
993
  lines = [
1013
994
  "# Codex configuration for zwarm",
1014
- "# This file isolates zwarm's codex settings from your global ~/.codex/config.toml",
995
+ "# zwarm parses this file and passes settings to codex via -c overrides",
996
+ "# Each .zwarm dir has its own config, independent of ~/.codex/config.toml",
1015
997
  "# Generated by 'zwarm init'",
1016
998
  "",
1017
999
  "# Model settings",
1018
1000
  f'model = "{model}"',
1019
1001
  f'model_reasoning_effort = "{reasoning_effort}" # low | medium | high',
1020
1002
  "",
1021
- "# Approval settings - zwarm manages these automatically",
1022
- "# disable_response_storage = false",
1003
+ "# DANGER MODE - bypasses all safety controls",
1004
+ "# Set to true to use --dangerously-bypass-approvals-and-sandbox",
1005
+ "full_danger = true",
1006
+ "",
1007
+ "# Web search - enables web_search tool for agents",
1008
+ "[features]",
1009
+ "web_search_request = true",
1023
1010
  "",
1024
- "# You can override any codex setting here",
1011
+ "# Sandbox settings - network access required for web search",
1012
+ "[sandbox_workspace_write]",
1013
+ "network_access = true",
1014
+ "",
1015
+ "# Approval policy - 'never' means no human approval needed",
1016
+ "# approval_policy = \"never\"",
1017
+ "",
1018
+ "# You can add any codex config key here",
1025
1019
  "# See: https://github.com/openai/codex#configuration",
1026
1020
  "",
1027
1021
  ]
@@ -1534,6 +1528,7 @@ def session_start(
1534
1528
  Start a new Codex session in the background.
1535
1529
 
1536
1530
  The session runs independently and you can check on it later.
1531
+ Web search is always enabled via .codex/config.toml (set up by `zwarm init`).
1537
1532
 
1538
1533
  [bold]Examples:[/]
1539
1534
  [dim]# Simple task[/]
@@ -1541,6 +1536,9 @@ def session_start(
1541
1536
 
1542
1537
  [dim]# With specific model[/]
1543
1538
  $ zwarm session start "Refactor the API" --model gpt-5.1-codex-max
1539
+
1540
+ [dim]# Web search is always available[/]
1541
+ $ zwarm session start "Research latest OAuth2 best practices"
1544
1542
  """
1545
1543
  from zwarm.sessions import CodexSessionManager
1546
1544
 
@@ -186,10 +186,12 @@ def build_pilot_orchestrator(
186
186
  lm_class = lm_map.get(lm_choice, GPT5LargeVerbose)
187
187
  lm = lm_class()
188
188
 
189
- # Load configuration
189
+ # Load configuration from working_dir (not cwd!)
190
+ # This ensures config.toml and .env are loaded from the project being worked on
190
191
  config = load_config(
191
192
  config_path=config_path,
192
193
  overrides=overrides,
194
+ working_dir=working_dir,
193
195
  )
194
196
 
195
197
  # Resolve working directory
@@ -37,6 +37,7 @@ class ExecutorConfig:
37
37
  sandbox: str = "workspace-write" # read-only | workspace-write | danger-full-access
38
38
  timeout: int = 3600
39
39
  reasoning_effort: str | None = "high" # low | medium | high (default to high for compatibility)
40
+ # Note: web_search is always enabled via .codex/config.toml (set up by `zwarm init`)
40
41
 
41
42
 
42
43
  @dataclass
@@ -60,7 +61,6 @@ class OrchestratorConfig:
60
61
  tools: list[str] = field(default_factory=lambda: ["delegate", "converse", "check_session", "end_session", "bash"])
61
62
  max_steps: int = 50
62
63
  parallel_delegations: int = 4
63
- sync_first: bool = True # prefer sync mode by default
64
64
  compaction: CompactionConfig = field(default_factory=CompactionConfig)
65
65
 
66
66
  # Directory restrictions for agent delegations
@@ -173,7 +173,6 @@ class ZwarmConfig:
173
173
  "tools": self.orchestrator.tools,
174
174
  "max_steps": self.orchestrator.max_steps,
175
175
  "parallel_delegations": self.orchestrator.parallel_delegations,
176
- "sync_first": self.orchestrator.sync_first,
177
176
  "compaction": {
178
177
  "enabled": self.orchestrator.compaction.enabled,
179
178
  "max_tokens": self.orchestrator.compaction.max_tokens,
@@ -195,15 +194,16 @@ class ZwarmConfig:
195
194
  }
196
195
 
197
196
 
198
- def load_env(path: Path | None = None) -> None:
197
+ def load_env(path: Path | None = None, base_dir: Path | None = None) -> None:
199
198
  """Load .env file if it exists."""
200
199
  if path is None:
201
- path = Path.cwd() / ".env"
200
+ base = base_dir or Path.cwd()
201
+ path = base / ".env"
202
202
  if path.exists():
203
203
  load_dotenv(path)
204
204
 
205
205
 
206
- def load_toml_config(path: Path | None = None) -> dict[str, Any]:
206
+ def load_toml_config(path: Path | None = None, base_dir: Path | None = None) -> dict[str, Any]:
207
207
  """
208
208
  Load config.toml file.
209
209
 
@@ -211,11 +211,16 @@ def load_toml_config(path: Path | None = None) -> dict[str, Any]:
211
211
  1. Explicit path (if provided)
212
212
  2. .zwarm/config.toml (new standard location)
213
213
  3. config.toml (legacy location for backwards compat)
214
+
215
+ Args:
216
+ path: Explicit path to config.toml
217
+ base_dir: Base directory to search in (defaults to cwd)
214
218
  """
215
219
  if path is None:
220
+ base = base_dir or Path.cwd()
216
221
  # Try new location first
217
- new_path = Path.cwd() / ".zwarm" / "config.toml"
218
- legacy_path = Path.cwd() / "config.toml"
222
+ new_path = base / ".zwarm" / "config.toml"
223
+ legacy_path = base / "config.toml"
219
224
  if new_path.exists():
220
225
  path = new_path
221
226
  elif legacy_path.exists():
@@ -306,6 +311,7 @@ def load_config(
306
311
  toml_path: Path | None = None,
307
312
  env_path: Path | None = None,
308
313
  overrides: list[str] | None = None,
314
+ working_dir: Path | None = None,
309
315
  ) -> ZwarmConfig:
310
316
  """
311
317
  Load configuration with full precedence chain:
@@ -314,15 +320,24 @@ def load_config(
314
320
  3. YAML config file (if provided)
315
321
  4. CLI overrides (--set key=value)
316
322
  5. Environment variables (for secrets)
323
+
324
+ Args:
325
+ config_path: Path to YAML config file
326
+ toml_path: Explicit path to config.toml
327
+ env_path: Explicit path to .env file
328
+ overrides: CLI overrides (--set key=value)
329
+ working_dir: Working directory to search for config files (defaults to cwd).
330
+ This is important when using --working-dir flag to ensure
331
+ config is loaded from the project directory, not invoke directory.
317
332
  """
318
333
  # Load .env first (for secrets)
319
- load_env(env_path)
334
+ load_env(env_path, base_dir=working_dir)
320
335
 
321
336
  # Start with defaults
322
337
  config_dict: dict[str, Any] = {}
323
338
 
324
339
  # Layer in config.toml
325
- toml_config = load_toml_config(toml_path)
340
+ toml_config = load_toml_config(toml_path, base_dir=working_dir)
326
341
  if toml_config:
327
342
  config_dict = deep_merge(config_dict, toml_config)
328
343
 
@@ -20,7 +20,6 @@ def test_default_config():
20
20
  assert config.executor.adapter == "codex_mcp"
21
21
  assert config.executor.sandbox == "workspace-write"
22
22
  assert config.orchestrator.lm == "gpt-5-mini"
23
- assert config.orchestrator.sync_first is True
24
23
  assert config.state_dir == ".zwarm"
25
24
 
26
25
 
@@ -68,8 +67,8 @@ def test_apply_overrides():
68
67
  assert result["executor"]["adapter"] == "claude_code"
69
68
 
70
69
  # Override with boolean
71
- result = apply_overrides(config, ["orchestrator.sync_first=false"])
72
- assert result["orchestrator"]["sync_first"] is False
70
+ result = apply_overrides(config, ["executor.web_search=true"])
71
+ assert result["executor"]["web_search"] is True
73
72
 
74
73
  # Create new nested path
75
74
  result = apply_overrides(config, ["weave.project=my-project"])
@@ -23,7 +23,6 @@ from wbal.helper import TOOL_CALL_TYPE, format_openai_tool_response
23
23
  from wbal.lm import LM as wbalLMGeneric
24
24
  from wbal.lm import GPT5LargeVerbose
25
25
 
26
- from zwarm.adapters import ExecutorAdapter, get_adapter
27
26
  from zwarm.core.compact import compact_messages, should_compact
28
27
  from zwarm.core.config import ZwarmConfig, load_config
29
28
  from zwarm.core.environment import OrchestratorEnv
@@ -72,7 +71,6 @@ class Orchestrator(YamlAgent):
72
71
  # State management
73
72
  _state: StateManager = PrivateAttr()
74
73
  _sessions: dict[str, ConversationSession] = PrivateAttr(default_factory=dict)
75
- _adapters: dict[str, ExecutorAdapter] = PrivateAttr(default_factory=dict)
76
74
  _watcher_manager: WatcherManager | None = PrivateAttr(default=None)
77
75
  _resumed: bool = PrivateAttr(default=False)
78
76
  _total_tokens: int = PrivateAttr(default=0) # Cumulative orchestrator tokens
@@ -85,7 +83,7 @@ class Orchestrator(YamlAgent):
85
83
  )
86
84
 
87
85
  def model_post_init(self, __context: Any) -> None:
88
- """Initialize state and adapters after model creation."""
86
+ """Initialize state after model creation."""
89
87
  super().model_post_init(__context)
90
88
 
91
89
  # Initialize state manager with instance isolation
@@ -151,41 +149,6 @@ class Orchestrator(YamlAgent):
151
149
  """Access state manager."""
152
150
  return self._state
153
151
 
154
- def _get_adapter(self, name: str) -> ExecutorAdapter:
155
- """Get or create an adapter by name using the adapter registry."""
156
- if name not in self._adapters:
157
- # Get model from config (adapters have their own defaults if None)
158
- model = self.config.executor.model
159
-
160
- # Use isolated codex config if available
161
- config_path = self.working_dir / self.config.state_dir / "codex.toml"
162
- if not config_path.exists():
163
- config_path = None # Fallback to adapter defaults
164
-
165
- self._adapters[name] = get_adapter(
166
- name, model=model, config_path=config_path
167
- )
168
- return self._adapters[name]
169
-
170
- def get_executor_usage(self) -> dict[str, int]:
171
- """Get aggregated token usage across all executors."""
172
- total = {
173
- "input_tokens": 0,
174
- "output_tokens": 0,
175
- "total_tokens": 0,
176
- }
177
- for adapter in self._adapters.values():
178
- if hasattr(adapter, "total_usage"):
179
- usage = adapter.total_usage
180
- for key in total:
181
- total[key] += usage.get(key, 0)
182
- return total
183
-
184
- @property
185
- def executor_usage(self) -> dict[str, int]:
186
- """Aggregated executor token usage (for Weave tracking)."""
187
- return self.get_executor_usage()
188
-
189
152
  def save_state(self) -> None:
190
153
  """Save orchestrator state for resume."""
191
154
  self._state.save_orchestrator_messages(self.messages)
@@ -599,8 +562,7 @@ Review what was accomplished in the previous session and delegate new tasks as n
599
562
 
600
563
  async def cleanup(self) -> None:
601
564
  """Clean up resources."""
602
- for adapter in self._adapters.values():
603
- await adapter.cleanup()
565
+ pass # Session cleanup handled by CodexSessionManager
604
566
 
605
567
 
606
568
  def build_orchestrator(
@@ -631,15 +593,17 @@ def build_orchestrator(
631
593
  """
632
594
  from uuid import uuid4
633
595
 
634
- # Load configuration
596
+ # Resolve working directory first (needed for config loading)
597
+ working_dir = working_dir or Path.cwd()
598
+
599
+ # Load configuration from working_dir (not cwd!)
600
+ # This ensures config.toml and .env are loaded from the project being worked on
635
601
  config = load_config(
636
602
  config_path=config_path,
637
603
  overrides=overrides,
604
+ working_dir=working_dir,
638
605
  )
639
606
 
640
- # Resolve working directory
641
- working_dir = working_dir or Path.cwd()
642
-
643
607
  # Generate instance ID if not provided (enables isolation by default for new runs)
644
608
  # For resume, instance_id should be provided explicitly
645
609
  if instance_id is None and not resume: