alpiecode 10.0.3__tar.gz → 10.5.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 (62) hide show
  1. {alpiecode-10.0.3 → alpiecode-10.5.0}/PKG-INFO +1 -1
  2. {alpiecode-10.0.3 → alpiecode-10.5.0}/pyproject.toml +1 -1
  3. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/PKG-INFO +1 -1
  4. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/SOURCES.txt +2 -16
  5. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/__init__.py +1 -1
  6. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/agent.py +2 -1
  7. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/local_backend.py +13 -2
  8. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/openai_backend.py +10 -3
  9. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/cli.py +53 -1
  10. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/discovery.py +5 -0
  11. alpiecode-10.5.0/src/codeagent/dream_rsi.py +373 -0
  12. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/executor.py +8 -1
  13. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/local_model.py +42 -9
  14. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/orchestrator.py +79 -5
  15. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/prompt.py +6 -2
  16. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/system1.py +23 -6
  17. alpiecode-10.0.3/test/test_audit_fixes.py +0 -71
  18. alpiecode-10.0.3/test/test_benchmark_system1.py +0 -90
  19. alpiecode-10.0.3/test/test_cache_reads.py +0 -66
  20. alpiecode-10.0.3/test/test_chat_mode.py +0 -85
  21. alpiecode-10.0.3/test/test_claude_codex_capabilities.py +0 -111
  22. alpiecode-10.0.3/test/test_context_filter.py +0 -57
  23. alpiecode-10.0.3/test/test_math_tools.py +0 -214
  24. alpiecode-10.0.3/test/test_metrics.py +0 -46
  25. alpiecode-10.0.3/test/test_offline_mode.py +0 -314
  26. alpiecode-10.0.3/test/test_offline_opt.py +0 -59
  27. alpiecode-10.0.3/test/test_palindrome_check.py +0 -50
  28. alpiecode-10.0.3/test/test_phase1.py +0 -302
  29. alpiecode-10.0.3/test/test_phase2.py +0 -231
  30. alpiecode-10.0.3/test/test_phase3.py +0 -154
  31. alpiecode-10.0.3/test/test_system1.py +0 -84
  32. {alpiecode-10.0.3 → alpiecode-10.5.0}/README.md +0 -0
  33. {alpiecode-10.0.3 → alpiecode-10.5.0}/setup.cfg +0 -0
  34. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode/__init__.py +0 -0
  35. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  36. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
  37. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/requires.txt +0 -0
  38. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/top_level.txt +0 -0
  39. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/__init__.py +0 -0
  40. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/base.py +0 -0
  41. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/cache.py +0 -0
  42. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/client.py +0 -0
  43. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/compaction.py +0 -0
  44. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/config.py +0 -0
  45. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/context.py +0 -0
  46. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/doctor.py +0 -0
  47. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/extension/alpiecode.vsix +0 -0
  48. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/git_ops.py +0 -0
  49. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/github.py +0 -0
  50. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/guardian.py +0 -0
  51. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/guardrails.py +0 -0
  52. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/ipython_ext.py +0 -0
  53. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/media.py +0 -0
  54. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/memory.py +0 -0
  55. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/metrics.py +0 -0
  56. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/progress.py +0 -0
  57. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/rephraser.py +0 -0
  58. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/server.py +0 -0
  59. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/session.py +0 -0
  60. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/tools.py +0 -0
  61. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/updater.py +0 -0
  62. {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/vscode_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.0.3
3
+ Version: 10.5.0
4
4
  Summary: AlpieCode
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 = "10.0.3"
7
+ version = "10.5.0"
8
8
  description = "AlpieCode"
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: 10.0.3
3
+ Version: 10.5.0
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -17,6 +17,7 @@ src/codeagent/config.py
17
17
  src/codeagent/context.py
18
18
  src/codeagent/discovery.py
19
19
  src/codeagent/doctor.py
20
+ src/codeagent/dream_rsi.py
20
21
  src/codeagent/executor.py
21
22
  src/codeagent/git_ops.py
22
23
  src/codeagent/github.py
@@ -41,19 +42,4 @@ src/codeagent/backends/__init__.py
41
42
  src/codeagent/backends/base.py
42
43
  src/codeagent/backends/local_backend.py
43
44
  src/codeagent/backends/openai_backend.py
44
- src/codeagent/extension/alpiecode.vsix
45
- test/test_audit_fixes.py
46
- test/test_benchmark_system1.py
47
- test/test_cache_reads.py
48
- test/test_chat_mode.py
49
- test/test_claude_codex_capabilities.py
50
- test/test_context_filter.py
51
- test/test_math_tools.py
52
- test/test_metrics.py
53
- test/test_offline_mode.py
54
- test/test_offline_opt.py
55
- test/test_palindrome_check.py
56
- test/test_phase1.py
57
- test/test_phase2.py
58
- test/test_phase3.py
59
- test/test_system1.py
45
+ src/codeagent/extension/alpiecode.vsix
@@ -17,7 +17,7 @@ Supports:
17
17
  from pathlib import Path
18
18
  from typing import Optional, Any, Dict
19
19
 
20
- __version__ = "10.0.3"
20
+ __version__ = "10.5.0"
21
21
 
22
22
  def run(task: str, workdir: str = ".", reasoning_level: str = "thinking", **kwargs):
23
23
  """Run an autonomous coding task programmatically."""
@@ -340,7 +340,8 @@ def run_agent(
340
340
  console.print(f"📺 URL: {url}", style="cyan")
341
341
 
342
342
  elif event.type == "adaptive_mode" and verbose and HAS_RICH:
343
- console.print("⚡ [dim]Adaptive mode: simple task detected, skipping deep reasoning for speed[/dim]")
343
+ msg = event.data.get("message") or "Adaptive mode: reasoning trace adjusted."
344
+ console.print(f"⚡ [dim]{msg}[/dim]")
344
345
 
345
346
  elif event.type == "status":
346
347
  phase = event.data.get("phase")
@@ -99,8 +99,17 @@ class LocalBackend:
99
99
  fn = tc.get("function", {})
100
100
  name = fn.get("name", "unknown")
101
101
  args = fn.get("arguments", "{}")
102
+ # Do not duplicate full file content in context history
103
+ if name in ("write_file", "edit_file"):
104
+ try:
105
+ import json as _json
106
+ a_dict = _json.loads(args) if isinstance(args, str) else args
107
+ p = a_dict.get("path") or a_dict.get("filename") or ""
108
+ args = f'{"path": "{p}"}'
109
+ except Exception:
110
+ args = "{...}"
102
111
  tc_descriptions.append(f"[Called tool: {name}({args})]")
103
- if tc_descriptions:
112
+ if tc_descriptions and "<tool_call>" not in content:
104
113
  content = (content + "\n" + "\n".join(tc_descriptions)).strip()
105
114
  # Do NOT include tool_calls key — GGUF template will crash on it
106
115
 
@@ -247,7 +256,9 @@ class LocalBackend:
247
256
  inside_suppressed_think = True
248
257
  text = text.replace("<think>", "")
249
258
  if inside_suppressed_think:
250
- if "</think>" in text:
259
+ if "<tool_call>" in text or "DONE:" in text or "<function=" in text:
260
+ inside_suppressed_think = False
261
+ elif "</think>" in text:
251
262
  _, after = text.split("</think>", 1)
252
263
  inside_suppressed_think = False
253
264
  text = after
@@ -57,11 +57,15 @@ def _repair_and_parse_json_arguments(raw_args: str) -> dict:
57
57
  # Check unclosed quotes: count non-escaped double quotes
58
58
  quote_count = len(re.findall(r'(?<!\\)"', repaired))
59
59
  if quote_count % 2 != 0:
60
+ dangling_bs = len(repaired) - len(repaired.rstrip('\\'))
61
+ if dangling_bs % 2 != 0:
62
+ repaired = repaired[:-1]
60
63
  repaired += '"'
61
64
 
62
- # Balance unclosed brackets and braces
63
- open_curly = repaired.count("{") - repaired.count("}")
64
- open_square = repaired.count("[") - repaired.count("]")
65
+ # Balance unclosed brackets and braces outside string literals
66
+ stripped = re.sub(r'"(?:\\.|[^"\\])*"', '""', repaired)
67
+ open_curly = stripped.count("{") - stripped.count("}")
68
+ open_square = stripped.count("[") - stripped.count("]")
65
69
 
66
70
  if open_square > 0:
67
71
  repaired += "]" * open_square
@@ -91,6 +95,9 @@ def _repair_and_parse_json_arguments(raw_args: str) -> dict:
91
95
  extracted[k] = v_lit
92
96
 
93
97
  if extracted:
98
+ if "name" in extracted and "arguments" not in extracted:
99
+ args = {k: v for k, v in extracted.items() if k not in ("name", "id", "type")}
100
+ return {"name": extracted["name"], "arguments": args}
94
101
  return extracted
95
102
 
96
103
  logger.warning("Failed to parse tool call arguments: %s", raw_args[:200])
@@ -29,7 +29,7 @@ def _show_banner():
29
29
 
30
30
 
31
31
  def _normalize_args():
32
- known_commands = {"init", "serve", "run", "chat", "plan", "diff", "undo", "doctor", "explain", "version", "-h", "--help", "-v", "--version"}
32
+ known_commands = {"init", "serve", "run", "chat", "plan", "diff", "undo", "doctor", "explain", "dream", "version", "-h", "--help", "-v", "--version"}
33
33
  subcommand = None
34
34
 
35
35
  # Check if a subcommand is present
@@ -131,6 +131,12 @@ def main():
131
131
  explain_p = sub.add_parser("explain", help="Explain a file, function, or codebase concept", parents=[common])
132
132
  explain_p.add_argument("target", help="File path or concept to explain")
133
133
 
134
+ # ?? dream ??
135
+ dream_p = sub.add_parser('dream', help='Dream-RSI: offline recursive self-improvement simulation')
136
+ dream_p.add_argument('--optimize', action='store_true', help='Run offline dreaming simulation to synthesize optimal search policy')
137
+ dream_p.add_argument('--stats', action='store_true', help='Show trajectory replay metrics and active policy status')
138
+ dream_p.add_argument('--clear', action='store_true', help='Clear historical trajectory replay pool')
139
+
134
140
  args = parser.parse_args()
135
141
 
136
142
  if not args.command:
@@ -145,6 +151,52 @@ def main():
145
151
  interactive_init()
146
152
  return
147
153
 
154
+ if args.command == 'dream':
155
+ import time
156
+ from .dream_rsi import TrajectoryStore, DreamOptimizer, load_active_dream_profile
157
+ store = TrajectoryStore()
158
+ optimizer = DreamOptimizer(store)
159
+ if getattr(args, 'clear', False):
160
+ count = store.clear_trajectories()
161
+ print(f'?? Cleared {count} historical trajectories from replay pool.')
162
+ return
163
+ if getattr(args, 'optimize', False):
164
+ print('?? Running Dream-RSI offline recursive self-improvement simulation...')
165
+ profile, res = optimizer.optimize()
166
+ if res.get('status') == 'no_trajectories':
167
+ print('?? No historical trajectories recorded yet. Run tasks with alpiecode run to populate the replay buffer.')
168
+ return
169
+ metrics = res.get('metrics', {})
170
+ print('\n? Dream-RSI Optimization Complete!')
171
+ print(f' ?? Trajectories Replayed: {metrics.get("total_trajectories", 0)}')
172
+ print(f' ?? Total Turns Saved: {metrics.get("total_turns_saved", 0)}')
173
+ print(f' ?? Loops Prevented: {metrics.get("total_loops_prevented", 0)}')
174
+ print(f' ?? Stalls Averted: {metrics.get("total_stalls_averted", 0)}')
175
+ print(f' ? Simulation Fitness: {metrics.get("average_score", 0.0)}')
176
+ print('\n?? Active Policy Updated:')
177
+ print(f' ? Syntax Loop Threshold: {profile.syntax_loop_threshold}')
178
+ print(f' ? General Loop Threshold: {profile.general_loop_threshold}')
179
+ print(f' ? Stall Trigger: {profile.stall_threshold}')
180
+ print(f' ? Single-File Enforcement: {profile.single_file_enforcement}')
181
+ return
182
+ trajectories = store.list_trajectories()
183
+ profile = load_active_dream_profile()
184
+ successes = sum(1 for t in trajectories if t.outcome == 'success')
185
+ failures = sum(1 for t in trajectories if t.outcome != 'success')
186
+ print('\n?? Dream-RSI Status & Replay Pool')
187
+ print(f' ?? Replay Buffer: {len(trajectories)} trajectories stored')
188
+ print(f' ? Successful Runs: {successes}')
189
+ print(f' ?? Failed/Stalled Runs: {failures}')
190
+ print('\n?? Active Policy Profile:')
191
+ print(f' ? Syntax Loop Threshold: {profile.syntax_loop_threshold}')
192
+ print(f' ? General Loop Threshold: {profile.general_loop_threshold}')
193
+ print(f' ? Stall Trigger: {profile.stall_threshold}')
194
+ print(f' ? Single-File Enforcement: {profile.single_file_enforcement}')
195
+ opt_time = time.ctime(profile.last_optimized_at) if profile.last_optimized_at else 'Never'
196
+ print(f' ? Last Optimized: {opt_time}')
197
+ print('\n?? Tip: Run alpiecode dream --optimize to run offline self-improvement.')
198
+ return
199
+
148
200
  if args.command == "doctor":
149
201
  from .doctor import run_doctor
150
202
  sys.exit(run_doctor())
@@ -565,6 +565,11 @@ def gather_relevant_context(
565
565
  return None
566
566
 
567
567
  task_lower = task.lower().strip()
568
+
569
+ # Skip pre-reading repo files for standalone algorithms or scripts (e.g. palindrome, calculator)
570
+ standalone_kws = ["palindrome", "fibonacci", "quicksort", "bubblesort", "factorial", "hello world", "calculator", "reverse string", "reverse_string"]
571
+ if any(kw in task_lower for kw in standalone_kws):
572
+ return None
568
573
  sections: List[str] = []
569
574
  chars_used = 0
570
575
 
@@ -0,0 +1,373 @@
1
+ """
2
+ Dream-RSI: Recursive Self-Improvement through Evolving Worlds for AlpieCode.
3
+ Reference: DeepMind / UMD / UVA (arXiv:2609.14858).
4
+
5
+ Decouples agent exploration/intervention policies from LLM weights:
6
+ 1. TrajectoryRecorder: Logs complete discovery trees to ~/.alpiecode/trajectories/
7
+ 2. ReplaySimulator: Fast offline world model evaluating counterfactual search policies
8
+ 3. DreamOptimizer: Simulates candidate policies over replay trees at near-zero compute cost
9
+ 4. DreamStrategyProfile: Distilled high-performance policy used by System 1 (< 1ms runtime)
10
+ """
11
+
12
+ import hashlib
13
+ import json
14
+ import logging
15
+ import os
16
+ import time
17
+ from dataclasses import asdict, dataclass, field
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional, Tuple
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ TRAJECTORY_DIR = Path.home() / ".alpiecode" / "trajectories"
24
+ POLICY_PATH = Path.home() / ".alpiecode" / "dream_policy.json"
25
+
26
+
27
+ # ?? 1. Trajectory Data Models ??????????????????????????????????????????
28
+
29
+ @dataclass
30
+ class TurnSnapshot:
31
+ turn: int
32
+ tool_calls: List[Dict[str, Any]] = field(default_factory=list)
33
+ tool_results: List[Dict[str, Any]] = field(default_factory=list)
34
+ error_types: List[str] = field(default_factory=list)
35
+ had_error: bool = False
36
+ system1_action: Optional[str] = None
37
+ system1_directive: Optional[str] = None
38
+ duration_ms: float = 0.0
39
+ tokens_estimated: int = 0
40
+
41
+
42
+ @dataclass
43
+ class TrajectoryRecord:
44
+ session_id: str
45
+ task: str
46
+ complexity: str = "low"
47
+ is_offline: bool = False
48
+ created_at: float = field(default_factory=time.time)
49
+ turns: List[TurnSnapshot] = field(default_factory=list)
50
+ outcome: str = "in_progress" # "success", "failed", "stalled", "cancelled"
51
+ total_duration_ms: float = 0.0
52
+ total_turns: int = 0
53
+ total_tokens: int = 0
54
+ files_touched: List[str] = field(default_factory=list)
55
+ error_summary: List[str] = field(default_factory=list)
56
+
57
+ def to_dict(self) -> Dict[str, Any]:
58
+ return asdict(self)
59
+
60
+ @classmethod
61
+ def from_dict(cls, data: Dict[str, Any]) -> "TrajectoryRecord":
62
+ turns_data = data.pop("turns", [])
63
+ turns = [TurnSnapshot(**t) for t in turns_data]
64
+ return cls(turns=turns, **data)
65
+
66
+
67
+ # ?? 2. Trajectory Persistence Store ?????????????????????????????????????
68
+
69
+ class TrajectoryStore:
70
+ """Local persistent repository of discovery trajectories."""
71
+
72
+ def __init__(self, storage_dir: Optional[Path] = None):
73
+ self.storage_dir = storage_dir or TRAJECTORY_DIR
74
+ self.storage_dir.mkdir(parents=True, exist_ok=True)
75
+
76
+ def save_trajectory(self, record: TrajectoryRecord) -> Path:
77
+ """Save a completed trajectory atomically to disk."""
78
+ self.storage_dir.mkdir(parents=True, exist_ok=True)
79
+ file_path = self.storage_dir / f"{record.session_id}.json"
80
+ temp_path = self.storage_dir / f"{record.session_id}.json.tmp"
81
+
82
+ try:
83
+ with open(temp_path, "w", encoding="utf-8") as f:
84
+ json.dump(record.to_dict(), f, indent=2)
85
+ os.replace(temp_path, file_path)
86
+ except Exception as e:
87
+ if temp_path.exists():
88
+ temp_path.unlink()
89
+ logger.warning("Failed to save trajectory %s: %s", record.session_id, e)
90
+
91
+ return file_path
92
+
93
+ def load_trajectory(self, session_id: str) -> Optional[TrajectoryRecord]:
94
+ """Load a specific trajectory record by session ID."""
95
+ path = self.storage_dir / f"{session_id}.json"
96
+ if not path.exists():
97
+ return None
98
+ try:
99
+ with open(path, "r", encoding="utf-8") as f:
100
+ data = json.load(f)
101
+ return TrajectoryRecord.from_dict(data)
102
+ except Exception as e:
103
+ logger.warning("Failed to load trajectory %s: %s", session_id, e)
104
+ return None
105
+
106
+ def list_trajectories(self, limit: int = 100) -> List[TrajectoryRecord]:
107
+ """List recent discovery trajectories, sorted newest first."""
108
+ if not self.storage_dir.exists():
109
+ return []
110
+
111
+ records = []
112
+ files = sorted(self.storage_dir.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True)
113
+ for p in files[:limit]:
114
+ try:
115
+ with open(p, "r", encoding="utf-8") as f:
116
+ data = json.load(f)
117
+ records.append(TrajectoryRecord.from_dict(data))
118
+ except Exception:
119
+ continue
120
+ return records
121
+
122
+ def clear_trajectories(self) -> int:
123
+ """Remove all historical trajectories. Returns count removed."""
124
+ if not self.storage_dir.exists():
125
+ return 0
126
+ count = 0
127
+ for p in self.storage_dir.glob("*.json"):
128
+ try:
129
+ p.unlink()
130
+ count += 1
131
+ except Exception:
132
+ pass
133
+ return count
134
+
135
+
136
+ # ?? 3. Distilled Strategy Profile ??????????????????????????????????????
137
+
138
+ @dataclass
139
+ class DreamStrategyProfile:
140
+ """
141
+ Distilled exploration and intervention policy synthesized by Dream-RSI.
142
+ Directly consumed by System 1 and Orchestrator in < 1ms.
143
+ """
144
+ syntax_loop_threshold: int = 1 # Intervene on syntax errors at turn 1
145
+ general_loop_threshold: int = 2 # Intervene on general repeated errors at turn 2
146
+ stall_threshold: int = 3 # Consecutive turns without file progress before intervention
147
+ single_file_enforcement: bool = True # For multi-file offline tasks, write 1 file per turn
148
+ prefer_surgical_edits: bool = True # Favor edit_file over full write_file on error recovery
149
+ nudge_missing_tools: bool = True # Nudge when model speaks text without tools on early turns
150
+ max_offline_tokens: int = 2048 # Maximum token headroom ceiling for offline generation
151
+ version: int = 1
152
+ last_optimized_at: float = field(default_factory=time.time)
153
+ trajectories_evaluated: int = 0
154
+ estimated_turns_saved: int = 0
155
+ loop_prevention_score: float = 0.0
156
+
157
+ def to_dict(self) -> Dict[str, Any]:
158
+ return asdict(self)
159
+
160
+ @classmethod
161
+ def from_dict(cls, data: Dict[str, Any]) -> "DreamStrategyProfile":
162
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
163
+
164
+
165
+ def load_active_dream_profile(path: Optional[Path] = None) -> DreamStrategyProfile:
166
+ """Load the current active dream policy profile."""
167
+ target = path or POLICY_PATH
168
+ if not target.exists():
169
+ return DreamStrategyProfile()
170
+ try:
171
+ with open(target, "r", encoding="utf-8") as f:
172
+ data = json.load(f)
173
+ return DreamStrategyProfile.from_dict(data)
174
+ except Exception:
175
+ return DreamStrategyProfile()
176
+
177
+
178
+ def save_active_dream_profile(profile: DreamStrategyProfile, path: Optional[Path] = None) -> None:
179
+ """Save a synthesized dream policy profile."""
180
+ target = path or POLICY_PATH
181
+ target.parent.mkdir(parents=True, exist_ok=True)
182
+ tmp = target.with_suffix(".tmp")
183
+ try:
184
+ with open(tmp, "w", encoding="utf-8") as f:
185
+ json.dump(profile.to_dict(), f, indent=2)
186
+ os.replace(tmp, target)
187
+ except Exception as e:
188
+ if tmp.exists():
189
+ tmp.unlink()
190
+ logger.warning("Failed to save dream policy: %s", e)
191
+
192
+
193
+ # ?? 4. Replay Simulator (The Dream Engine) ?????????????????????????????
194
+
195
+ class ReplaySimulator:
196
+ """
197
+ Offline simulator that replays recorded discovery trajectories under
198
+ counterfactual policy rules at near-zero execution cost.
199
+ """
200
+
201
+ def evaluate_trajectory(
202
+ self,
203
+ trajectory: TrajectoryRecord,
204
+ profile: DreamStrategyProfile,
205
+ ) -> Dict[str, Any]:
206
+ """
207
+ Simulate how a given policy profile would have handled this trajectory.
208
+ Computes counterfactual turns saved, loop prevention, and recovery efficiency.
209
+ """
210
+ turns_saved = 0
211
+ loops_prevented = 0
212
+ stalls_averted = 0
213
+ early_nudges = 0
214
+
215
+ action_counts: Dict[str, int] = {}
216
+
217
+ for turn in trajectory.turns:
218
+ for tc in turn.tool_calls:
219
+ name = tc.get("name", "")
220
+ path = tc.get("arguments", {}).get("path") or tc.get("arguments", {}).get("command") or ""
221
+ sig = f"{name}:{path}"
222
+ action_counts[sig] = action_counts.get(sig, 0) + 1
223
+
224
+ # Counterfactual Loop Evaluation
225
+ has_syntax = "syntax" in turn.error_types
226
+ threshold = profile.syntax_loop_threshold if has_syntax else profile.general_loop_threshold
227
+ if action_counts[sig] >= threshold and turn.had_error:
228
+ loops_prevented += 1
229
+ # If this loop occurred at a higher threshold historically, we save turns
230
+ if action_counts[sig] > threshold:
231
+ turns_saved += (action_counts[sig] - threshold)
232
+
233
+ # Counterfactual Multi-File Token Budget Check
234
+ if profile.single_file_enforcement and trajectory.is_offline:
235
+ write_calls = [tc for tc in turn.tool_calls if tc.get("name") == "write_file"]
236
+ if len(write_calls) > 1:
237
+ # In historical run, bundling multiple writes caused truncation/error
238
+ turns_saved += 1
239
+ stalls_averted += 1
240
+
241
+ # Counterfactual Conversational Nudge Check
242
+ if profile.nudge_missing_tools and not turn.tool_calls and turn.turn < 5:
243
+ if trajectory.outcome != "success":
244
+ early_nudges += 1
245
+ turns_saved += 1
246
+
247
+ # Net effectiveness score
248
+ base_score = 100.0 if trajectory.outcome == "success" else 20.0
249
+ policy_gain = (turns_saved * 15.0) + (loops_prevented * 20.0) + (stalls_averted * 25.0)
250
+ final_score = base_score + policy_gain
251
+
252
+ return {
253
+ "session_id": trajectory.session_id,
254
+ "turns_saved": turns_saved,
255
+ "loops_prevented": loops_prevented,
256
+ "stalls_averted": stalls_averted,
257
+ "score": final_score,
258
+ }
259
+
260
+ def evaluate_policy(
261
+ self,
262
+ profile: DreamStrategyProfile,
263
+ trajectories: List[TrajectoryRecord],
264
+ ) -> Dict[str, Any]:
265
+ """Evaluate candidate profile across the entire replay buffer."""
266
+ if not trajectories:
267
+ return {
268
+ "total_trajectories": 0,
269
+ "total_turns_saved": 0,
270
+ "total_loops_prevented": 0,
271
+ "average_score": 0.0,
272
+ }
273
+
274
+ total_turns_saved = 0
275
+ total_loops_prevented = 0
276
+ total_stalls_averted = 0
277
+ total_score = 0.0
278
+
279
+ for traj in trajectories:
280
+ res = self.evaluate_trajectory(traj, profile)
281
+ total_turns_saved += res["turns_saved"]
282
+ total_loops_prevented += res["loops_prevented"]
283
+ total_stalls_averted += res["stalls_averted"]
284
+ total_score += res["score"]
285
+
286
+ count = len(trajectories)
287
+ return {
288
+ "total_trajectories": count,
289
+ "total_turns_saved": total_turns_saved,
290
+ "total_loops_prevented": total_loops_prevented,
291
+ "total_stalls_averted": total_stalls_averted,
292
+ "average_score": round(total_score / count, 2),
293
+ }
294
+
295
+
296
+ # ?? 5. Dream Optimizer (Offline Self-Improvement Loop) ??????????????????
297
+
298
+ class DreamOptimizer:
299
+ """
300
+ Recursive Self-Improvement (RSI) optimizer that searches parameter space
301
+ across recorded trajectory trees to find the optimal search harness.
302
+ """
303
+
304
+ def __init__(self, store: Optional[TrajectoryStore] = None):
305
+ self.store = store or TrajectoryStore()
306
+ self.simulator = ReplaySimulator()
307
+
308
+ def optimize(
309
+ self,
310
+ trajectories: Optional[List[TrajectoryRecord]] = None,
311
+ ) -> Tuple[DreamStrategyProfile, Dict[str, Any]]:
312
+ """
313
+ Run the dream optimization cycle over available discovery trees.
314
+ Returns the winning profile and its performance metrics.
315
+ """
316
+ traj_pool = trajectories if trajectories is not None else self.store.list_trajectories(limit=250)
317
+
318
+ best_profile = DreamStrategyProfile(
319
+ trajectories_evaluated=len(traj_pool),
320
+ last_optimized_at=time.time(),
321
+ )
322
+
323
+ if not traj_pool:
324
+ # No replay data yet; return baseline defaults
325
+ return best_profile, {"status": "no_trajectories", "message": "No historical trajectories to replay yet."}
326
+
327
+ best_score = -1.0
328
+ best_stats = {}
329
+
330
+ # Exploration Policy Search Grid (Dream Simulation Space)
331
+ syntax_thresholds = [1, 2]
332
+ general_thresholds = [1, 2, 3]
333
+ stall_thresholds = [2, 3, 4]
334
+ single_file_options = [True, False]
335
+ nudge_options = [True, False]
336
+
337
+ for st in syntax_thresholds:
338
+ for gt in general_thresholds:
339
+ for stall in stall_thresholds:
340
+ for sfo in single_file_options:
341
+ for no in nudge_options:
342
+ candidate = DreamStrategyProfile(
343
+ syntax_loop_threshold=st,
344
+ general_loop_threshold=gt,
345
+ stall_threshold=stall,
346
+ single_file_enforcement=sfo,
347
+ nudge_missing_tools=no,
348
+ prefer_surgical_edits=True,
349
+ max_offline_tokens=2048,
350
+ trajectories_evaluated=len(traj_pool),
351
+ last_optimized_at=time.time(),
352
+ )
353
+
354
+ metrics = self.simulator.evaluate_policy(candidate, traj_pool)
355
+ score = metrics["average_score"]
356
+
357
+ if score > best_score:
358
+ best_score = score
359
+ candidate.estimated_turns_saved = metrics["total_turns_saved"]
360
+ candidate.loop_prevention_score = round(
361
+ metrics["total_loops_prevented"] / max(1, len(traj_pool)), 2
362
+ )
363
+ best_profile = candidate
364
+ best_stats = metrics
365
+
366
+ # Persist winning policy
367
+ save_active_dream_profile(best_profile)
368
+
369
+ return best_profile, {
370
+ "status": "optimized",
371
+ "winning_profile": best_profile.to_dict(),
372
+ "metrics": best_stats,
373
+ }
@@ -49,9 +49,16 @@ def parse_text_tool_calls(text: str) -> List[dict]:
49
49
  try:
50
50
  data = _repair_and_parse_json_arguments(raw)
51
51
  if isinstance(data, dict) and "name" in data:
52
+ args = data.get("arguments")
53
+ if not isinstance(args, dict) or not args:
54
+ fallback_args = {k: v for k, v in data.items() if k not in ("name", "id", "type", "_raw_arguments", "_parse_error")}
55
+ if fallback_args:
56
+ args = fallback_args
57
+ elif not isinstance(args, dict):
58
+ args = {}
52
59
  tool_calls.append({
53
60
  "name": data["name"],
54
- "arguments": data.get("arguments", {})
61
+ "arguments": args
55
62
  })
56
63
  except Exception:
57
64
  pass