copex 0.8.4__py3-none-any.whl
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.
- copex/__init__.py +69 -0
- copex/checkpoint.py +445 -0
- copex/cli.py +1106 -0
- copex/client.py +725 -0
- copex/config.py +311 -0
- copex/mcp.py +561 -0
- copex/metrics.py +383 -0
- copex/models.py +50 -0
- copex/persistence.py +324 -0
- copex/plan.py +358 -0
- copex/ralph.py +247 -0
- copex/tools.py +404 -0
- copex/ui.py +971 -0
- copex-0.8.4.dist-info/METADATA +511 -0
- copex-0.8.4.dist-info/RECORD +18 -0
- copex-0.8.4.dist-info/WHEEL +4 -0
- copex-0.8.4.dist-info/entry_points.txt +2 -0
- copex-0.8.4.dist-info/licenses/LICENSE +21 -0
copex/__init__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Copex - Copilot Extended: A resilient wrapper for GitHub Copilot SDK."""
|
|
2
|
+
|
|
3
|
+
# Checkpointing
|
|
4
|
+
from copex.checkpoint import Checkpoint, CheckpointedRalph, CheckpointStore
|
|
5
|
+
from copex.client import Copex
|
|
6
|
+
from copex.config import CopexConfig, find_copilot_cli
|
|
7
|
+
|
|
8
|
+
# MCP integration
|
|
9
|
+
from copex.mcp import MCPClient, MCPManager, MCPServerConfig, MCPTool, load_mcp_config
|
|
10
|
+
|
|
11
|
+
# Metrics
|
|
12
|
+
from copex.metrics import MetricsCollector, RequestMetrics, SessionMetrics, get_collector
|
|
13
|
+
from copex.models import Model, ReasoningEffort
|
|
14
|
+
|
|
15
|
+
# Persistence
|
|
16
|
+
from copex.persistence import Message, PersistentSession, SessionData, SessionStore
|
|
17
|
+
|
|
18
|
+
# Plan mode
|
|
19
|
+
from copex.plan import Plan, PlanExecutor, PlanStep, StepStatus
|
|
20
|
+
|
|
21
|
+
# Ralph Wiggum loops
|
|
22
|
+
from copex.ralph import RalphConfig, RalphState, RalphWiggum, ralph_loop
|
|
23
|
+
|
|
24
|
+
# Parallel tools
|
|
25
|
+
from copex.tools import ParallelToolExecutor, ToolRegistry, ToolResult
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Core
|
|
29
|
+
"Copex",
|
|
30
|
+
"CopexConfig",
|
|
31
|
+
"Model",
|
|
32
|
+
"ReasoningEffort",
|
|
33
|
+
"find_copilot_cli",
|
|
34
|
+
# Ralph
|
|
35
|
+
"RalphWiggum",
|
|
36
|
+
"RalphConfig",
|
|
37
|
+
"RalphState",
|
|
38
|
+
"ralph_loop",
|
|
39
|
+
# Plan
|
|
40
|
+
"Plan",
|
|
41
|
+
"PlanStep",
|
|
42
|
+
"PlanExecutor",
|
|
43
|
+
"StepStatus",
|
|
44
|
+
# Persistence
|
|
45
|
+
"SessionStore",
|
|
46
|
+
"PersistentSession",
|
|
47
|
+
"Message",
|
|
48
|
+
"SessionData",
|
|
49
|
+
# Checkpointing
|
|
50
|
+
"CheckpointStore",
|
|
51
|
+
"Checkpoint",
|
|
52
|
+
"CheckpointedRalph",
|
|
53
|
+
# Metrics
|
|
54
|
+
"MetricsCollector",
|
|
55
|
+
"RequestMetrics",
|
|
56
|
+
"SessionMetrics",
|
|
57
|
+
"get_collector",
|
|
58
|
+
# Tools
|
|
59
|
+
"ToolRegistry",
|
|
60
|
+
"ParallelToolExecutor",
|
|
61
|
+
"ToolResult",
|
|
62
|
+
# MCP
|
|
63
|
+
"MCPClient",
|
|
64
|
+
"MCPManager",
|
|
65
|
+
"MCPServerConfig",
|
|
66
|
+
"MCPTool",
|
|
67
|
+
"load_mcp_config",
|
|
68
|
+
]
|
|
69
|
+
__version__ = "0.4.2"
|
copex/checkpoint.py
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Checkpointing - Save and restore Ralph loop state for crash recovery.
|
|
3
|
+
|
|
4
|
+
Enables:
|
|
5
|
+
- Resuming interrupted Ralph loops
|
|
6
|
+
- Crash recovery
|
|
7
|
+
- Inspection of loop progress
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from dataclasses import asdict, dataclass, field
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Checkpoint:
|
|
24
|
+
"""A checkpoint of Ralph loop state."""
|
|
25
|
+
|
|
26
|
+
# Identity
|
|
27
|
+
checkpoint_id: str
|
|
28
|
+
loop_id: str
|
|
29
|
+
|
|
30
|
+
# Loop state
|
|
31
|
+
prompt: str
|
|
32
|
+
iteration: int
|
|
33
|
+
max_iterations: int | None
|
|
34
|
+
completion_promise: str | None
|
|
35
|
+
|
|
36
|
+
# Timestamps
|
|
37
|
+
created_at: str
|
|
38
|
+
updated_at: str
|
|
39
|
+
started_at: str
|
|
40
|
+
|
|
41
|
+
# History
|
|
42
|
+
history: list[str] = field(default_factory=list)
|
|
43
|
+
|
|
44
|
+
# Status
|
|
45
|
+
completed: bool = False
|
|
46
|
+
completion_reason: str | None = None
|
|
47
|
+
|
|
48
|
+
# Metadata
|
|
49
|
+
model: str = "gpt-5.2-codex"
|
|
50
|
+
reasoning_effort: str = "xhigh"
|
|
51
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> dict[str, Any]:
|
|
54
|
+
"""Convert to dictionary."""
|
|
55
|
+
return asdict(self)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_dict(cls, data: dict[str, Any]) -> "Checkpoint":
|
|
59
|
+
"""Create from dictionary."""
|
|
60
|
+
return cls(**data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CheckpointStore:
|
|
64
|
+
"""
|
|
65
|
+
Persistent storage for Ralph loop checkpoints.
|
|
66
|
+
|
|
67
|
+
Usage:
|
|
68
|
+
store = CheckpointStore()
|
|
69
|
+
|
|
70
|
+
# Create checkpoint
|
|
71
|
+
cp = store.create("my-loop", prompt, iteration, ...)
|
|
72
|
+
|
|
73
|
+
# Update on each iteration
|
|
74
|
+
store.update(cp.checkpoint_id, iteration=5, history=[...])
|
|
75
|
+
|
|
76
|
+
# Resume after crash
|
|
77
|
+
cp = store.get_latest("my-loop")
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, base_dir: Path | str | None = None):
|
|
81
|
+
"""
|
|
82
|
+
Initialize checkpoint store.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
base_dir: Directory for checkpoint files. Defaults to ~/.copex/checkpoints
|
|
86
|
+
"""
|
|
87
|
+
if base_dir is None:
|
|
88
|
+
base_dir = Path.home() / ".copex" / "checkpoints"
|
|
89
|
+
self.base_dir = Path(base_dir)
|
|
90
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
|
|
92
|
+
def _checkpoint_path(self, checkpoint_id: str) -> Path:
|
|
93
|
+
"""Get path for a checkpoint file."""
|
|
94
|
+
safe_id = "".join(c if c.isalnum() or c in "-_" else "_" for c in checkpoint_id)
|
|
95
|
+
return self.base_dir / f"{safe_id}.json"
|
|
96
|
+
|
|
97
|
+
def create(
|
|
98
|
+
self,
|
|
99
|
+
loop_id: str,
|
|
100
|
+
prompt: str,
|
|
101
|
+
max_iterations: int | None = None,
|
|
102
|
+
completion_promise: str | None = None,
|
|
103
|
+
model: str = "gpt-5.2-codex",
|
|
104
|
+
reasoning_effort: str = "xhigh",
|
|
105
|
+
metadata: dict[str, Any] | None = None,
|
|
106
|
+
) -> Checkpoint:
|
|
107
|
+
"""
|
|
108
|
+
Create a new checkpoint.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
loop_id: Identifier for this loop (e.g., project name)
|
|
112
|
+
prompt: The loop prompt
|
|
113
|
+
max_iterations: Maximum iterations
|
|
114
|
+
completion_promise: Completion promise text
|
|
115
|
+
model: Model being used
|
|
116
|
+
reasoning_effort: Reasoning effort level
|
|
117
|
+
metadata: Additional metadata
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
New Checkpoint object
|
|
121
|
+
"""
|
|
122
|
+
now = datetime.now().isoformat()
|
|
123
|
+
checkpoint_id = f"{loop_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
124
|
+
|
|
125
|
+
checkpoint = Checkpoint(
|
|
126
|
+
checkpoint_id=checkpoint_id,
|
|
127
|
+
loop_id=loop_id,
|
|
128
|
+
prompt=prompt,
|
|
129
|
+
iteration=0,
|
|
130
|
+
max_iterations=max_iterations,
|
|
131
|
+
completion_promise=completion_promise,
|
|
132
|
+
created_at=now,
|
|
133
|
+
updated_at=now,
|
|
134
|
+
started_at=now,
|
|
135
|
+
model=model,
|
|
136
|
+
reasoning_effort=reasoning_effort,
|
|
137
|
+
metadata=metadata or {},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
self._save(checkpoint)
|
|
141
|
+
return checkpoint
|
|
142
|
+
|
|
143
|
+
def _save(self, checkpoint: Checkpoint) -> None:
|
|
144
|
+
"""Save checkpoint to disk."""
|
|
145
|
+
path = self._checkpoint_path(checkpoint.checkpoint_id)
|
|
146
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
147
|
+
json.dump(checkpoint.to_dict(), f, indent=2, ensure_ascii=False)
|
|
148
|
+
|
|
149
|
+
def update(
|
|
150
|
+
self,
|
|
151
|
+
checkpoint_id: str,
|
|
152
|
+
iteration: int | None = None,
|
|
153
|
+
history: list[str] | None = None,
|
|
154
|
+
completed: bool | None = None,
|
|
155
|
+
completion_reason: str | None = None,
|
|
156
|
+
metadata: dict[str, Any] | None = None,
|
|
157
|
+
) -> Checkpoint | None:
|
|
158
|
+
"""
|
|
159
|
+
Update an existing checkpoint.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Updated checkpoint, or None if not found
|
|
163
|
+
"""
|
|
164
|
+
checkpoint = self.load(checkpoint_id)
|
|
165
|
+
if not checkpoint:
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
checkpoint.updated_at = datetime.now().isoformat()
|
|
169
|
+
|
|
170
|
+
if iteration is not None:
|
|
171
|
+
checkpoint.iteration = iteration
|
|
172
|
+
if history is not None:
|
|
173
|
+
checkpoint.history = history
|
|
174
|
+
if completed is not None:
|
|
175
|
+
checkpoint.completed = completed
|
|
176
|
+
if completion_reason is not None:
|
|
177
|
+
checkpoint.completion_reason = completion_reason
|
|
178
|
+
if metadata is not None:
|
|
179
|
+
checkpoint.metadata.update(metadata)
|
|
180
|
+
|
|
181
|
+
self._save(checkpoint)
|
|
182
|
+
return checkpoint
|
|
183
|
+
|
|
184
|
+
def load(self, checkpoint_id: str) -> Checkpoint | None:
|
|
185
|
+
"""Load a checkpoint by ID."""
|
|
186
|
+
path = self._checkpoint_path(checkpoint_id)
|
|
187
|
+
if not path.exists():
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
191
|
+
data = json.load(f)
|
|
192
|
+
|
|
193
|
+
return Checkpoint.from_dict(data)
|
|
194
|
+
|
|
195
|
+
def get_latest(self, loop_id: str) -> Checkpoint | None:
|
|
196
|
+
"""
|
|
197
|
+
Get the latest checkpoint for a loop.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
loop_id: The loop identifier
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Latest checkpoint, or None if none found
|
|
204
|
+
"""
|
|
205
|
+
checkpoints = self.list_checkpoints(loop_id=loop_id)
|
|
206
|
+
if not checkpoints:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
# Already sorted by updated_at descending
|
|
210
|
+
latest_id = checkpoints[0]["checkpoint_id"]
|
|
211
|
+
return self.load(latest_id)
|
|
212
|
+
|
|
213
|
+
def get_incomplete(self, loop_id: str | None = None) -> list[Checkpoint]:
|
|
214
|
+
"""
|
|
215
|
+
Get all incomplete checkpoints.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
loop_id: Optional filter by loop ID
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
List of incomplete checkpoints
|
|
222
|
+
"""
|
|
223
|
+
result = []
|
|
224
|
+
for path in self.base_dir.glob("*.json"):
|
|
225
|
+
try:
|
|
226
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
227
|
+
data = json.load(f)
|
|
228
|
+
|
|
229
|
+
if data.get("completed", False):
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
if loop_id and data.get("loop_id") != loop_id:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
result.append(Checkpoint.from_dict(data))
|
|
236
|
+
except (json.JSONDecodeError, KeyError):
|
|
237
|
+
logger.warning("Skipping invalid checkpoint file: %s", path, exc_info=True)
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
# Sort by updated_at descending
|
|
241
|
+
result.sort(key=lambda x: x.updated_at, reverse=True)
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
def delete(self, checkpoint_id: str) -> bool:
|
|
245
|
+
"""Delete a checkpoint."""
|
|
246
|
+
path = self._checkpoint_path(checkpoint_id)
|
|
247
|
+
if path.exists():
|
|
248
|
+
path.unlink()
|
|
249
|
+
return True
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
def cleanup(self, loop_id: str | None = None, keep_latest: int = 5) -> int:
|
|
253
|
+
"""
|
|
254
|
+
Clean up old checkpoints, keeping only the latest N.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
loop_id: Optional filter by loop ID
|
|
258
|
+
keep_latest: Number of checkpoints to keep per loop
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Number of checkpoints deleted
|
|
262
|
+
"""
|
|
263
|
+
# Group by loop_id
|
|
264
|
+
by_loop: dict[str, list[dict]] = {}
|
|
265
|
+
for cp in self.list_checkpoints():
|
|
266
|
+
lid = cp["loop_id"]
|
|
267
|
+
if loop_id and lid != loop_id:
|
|
268
|
+
continue
|
|
269
|
+
if lid not in by_loop:
|
|
270
|
+
by_loop[lid] = []
|
|
271
|
+
by_loop[lid].append(cp)
|
|
272
|
+
|
|
273
|
+
deleted = 0
|
|
274
|
+
for lid, checkpoints in by_loop.items():
|
|
275
|
+
# Sort by updated_at descending
|
|
276
|
+
checkpoints.sort(key=lambda x: x["updated_at"], reverse=True)
|
|
277
|
+
|
|
278
|
+
# Delete old ones
|
|
279
|
+
for cp in checkpoints[keep_latest:]:
|
|
280
|
+
if self.delete(cp["checkpoint_id"]):
|
|
281
|
+
deleted += 1
|
|
282
|
+
|
|
283
|
+
return deleted
|
|
284
|
+
|
|
285
|
+
def list_checkpoints(self, loop_id: str | None = None) -> list[dict[str, Any]]:
|
|
286
|
+
"""
|
|
287
|
+
List all checkpoints.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
loop_id: Optional filter by loop ID
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of checkpoint summaries
|
|
294
|
+
"""
|
|
295
|
+
checkpoints = []
|
|
296
|
+
for path in self.base_dir.glob("*.json"):
|
|
297
|
+
try:
|
|
298
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
299
|
+
data = json.load(f)
|
|
300
|
+
|
|
301
|
+
if loop_id and data.get("loop_id") != loop_id:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
checkpoints.append({
|
|
305
|
+
"checkpoint_id": data["checkpoint_id"],
|
|
306
|
+
"loop_id": data["loop_id"],
|
|
307
|
+
"iteration": data["iteration"],
|
|
308
|
+
"max_iterations": data.get("max_iterations"),
|
|
309
|
+
"completed": data.get("completed", False),
|
|
310
|
+
"completion_reason": data.get("completion_reason"),
|
|
311
|
+
"created_at": data["created_at"],
|
|
312
|
+
"updated_at": data["updated_at"],
|
|
313
|
+
})
|
|
314
|
+
except (json.JSONDecodeError, KeyError):
|
|
315
|
+
logger.warning("Skipping invalid checkpoint file: %s", path, exc_info=True)
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
# Sort by updated_at descending
|
|
319
|
+
checkpoints.sort(key=lambda x: x["updated_at"], reverse=True)
|
|
320
|
+
return checkpoints
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class CheckpointedRalph:
|
|
324
|
+
"""
|
|
325
|
+
Ralph Wiggum with automatic checkpointing.
|
|
326
|
+
|
|
327
|
+
Usage:
|
|
328
|
+
from copex import Copex
|
|
329
|
+
from copex.checkpoint import CheckpointedRalph, CheckpointStore
|
|
330
|
+
|
|
331
|
+
store = CheckpointStore()
|
|
332
|
+
|
|
333
|
+
async with Copex() as copex:
|
|
334
|
+
ralph = CheckpointedRalph(copex, store, loop_id="my-project")
|
|
335
|
+
|
|
336
|
+
# Start or resume a loop
|
|
337
|
+
result = await ralph.loop(
|
|
338
|
+
prompt="Build a REST API",
|
|
339
|
+
completion_promise="DONE",
|
|
340
|
+
)
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
def __init__(
|
|
344
|
+
self,
|
|
345
|
+
client: Any,
|
|
346
|
+
store: CheckpointStore,
|
|
347
|
+
loop_id: str,
|
|
348
|
+
):
|
|
349
|
+
"""
|
|
350
|
+
Initialize checkpointed Ralph.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
client: Copex client
|
|
354
|
+
store: Checkpoint store
|
|
355
|
+
loop_id: Identifier for this loop
|
|
356
|
+
"""
|
|
357
|
+
self.client = client
|
|
358
|
+
self.store = store
|
|
359
|
+
self.loop_id = loop_id
|
|
360
|
+
self._checkpoint: Checkpoint | None = None
|
|
361
|
+
|
|
362
|
+
async def loop(
|
|
363
|
+
self,
|
|
364
|
+
prompt: str,
|
|
365
|
+
*,
|
|
366
|
+
max_iterations: int | None = None,
|
|
367
|
+
completion_promise: str | None = None,
|
|
368
|
+
resume: bool = True,
|
|
369
|
+
) -> Checkpoint:
|
|
370
|
+
"""
|
|
371
|
+
Run a checkpointed Ralph loop.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
prompt: Task prompt
|
|
375
|
+
max_iterations: Maximum iterations
|
|
376
|
+
completion_promise: Text that signals completion
|
|
377
|
+
resume: Whether to resume from last checkpoint if available
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Final checkpoint state
|
|
381
|
+
"""
|
|
382
|
+
from copex.ralph import RalphConfig, RalphWiggum
|
|
383
|
+
|
|
384
|
+
# Check for existing checkpoint to resume
|
|
385
|
+
if resume:
|
|
386
|
+
existing = self.store.get_latest(self.loop_id)
|
|
387
|
+
if existing and not existing.completed:
|
|
388
|
+
self._checkpoint = existing
|
|
389
|
+
history = existing.history
|
|
390
|
+
else:
|
|
391
|
+
self._checkpoint = None
|
|
392
|
+
history = []
|
|
393
|
+
else:
|
|
394
|
+
history = []
|
|
395
|
+
|
|
396
|
+
# Create new checkpoint if needed
|
|
397
|
+
if not self._checkpoint:
|
|
398
|
+
model = self.client.config.model.value if hasattr(self.client.config.model, 'value') else str(self.client.config.model)
|
|
399
|
+
reasoning = self.client.config.reasoning_effort.value if hasattr(self.client.config.reasoning_effort, 'value') else str(self.client.config.reasoning_effort)
|
|
400
|
+
|
|
401
|
+
self._checkpoint = self.store.create(
|
|
402
|
+
loop_id=self.loop_id,
|
|
403
|
+
prompt=prompt,
|
|
404
|
+
max_iterations=max_iterations,
|
|
405
|
+
completion_promise=completion_promise,
|
|
406
|
+
model=model,
|
|
407
|
+
reasoning_effort=reasoning,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Create Ralph with config
|
|
411
|
+
config = RalphConfig(
|
|
412
|
+
max_iterations=max_iterations,
|
|
413
|
+
completion_promise=completion_promise,
|
|
414
|
+
)
|
|
415
|
+
ralph = RalphWiggum(self.client, config)
|
|
416
|
+
|
|
417
|
+
# Set up iteration callback to save checkpoints
|
|
418
|
+
def on_iteration(iteration: int, response: str) -> None:
|
|
419
|
+
history.append(response)
|
|
420
|
+
self.store.update(
|
|
421
|
+
self._checkpoint.checkpoint_id,
|
|
422
|
+
iteration=iteration,
|
|
423
|
+
history=history,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def on_complete(state) -> None:
|
|
427
|
+
self.store.update(
|
|
428
|
+
self._checkpoint.checkpoint_id,
|
|
429
|
+
iteration=state.iteration,
|
|
430
|
+
completed=True,
|
|
431
|
+
completion_reason=state.completion_reason,
|
|
432
|
+
history=history,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# Run the loop
|
|
436
|
+
await ralph.loop(
|
|
437
|
+
prompt,
|
|
438
|
+
max_iterations=max_iterations,
|
|
439
|
+
completion_promise=completion_promise,
|
|
440
|
+
on_iteration=on_iteration,
|
|
441
|
+
on_complete=on_complete,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Return updated checkpoint
|
|
445
|
+
return self.store.load(self._checkpoint.checkpoint_id)
|