alpiecode 3.0.0__tar.gz → 4.0.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 (40) hide show
  1. {alpiecode-3.0.0 → alpiecode-4.0.0}/PKG-INFO +1 -1
  2. {alpiecode-3.0.0 → alpiecode-4.0.0}/pyproject.toml +1 -1
  3. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/PKG-INFO +1 -1
  4. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/__init__.py +5 -0
  5. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/agent.py +16 -2
  6. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/cli.py +2 -0
  7. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/client.py +1 -0
  8. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/config.py +5 -4
  9. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/ipython_ext.py +16 -0
  10. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/orchestrator.py +92 -43
  11. alpiecode-4.0.0/src/codeagent/prompt.py +404 -0
  12. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/server.py +9 -4
  13. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/tools.py +3 -1
  14. alpiecode-3.0.0/src/codeagent/prompt.py +0 -454
  15. {alpiecode-3.0.0 → alpiecode-4.0.0}/README.md +0 -0
  16. {alpiecode-3.0.0 → alpiecode-4.0.0}/setup.cfg +0 -0
  17. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode/__init__.py +0 -0
  18. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/SOURCES.txt +0 -0
  19. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  20. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
  21. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/requires.txt +0 -0
  22. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/alpiecode.egg-info/top_level.txt +0 -0
  23. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/__init__.py +0 -0
  24. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/base.py +0 -0
  25. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/local_backend.py +0 -0
  26. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/backends/openai_backend.py +0 -0
  27. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/cache.py +0 -0
  28. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/compaction.py +0 -0
  29. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/context.py +0 -0
  30. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/doctor.py +0 -0
  31. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/executor.py +0 -0
  32. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/extension/alpiecode.vsix +0 -0
  33. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/github.py +0 -0
  34. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/guardian.py +0 -0
  35. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/local_model.py +0 -0
  36. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/media.py +0 -0
  37. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/memory.py +0 -0
  38. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/session.py +0 -0
  39. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/updater.py +0 -0
  40. {alpiecode-3.0.0 → alpiecode-4.0.0}/src/codeagent/vscode_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 3.0.0
3
+ Version: 4.0.0
4
4
  Summary: AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alpiecode"
7
- version = "3.0.0"
7
+ version = "4.0.0"
8
8
  description = "AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 3.0.0
3
+ Version: 4.0.0
4
4
  Summary: AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -1,3 +1,8 @@
1
+ import warnings
2
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
3
+ warnings.filterwarnings("ignore", category=FutureWarning)
4
+ warnings.filterwarnings("ignore", module="jupyter_client.*")
5
+
1
6
  """
2
7
  AlpieCode — Autonomous AI Coding Agent powered by local 169Pi GGUF VLM & Online API.
3
8
 
@@ -1,3 +1,8 @@
1
+ import warnings
2
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
3
+ warnings.filterwarnings("ignore", category=FutureWarning)
4
+ warnings.filterwarnings("ignore", module="jupyter_client.*")
5
+
1
6
  """
2
7
  CLI agent wrapper and presentation adapter for AlpieCode.
3
8
 
@@ -150,6 +155,7 @@ def run_agent(
150
155
  url: str = None,
151
156
  github_repo: str = None,
152
157
  server_url: str = None,
158
+ complexity: str = None,
153
159
  ) -> list:
154
160
  """Run non-interactive agent task with Rich presentation."""
155
161
  workdir = workdir.resolve()
@@ -173,6 +179,7 @@ def run_agent(
173
179
  video_path=video_path,
174
180
  url=url,
175
181
  github_repo=github_repo,
182
+ complexity=complexity,
176
183
  )
177
184
  else:
178
185
  backend = resolve_backend(cfg)
@@ -187,6 +194,7 @@ def run_agent(
187
194
  video_path=video_path,
188
195
  url=url,
189
196
  github_repo=github_repo,
197
+ complexity=complexity,
190
198
  )
191
199
 
192
200
  _checkpoint(workdir, "checkpoint: start")
