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.
- {alpiecode-10.0.3 → alpiecode-10.5.0}/PKG-INFO +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.0}/pyproject.toml +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/SOURCES.txt +2 -16
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/__init__.py +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/agent.py +2 -1
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/local_backend.py +13 -2
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/openai_backend.py +10 -3
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/cli.py +53 -1
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/discovery.py +5 -0
- alpiecode-10.5.0/src/codeagent/dream_rsi.py +373 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/executor.py +8 -1
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/local_model.py +42 -9
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/orchestrator.py +79 -5
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/prompt.py +6 -2
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/system1.py +23 -6
- alpiecode-10.0.3/test/test_audit_fixes.py +0 -71
- alpiecode-10.0.3/test/test_benchmark_system1.py +0 -90
- alpiecode-10.0.3/test/test_cache_reads.py +0 -66
- alpiecode-10.0.3/test/test_chat_mode.py +0 -85
- alpiecode-10.0.3/test/test_claude_codex_capabilities.py +0 -111
- alpiecode-10.0.3/test/test_context_filter.py +0 -57
- alpiecode-10.0.3/test/test_math_tools.py +0 -214
- alpiecode-10.0.3/test/test_metrics.py +0 -46
- alpiecode-10.0.3/test/test_offline_mode.py +0 -314
- alpiecode-10.0.3/test/test_offline_opt.py +0 -59
- alpiecode-10.0.3/test/test_palindrome_check.py +0 -50
- alpiecode-10.0.3/test/test_phase1.py +0 -302
- alpiecode-10.0.3/test/test_phase2.py +0 -231
- alpiecode-10.0.3/test/test_phase3.py +0 -154
- alpiecode-10.0.3/test/test_system1.py +0 -84
- {alpiecode-10.0.3 → alpiecode-10.5.0}/README.md +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/setup.cfg +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode/__init__.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/backends/base.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/cache.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/client.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/compaction.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/config.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/context.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/doctor.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/git_ops.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/github.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/guardian.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/guardrails.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/ipython_ext.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/media.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/memory.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/metrics.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/progress.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/rephraser.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/server.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/session.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/tools.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/updater.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.0}/src/codeagent/vscode_installer.py +0 -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
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
64
|
-
|
|
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":
|
|
61
|
+
"arguments": args
|
|
55
62
|
})
|
|
56
63
|
except Exception:
|
|
57
64
|
pass
|