@@ -209,10 +217,12 @@ def run_agent(
209
217
  console.print(f"📂 Workdir: {workdir}", style="dim")
210
218
  if not data["is_offline"]:
211
219
  console.print(f"🌐 Mode: [bold green]ONLINE[/bold green]", style="dim")
212
- console.print(f"🤖 Model: [bold cyan]alpie_9b[/bold cyan]", style="dim")
213
220
  else:
214
221
  console.print(f"🧠 Mode: [bold yellow]OFFLINE[/bold yellow]", style="dim")
215
- console.print(f"🤖 Model: [bold cyan]alpie_9b (Local GGUF)[/bold cyan]", style="dim")
222
+ comp = data.get("complexity", "low")
223
+ comp_label = {"qa": "Q&A (instant)", "low": "Low (fast)", "medium": "Medium (balanced)", "high": "High (thorough)"}.get(comp, comp)
224
+ comp_color = {"qa": "cyan", "low": "green", "medium": "yellow", "high": "red"}.get(comp, "white")
225
+ console.print(f"⚡ Complexity: [bold {comp_color}]{comp_label}[/bold {comp_color}]", style="dim")
216
226
  if cfg.enable_thinking:
217
227
  console.print(f"🧠 Reasoning: [bold green]ON[/bold green]", style="dim")
218
228
  else:
@@ -234,6 +244,10 @@ def run_agent(
234
244
  elif event.type == "compaction" and verbose:
235
245
  console.print("🗜️ Compacting context (approaching token limit)...", style="yellow")
236
246
 
247
+ elif event.type == "wrap_up" and verbose:
248
+ if HAS_RICH:
249
+ console.print(f"⏳ [bold yellow]Wrap-up: {event.data['remaining']} turns remaining[/bold yellow]", style="yellow")
250
+
237
251
  elif event.type == "thinking" and verbose:
238
252
  _print_reasoning(event.data["content"])
239
253
 
@@ -76,6 +76,7 @@ def main():
76
76
  common.add_argument("--max-turns", type=int, default=None, help="Override max turns")
77
77
  common.add_argument("--thinking", "--reasoning", dest="thinking", action="store_true", help="Enable VLM reasoning mode")
78
78
  common.add_argument("--no-thinking", "--non-thinking", dest="no_thinking", action="store_true", help="Disable VLM reasoning mode")
79
+ common.add_argument("--complexity", choices=["low", "medium", "high"], default=None, help="Task complexity: low (fast, 10 turns), medium (balanced, 20 turns), high (thorough, 40 turns)")
79
80
  common.add_argument("--no-update", action="store_true", help="Skip automatic update check")
80
81
  common.add_argument("--quiet", action="store_true", help="Suppress per-turn logging")
81
82
 
@@ -166,6 +167,7 @@ def main():
166
167
  image_path=args.image,
167
168
  video_path=getattr(args, "video", None),
168
169
  url=getattr(args, "url", None),
170
+ complexity=getattr(args, "complexity", None),
169
171
  github_repo=getattr(args, "github", None),
170
172
  )
171
173
 
@@ -73,6 +73,7 @@ class AlpieCodeClient:
73
73
  video_path: Optional[str] = None,
74
74
  url: Optional[str] = None,
75
75
  github_repo: Optional[str] = None,
76
+ complexity: Optional[str] = None,
76
77
  ) -> Iterator[AgentEvent]:
77
78
  """
78
79
  Stream agent events from POST /chat using Server-Sent Events (SSE).
@@ -18,7 +18,7 @@ CONFIG_DIR = Path.home() / ".alpiecode"
18
18
  CONFIG_PATH = CONFIG_DIR / "config.json"
19
19
 
20
20
  # Config version — bump this when defaults change to trigger auto-migration
21
- CONFIG_VERSION = 5 # v5: model alpie_9b, reasoning OFF by default, clean display
21
+ CONFIG_VERSION = 6 # v6: smart flow, max_turns 20, complexity-aware
22
22
 
23
23
  DEFAULTS = {
24
24
  "base_url": "http://20.245.200.125:8000/v1", # Primary endpoint
@@ -26,7 +26,7 @@ DEFAULTS = {
26
26
  "model_repo": "169Pi/Alpie_learn_prototype_GGUF_NEW",
27
27
  "api_key": "not-needed",
28
28
  "hf_token": None,
29
- "max_turns": 50,
29
+ "max_turns": 20,
30
30
  "temperature": 0.1,
31
31
  "max_tokens": 8192,
32
32
  "enable_thinking": False, # Reasoning OFF by default
@@ -88,7 +88,7 @@ class Config:
88
88
  model_repo: str = "169Pi/Alpie_learn_prototype_GGUF_NEW" # HuggingFace repo for offline GGUF
89
89
  api_key: str = "not-needed"
90
90
  hf_token: Optional[str] = None
91
- max_turns: int = 50
91
+ max_turns: int = 20
92
92
  temperature: float = 0.1
93
93
  max_tokens: int = 8192
94
94
  enable_thinking: bool = False
@@ -111,12 +111,13 @@ def load_config() -> Config:
111
111
  # v1 → v2: n_ctx was 16384, upgrade to 32768
112
112
  if saved_data.get("n_ctx") == 16384:
113
113
  data["n_ctx"] = 32768
114
- # v4v5: model alpie_9b, reasoning OFF default
114
+ # v5v6: smart flow, max_turns 20
115
115
  data["base_url"] = "http://20.245.200.125:8000/v1"
116
116
  data["model"] = "alpie_9b"
117
117
  data["enable_thinking"] = False
118
118
  data["temperature"] = 0.1
119
119
  data["max_tokens"] = 8192
120
+ data["max_turns"] = 20
120
121
  data["config_version"] = CONFIG_VERSION
121
122
  needs_save = True
122
123
  except Exception:
@@ -11,9 +11,16 @@ and Google Colab via cell/line magic commands and rich interactive displays:
11
11
  """
12
12
 
13
13
  import sys
14
+ import warnings
14
15
  from pathlib import Path
15
16
  from typing import Optional
16
17
 
18
+ # Suppress noisy Jupyter / Python 3.12+ deprecation warnings (e.g. datetime.utcnow in jupyter_client)
19
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
20
+ warnings.filterwarnings("ignore", category=FutureWarning)
21
+ warnings.filterwarnings("ignore", module="jupyter_client.*")
22
+ warnings.filterwarnings("ignore", module="ipykernel.*")
23
+
17
24
  from .config import load_config
18
25
  from .agent import run_agent
19
26
 
@@ -42,6 +49,15 @@ def _display_html(html_str: str):
42
49
  print(html_str)
43
50
 
44
51
 
52
+ def _display_markdown(md_str: str):
53
+ """Render rich Markdown in Jupyter or Google Colab."""
54
+ try:
55
+ from IPython.display import Markdown, display
56
+ display(Markdown(md_str))
57
+ except Exception:
58
+ print(md_str)
59
+
60
+
45
61
  def alpie_magic(line: str, cell: Optional[str] = None):
46
62
  """
47
63
  %alpie <task> (line magic)
@@ -1,40 +1,31 @@
1
1
  """
2
2
  Agent orchestrator for AlpieCode.
3
3
 
4
- Coordinates inference backends, context management, prompt construction, and tool execution.
5
- Yields a stream of transport-agnostic AgentEvent objects.
4
+ Owns the turn loop, backend resolution, caching, and event stream.
6
5
  """
7
6
 
8
- import json
9
- from dataclasses import dataclass
10
- from pathlib import Path
11
- from typing import Any, Dict, Iterator, Optional
7
+ import copy
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Dict, Iterator, List, Optional
12
10
 
13
- from .backends.base import ChatResponse, InferenceBackend, ToolCall
11
+ from .backends.base import InferenceBackend, ChatResponse
14
12
  from .backends.local_backend import LocalBackend
15
13
  from .backends.openai_backend import OpenAIBackend
16
14
  from .cache import get_cache
17
15
  from .config import Config, is_server_reachable
18
- from .context import ContextManager
19
- from .executor import ToolExecutor, ToolResult
20
16
  from .memory import extract_and_save_memories
21
- from .prompt import PromptBuilder, is_simple_task
17
+ from .prompt import PromptBuilder, classify_task, COMPLEXITY_CONFIG
22
18
  from .session import Session, SessionManager
23
19
 
24
20
 
25
21
  @dataclass
26
22
  class AgentEvent:
27
- """Structured event yielded by the orchestrator."""
28
23
  type: str
29
24
  data: Dict[str, Any]
30
25
 
31
26
 
32
27
  def resolve_backend(cfg: Config, timeout: float = 2.0) -> InferenceBackend:
33
- """Resolve online vs offline backend based on server reachability.
34
-
35
- Uses a generous timeout at startup (2s default) to avoid false negatives
36
- when the remote API is slow to respond (e.g. Azure VM cold start).
37
- """
28
+ """Resolve online vs offline backend based on server reachability."""
38
29
  if is_server_reachable(cfg.base_url, timeout=timeout):
39
30
  return OpenAIBackend(cfg)
40
31
  return LocalBackend(cfg)
@@ -60,12 +51,25 @@ class AgentOrchestrator:
60
51
  video_path: Optional[str] = None,
61
52
  url: Optional[str] = None,
62
53
  github_repo: Optional[str] = None,
54
+ complexity: Optional[str] = None,
63
55
  ) -> Iterator[AgentEvent]:
64
- """
65
- Run full agent task loop for a session. Yields AgentEvents.
66
- """
67
- # ── Response cache check (instant return for repeated prompts) ──
68
- # Only cache pure text prompts (no images, videos, URLs, or GitHub repos)
56
+ """Run full agent task loop. Yields AgentEvents."""
57
+
58
+ # ── Auto-classify complexity if not provided ──
59
+ if complexity is None:
60
+ complexity = classify_task(task)
61
+
62
+ comp_cfg = COMPLEXITY_CONFIG.get(complexity, COMPLEXITY_CONFIG["low"])
63
+
64
+ # ── Determine effective max_turns and max_tokens ──
65
+ effective_max_turns = min(cfg.max_turns, comp_cfg["max_turns"])
66
+ effective_max_tokens = comp_cfg["max_tokens"]
67
+
68
+ # User override: if they set --max-turns explicitly, respect it
69
+ if cfg.max_turns != 20: # 20 is new default, so non-default = explicit
70
+ effective_max_turns = cfg.max_turns
71
+
72
+ # ── Response cache check ──
69
73
  is_cacheable = not any([image_path, video_path, url, github_repo])
70
74
  if is_cacheable:
71
75
  cache = get_cache()
@@ -77,6 +81,7 @@ class AgentOrchestrator:
77
81
  "backend": "cache",
78
82
  "is_offline": False,
79
83
  "tool_count": 0,
84
+ "complexity": complexity,
80
85
  })
81
86
  yield AgentEvent("cache_hit", {
82
87
  "message": "Returning cached response (instant)",
@@ -87,19 +92,18 @@ class AgentOrchestrator:
87
92
  yield AgentEvent("done", {"summary": cached["response"]})
88
93
  return
89
94
 
90
- # Dynamic backend re-check: if currently on LocalBackend but remote
91
- # API is now reachable, switch to OnlineBackend automatically.
92
- # This handles the case where the server started offline but the
93
- # remote API came online later (e.g. VM cold start, network hiccup).
95
+ # ── Dynamic backend re-check ──
94
96
  if isinstance(self.backend, LocalBackend) and is_server_reachable(cfg.base_url, timeout=1.5):
95
97
  self.backend = OpenAIBackend(cfg)
96
98
 
97
99
  is_offline = not self.backend.is_available or isinstance(self.backend, LocalBackend)
98
100
  session.is_offline = is_offline
99
101
 
100
- # Configure tools & system prompt
101
- active_tools = self.prompt_builder.get_tools(is_offline=is_offline)
102
- system_prompt = self.prompt_builder.build_system_prompt(session.workdir, is_offline=is_offline)
102
+ # ── Configure tools & system prompt based on complexity ──
103
+ active_tools = self.prompt_builder.get_tools(is_offline=is_offline, complexity=complexity)
104
+ system_prompt = self.prompt_builder.build_system_prompt(
105
+ session.workdir, is_offline=is_offline, complexity=complexity
106
+ )
103
107
  session.context.set_system_prompt(system_prompt)
104
108
 
105
109
  user_content = self.prompt_builder.build_user_content(
@@ -118,50 +122,65 @@ class AgentOrchestrator:
118
122
  "backend": self.backend.name,
119
123
  "is_offline": is_offline,
120
124
  "tool_count": len(active_tools),
125
+ "complexity": complexity,
121
126
  })
122
127
 
123
- # Adaptive thinking check
128
+ # ── Adaptive thinking ──
124
129
  enable_thinking = cfg.enable_thinking
125
- if enable_thinking and is_simple_task(task):
130
+ if enable_thinking and complexity in ("qa", "low"):
126
131
  enable_thinking = False
127
132
  yield AgentEvent("adaptive_mode", {"message": "Simple task detected, skipping deep reasoning."})
128
133
 
129
- for turn in range(cfg.max_turns):
134
+ # ── Turn loop ──
135
+ wrap_up_injected = False
136
+
137
+ for turn in range(effective_max_turns):
130
138
  if session.cancelled:
131
139
  yield AgentEvent("cancelled", {"turn": turn + 1})
132
140
  break
133
141
 
134
- # Context compaction check
142
+ # Context compaction
135
143
  if session.context.check_and_compact():
136
144
  yield AgentEvent("compaction", {"turn": turn + 1})
137
145
 
146
+ # ── Wrap-up injection at 80% of turns ──
147
+ if not wrap_up_injected and turn >= int(effective_max_turns * 0.8):
148
+ wrap_up_injected = True
149
+ remaining = effective_max_turns - turn
150
+ session.context.add_user_message(
151
+ f"[SYSTEM] You have {remaining} turns remaining. "
152
+ "Finish your current work now. If code is written and tested, "
153
+ "output DONE: <summary>. If code has errors, make one final fix attempt."
154
+ )
155
+ yield AgentEvent("wrap_up", {"turn": turn + 1, "remaining": remaining})
156
+
138
157
  yield AgentEvent("turn_start", {"turn": turn + 1})
139
158
 
140
159
  try:
141
160
  if enable_thinking:
142
- max_tokens = 4096 if is_offline else max(cfg.max_tokens, 16384)
161
+ max_tokens = 4096 if is_offline else max(effective_max_tokens, 16384)
143
162
  else:
144
- max_tokens = 2048 if is_offline else cfg.max_tokens
163
+ max_tokens = 2048 if is_offline else effective_max_tokens
145
164
 
146
165
  resp = self.backend.chat_completion(
147
166
  messages=session.context.messages,
148
- tools=active_tools,
167
+ tools=active_tools if active_tools else None,
149
168
  temperature=cfg.temperature,
150
169
  max_tokens=max_tokens,
151
170
  enable_thinking=enable_thinking,
152
171
  )
153
172
  except Exception as e:
154
- # Online error auto-fallback attempt
173
+ # Online error -> fallback to local
155
174
  if not is_offline and isinstance(self.backend, OpenAIBackend):
156
175
  yield AgentEvent("fallback", {"error": str(e), "message": "Falling back to local engine"})
157
176
  self.backend = LocalBackend(cfg)
158
177
  session.is_offline = True
159
178
  is_offline = True
160
- active_tools = self.prompt_builder.get_tools(is_offline=True)
179
+ active_tools = self.prompt_builder.get_tools(is_offline=True, complexity=complexity)
161
180
  try:
162
181
  resp = self.backend.chat_completion(
163
182
  messages=session.context.messages,
164
- tools=active_tools,
183
+ tools=active_tools if active_tools else None,
165
184
  temperature=cfg.temperature,
166
185
  max_tokens=2048,
167
186
  enable_thinking=enable_thinking,
@@ -178,7 +197,38 @@ class AgentOrchestrator:
178
197
 
179
198
  session.context.add_assistant_response(resp)
180
199
 
181
- # Extract tool calls
200
+ # ── DONE detection in assistant content ──
201
+ if resp.content and "DONE:" in resp.content.upper():
202
+ # Model said DONE — finish even if there are tool calls
203
+ tool_calls = session.executor.extract_tool_calls(resp)
204
+ if tool_calls:
205
+ # Execute final tool calls before finishing
206
+ results = session.executor.execute_tool_calls(tool_calls)
207
+ for res in results:
208
+ yield AgentEvent("tool_result", {
209
+ "turn": turn + 1,
210
+ "id": res.tool_call_id,
211
+ "name": res.name,
212
+ "content": res.content,
213
+ "duration_ms": res.duration_ms,
214
+ })
215
+ session.context.add_tool_result(res.tool_call_id, res.content)
216
+
217
+ yield AgentEvent("message", {"content": resp.content})
218
+ extract_and_save_memories(session.workdir, session.context.messages)
219
+
220
+ # Cache if single-turn
221
+ if is_cacheable and turn == 0:
222
+ try:
223
+ cache = get_cache()
224
+ cache.put(task, resp.content, reasoning=resp.reasoning)
225
+ except Exception:
226
+ pass
227
+
228
+ yield AgentEvent("done", {"summary": resp.content})
229
+ return
230
+
231
+ # Extract and execute tool calls
182
232
  tool_calls = session.executor.extract_tool_calls(resp)
183
233
 
184
234
  if tool_calls:
@@ -204,9 +254,8 @@ class AgentOrchestrator:
204
254
 
205
255
  continue
206
256
 
207
- # Assistant text response (no tool calls = cacheable)
257
+ # Text-only response = done
208
258
  if resp.content:
209
- # Cache this response — it completed in a single turn without tools
210
259
  if is_cacheable and turn == 0:
211
260
  try:
212
261
  cache = get_cache()
@@ -219,10 +268,10 @@ class AgentOrchestrator:
219
268
  yield AgentEvent("done", {"summary": resp.content})
220
269
  return
221
270
 
222
- # Exhausted tokens or empty response
271
+ # Empty response
223
272
  extract_and_save_memories(session.workdir, session.context.messages)
224
273
  yield AgentEvent("done", {"summary": "Task completed."})
225
274
  return
226
275
 
227
- yield AgentEvent("max_turns_reached", {"max_turns": cfg.max_turns})
276
+ yield AgentEvent("max_turns_reached", {"max_turns": effective_max_turns})
228
277
  extract_and_save_memories(session.workdir, session.context.messages)