cortex-loop 0.1.0a1__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.
- cortex/__init__.py +7 -0
- cortex/adapters.py +339 -0
- cortex/blocklist.py +51 -0
- cortex/challenges.py +210 -0
- cortex/cli.py +7 -0
- cortex/core.py +601 -0
- cortex/core_helpers.py +190 -0
- cortex/data/identity_preamble.md +5 -0
- cortex/data/layer1_part_a.md +65 -0
- cortex/data/layer1_part_b.md +17 -0
- cortex/executive.py +295 -0
- cortex/foundation.py +185 -0
- cortex/genome.py +348 -0
- cortex/graveyard.py +226 -0
- cortex/hooks/__init__.py +27 -0
- cortex/hooks/_shared.py +167 -0
- cortex/hooks/post_tool_use.py +13 -0
- cortex/hooks/pre_tool_use.py +13 -0
- cortex/hooks/session_start.py +13 -0
- cortex/hooks/stop.py +13 -0
- cortex/invariants.py +258 -0
- cortex/packs.py +118 -0
- cortex/repomap.py +6 -0
- cortex/requirements.py +497 -0
- cortex/retry.py +312 -0
- cortex/stop_contract.py +217 -0
- cortex/stop_payload.py +122 -0
- cortex/stop_policy.py +100 -0
- cortex/stop_runtime.py +400 -0
- cortex/stop_signals.py +75 -0
- cortex/store.py +793 -0
- cortex/templates/__init__.py +10 -0
- cortex/utils.py +58 -0
- cortex_loop-0.1.0a1.dist-info/METADATA +121 -0
- cortex_loop-0.1.0a1.dist-info/RECORD +52 -0
- cortex_loop-0.1.0a1.dist-info/WHEEL +5 -0
- cortex_loop-0.1.0a1.dist-info/entry_points.txt +3 -0
- cortex_loop-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- cortex_loop-0.1.0a1.dist-info/top_level.txt +3 -0
- cortex_ops_cli/__init__.py +3 -0
- cortex_ops_cli/_adapter_validation.py +119 -0
- cortex_ops_cli/_check_report.py +454 -0
- cortex_ops_cli/_check_report_output.py +270 -0
- cortex_ops_cli/_openai_bridge_probe.py +241 -0
- cortex_ops_cli/_openai_bridge_protocol.py +469 -0
- cortex_ops_cli/_runtime_profile_templates.py +341 -0
- cortex_ops_cli/_runtime_profiles.py +445 -0
- cortex_ops_cli/gemini_hooks.py +301 -0
- cortex_ops_cli/main.py +911 -0
- cortex_ops_cli/openai_app_server_bridge.py +375 -0
- cortex_repomap/__init__.py +1 -0
- cortex_repomap/engine.py +1201 -0
cortex/store.py
ADDED
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sqlite3
|
|
7
|
+
import time
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Callable, Iterator
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
|
|
15
|
+
DB_SCHEMA_VERSION = 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _utc_now() -> str:
|
|
19
|
+
return datetime.now(timezone.utc).isoformat()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_FTS_TOKEN_RE = re.compile(r"^[a-z0-9_]+$")
|
|
23
|
+
_COMPACT_TEXT_FIELDS = ("last_assistant_message", "stdout", "stderr", "output", "message")
|
|
24
|
+
_COMPACT_TEXT_MAX_LEN = 2048
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(slots=True)
|
|
28
|
+
class SQLiteStore:
|
|
29
|
+
db_path: Path
|
|
30
|
+
lock_retry_attempts: int = 3
|
|
31
|
+
lock_retry_backoff_ms: int = 25
|
|
32
|
+
busy_timeout_ms: int = 5000
|
|
33
|
+
|
|
34
|
+
def initialize(self) -> None:
|
|
35
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
with self.connection() as conn:
|
|
37
|
+
conn.execute("PRAGMA journal_mode = WAL")
|
|
38
|
+
conn.execute("PRAGMA foreign_keys = ON")
|
|
39
|
+
conn.execute(f"PRAGMA user_version = {DB_SCHEMA_VERSION}")
|
|
40
|
+
conn.executescript(
|
|
41
|
+
"""
|
|
42
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
43
|
+
session_id TEXT PRIMARY KEY,
|
|
44
|
+
started_at TEXT NOT NULL,
|
|
45
|
+
ended_at TEXT,
|
|
46
|
+
status TEXT NOT NULL,
|
|
47
|
+
genome_path TEXT,
|
|
48
|
+
metadata_json TEXT NOT NULL
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE IF NOT EXISTS graveyard (
|
|
52
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
session_id TEXT,
|
|
54
|
+
summary TEXT NOT NULL,
|
|
55
|
+
reason TEXT NOT NULL,
|
|
56
|
+
files_json TEXT NOT NULL,
|
|
57
|
+
keywords_json TEXT NOT NULL,
|
|
58
|
+
created_at TEXT NOT NULL
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE TABLE IF NOT EXISTS invariants (
|
|
62
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
|
+
session_id TEXT NOT NULL,
|
|
64
|
+
test_path TEXT NOT NULL,
|
|
65
|
+
status TEXT NOT NULL,
|
|
66
|
+
duration_ms INTEGER NOT NULL,
|
|
67
|
+
stdout TEXT NOT NULL,
|
|
68
|
+
stderr TEXT NOT NULL,
|
|
69
|
+
graduated_from TEXT,
|
|
70
|
+
created_at TEXT NOT NULL
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS challenge_results (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
session_id TEXT NOT NULL,
|
|
76
|
+
category TEXT NOT NULL,
|
|
77
|
+
covered INTEGER NOT NULL,
|
|
78
|
+
evidence_json TEXT NOT NULL,
|
|
79
|
+
created_at TEXT NOT NULL
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
83
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
|
+
session_id TEXT NOT NULL,
|
|
85
|
+
hook TEXT NOT NULL,
|
|
86
|
+
tool_name TEXT,
|
|
87
|
+
status TEXT,
|
|
88
|
+
payload_json TEXT NOT NULL,
|
|
89
|
+
created_at TEXT NOT NULL
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS retry_budget (
|
|
93
|
+
session_id TEXT PRIMARY KEY,
|
|
94
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
95
|
+
updated_at TEXT NOT NULL
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
CREATE TABLE IF NOT EXISTS retry_reason_budget (
|
|
99
|
+
session_id TEXT NOT NULL,
|
|
100
|
+
reason TEXT NOT NULL,
|
|
101
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
102
|
+
updated_at TEXT NOT NULL,
|
|
103
|
+
PRIMARY KEY (session_id, reason)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE IF NOT EXISTS retry_delta_state (
|
|
107
|
+
session_id TEXT NOT NULL,
|
|
108
|
+
reason TEXT NOT NULL,
|
|
109
|
+
failure_signature TEXT NOT NULL,
|
|
110
|
+
last_delta_hash TEXT NOT NULL,
|
|
111
|
+
updated_at TEXT NOT NULL,
|
|
112
|
+
PRIMARY KEY (session_id, reason, failure_signature)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE TABLE IF NOT EXISTS cortex_meta (
|
|
116
|
+
key TEXT PRIMARY KEY,
|
|
117
|
+
value_text TEXT NOT NULL
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS executive_memory (
|
|
121
|
+
id TEXT PRIMARY KEY,
|
|
122
|
+
type TEXT NOT NULL,
|
|
123
|
+
trigger_pattern TEXT NOT NULL,
|
|
124
|
+
error_pattern TEXT NOT NULL,
|
|
125
|
+
resolution TEXT NOT NULL,
|
|
126
|
+
strength INTEGER NOT NULL DEFAULT 1,
|
|
127
|
+
created_at_session INTEGER NOT NULL,
|
|
128
|
+
last_accessed_at_session INTEGER NOT NULL,
|
|
129
|
+
source_session_id TEXT NOT NULL
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
CREATE TABLE IF NOT EXISTS session_counter_allocations (
|
|
133
|
+
session_id TEXT PRIMARY KEY,
|
|
134
|
+
counter INTEGER NOT NULL
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS executive_stop_signatures (
|
|
138
|
+
session_id TEXT PRIMARY KEY,
|
|
139
|
+
signature TEXT NOT NULL,
|
|
140
|
+
updated_at TEXT NOT NULL
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_events_session_hook ON events(session_id, hook);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_graveyard_session ON graveyard(session_id);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_challenge_results_session_category
|
|
146
|
+
ON challenge_results(session_id, category);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_invariants_session_status
|
|
148
|
+
ON invariants(session_id, status);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_executive_memory_type_strength
|
|
150
|
+
ON executive_memory(type, strength DESC);
|
|
151
|
+
"""
|
|
152
|
+
)
|
|
153
|
+
self._purge_transient_session_state(conn)
|
|
154
|
+
|
|
155
|
+
@contextmanager
|
|
156
|
+
def connection(self) -> Iterator[sqlite3.Connection]:
|
|
157
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
conn = sqlite3.connect(self.db_path, timeout=5.0)
|
|
159
|
+
conn.row_factory = sqlite3.Row
|
|
160
|
+
conn.execute(f"PRAGMA busy_timeout = {max(0, int(self.busy_timeout_ms))}")
|
|
161
|
+
try:
|
|
162
|
+
yield conn
|
|
163
|
+
conn.commit()
|
|
164
|
+
finally:
|
|
165
|
+
conn.close()
|
|
166
|
+
|
|
167
|
+
def _run_write(self, operation: Callable[[sqlite3.Connection], None]) -> None:
|
|
168
|
+
attempts = max(0, int(self.lock_retry_attempts)) + 1
|
|
169
|
+
backoff = max(0, int(self.lock_retry_backoff_ms)) / 1000.0
|
|
170
|
+
for attempt in range(attempts):
|
|
171
|
+
try:
|
|
172
|
+
with self.connection() as conn:
|
|
173
|
+
operation(conn)
|
|
174
|
+
return
|
|
175
|
+
except sqlite3.OperationalError as exc:
|
|
176
|
+
if not self._is_lock_error(exc) or attempt >= attempts - 1:
|
|
177
|
+
raise
|
|
178
|
+
if backoff > 0:
|
|
179
|
+
time.sleep(backoff * (2**attempt))
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _is_lock_error(exc: sqlite3.OperationalError) -> bool:
|
|
183
|
+
return any(msg in str(exc).lower() for msg in ("database is locked", "database table is locked"))
|
|
184
|
+
|
|
185
|
+
def upsert_session_start(
|
|
186
|
+
self, session_id: str, status: str, genome_path: str | None, metadata: dict[str, Any] | None = None
|
|
187
|
+
) -> None:
|
|
188
|
+
self._execute_write(
|
|
189
|
+
"""
|
|
190
|
+
INSERT INTO sessions (session_id, started_at, status, genome_path, metadata_json)
|
|
191
|
+
VALUES (?, ?, ?, ?, ?)
|
|
192
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
193
|
+
status=excluded.status,
|
|
194
|
+
genome_path=excluded.genome_path,
|
|
195
|
+
metadata_json=excluded.metadata_json
|
|
196
|
+
""",
|
|
197
|
+
(
|
|
198
|
+
session_id,
|
|
199
|
+
_utc_now(),
|
|
200
|
+
status,
|
|
201
|
+
genome_path,
|
|
202
|
+
json.dumps(metadata or {}, sort_keys=True),
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def ensure_session_start(
|
|
207
|
+
self, session_id: str, status: str, genome_path: str | None, metadata: dict[str, Any] | None = None
|
|
208
|
+
) -> None:
|
|
209
|
+
self._execute_write(
|
|
210
|
+
"""
|
|
211
|
+
INSERT OR IGNORE INTO sessions (session_id, started_at, status, genome_path, metadata_json)
|
|
212
|
+
VALUES (?, ?, ?, ?, ?)
|
|
213
|
+
""",
|
|
214
|
+
(
|
|
215
|
+
session_id,
|
|
216
|
+
_utc_now(),
|
|
217
|
+
status,
|
|
218
|
+
genome_path,
|
|
219
|
+
json.dumps(metadata or {}, sort_keys=True),
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def close_session(self, session_id: str, status: str, metadata: dict[str, Any] | None = None) -> None:
|
|
224
|
+
now = _utc_now()
|
|
225
|
+
closed_filter = "session_id IN (SELECT session_id FROM sessions WHERE status != 'running' OR ended_at IS NOT NULL)"
|
|
226
|
+
|
|
227
|
+
def _op(conn: sqlite3.Connection) -> None:
|
|
228
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
229
|
+
conn.execute(
|
|
230
|
+
"""
|
|
231
|
+
UPDATE sessions
|
|
232
|
+
SET ended_at = ?, status = ?, metadata_json = ?
|
|
233
|
+
WHERE session_id = ?
|
|
234
|
+
""",
|
|
235
|
+
(
|
|
236
|
+
now,
|
|
237
|
+
status,
|
|
238
|
+
json.dumps(metadata or {}, sort_keys=True),
|
|
239
|
+
session_id,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
conn.execute(f"DELETE FROM session_counter_allocations WHERE {closed_filter}")
|
|
243
|
+
conn.execute(
|
|
244
|
+
f"DELETE FROM executive_stop_signatures WHERE session_id <> ? AND {closed_filter}",
|
|
245
|
+
(session_id,),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
self._run_write(_op)
|
|
249
|
+
|
|
250
|
+
def record_event(
|
|
251
|
+
self,
|
|
252
|
+
session_id: str,
|
|
253
|
+
hook: str,
|
|
254
|
+
payload: dict[str, Any],
|
|
255
|
+
tool_name: str | None = None,
|
|
256
|
+
status: str | None = None,
|
|
257
|
+
) -> None:
|
|
258
|
+
compacted_payload = self._compact_event_payload(payload)
|
|
259
|
+
self._execute_write(
|
|
260
|
+
"""
|
|
261
|
+
INSERT INTO events (session_id, hook, tool_name, status, payload_json, created_at)
|
|
262
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
263
|
+
""",
|
|
264
|
+
(
|
|
265
|
+
session_id,
|
|
266
|
+
hook,
|
|
267
|
+
tool_name,
|
|
268
|
+
status,
|
|
269
|
+
json.dumps(compacted_payload, sort_keys=True),
|
|
270
|
+
_utc_now(),
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def get_retry_delta_hash(self, session_id: str, reason: str, failure_signature: str) -> str | None:
|
|
275
|
+
reason_token = str(reason or "").strip()
|
|
276
|
+
signature_token = str(failure_signature or "").strip()
|
|
277
|
+
if not reason_token or not signature_token:
|
|
278
|
+
return None
|
|
279
|
+
with self.connection() as conn:
|
|
280
|
+
row = conn.execute(
|
|
281
|
+
"""
|
|
282
|
+
SELECT last_delta_hash
|
|
283
|
+
FROM retry_delta_state
|
|
284
|
+
WHERE session_id = ? AND reason = ? AND failure_signature = ?
|
|
285
|
+
LIMIT 1
|
|
286
|
+
""",
|
|
287
|
+
(session_id, reason_token, signature_token),
|
|
288
|
+
).fetchone()
|
|
289
|
+
if row is None:
|
|
290
|
+
return None
|
|
291
|
+
return str(row["last_delta_hash"] or "")
|
|
292
|
+
|
|
293
|
+
def upsert_retry_delta_hash(
|
|
294
|
+
self,
|
|
295
|
+
session_id: str,
|
|
296
|
+
*,
|
|
297
|
+
reason: str,
|
|
298
|
+
failure_signature: str,
|
|
299
|
+
delta_hash: str,
|
|
300
|
+
) -> None:
|
|
301
|
+
reason_token = str(reason or "").strip()
|
|
302
|
+
signature_token = str(failure_signature or "").strip()
|
|
303
|
+
if not reason_token or not signature_token:
|
|
304
|
+
return
|
|
305
|
+
self._execute_write(
|
|
306
|
+
"""
|
|
307
|
+
INSERT INTO retry_delta_state (session_id, reason, failure_signature, last_delta_hash, updated_at)
|
|
308
|
+
VALUES (?, ?, ?, ?, ?)
|
|
309
|
+
ON CONFLICT(session_id, reason, failure_signature) DO UPDATE SET
|
|
310
|
+
last_delta_hash = excluded.last_delta_hash,
|
|
311
|
+
updated_at = excluded.updated_at
|
|
312
|
+
""",
|
|
313
|
+
(
|
|
314
|
+
session_id,
|
|
315
|
+
reason_token,
|
|
316
|
+
signature_token,
|
|
317
|
+
str(delta_hash or ""),
|
|
318
|
+
_utc_now(),
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def get_retry_attempts(self, session_id: str, *, reason: str) -> dict[str, int]:
|
|
323
|
+
reason_token = str(reason or "").strip()
|
|
324
|
+
if not reason_token:
|
|
325
|
+
return {"attempts": 0, "reason_attempts": 0}
|
|
326
|
+
with self.connection() as conn:
|
|
327
|
+
session_row = conn.execute(
|
|
328
|
+
"SELECT attempts FROM retry_budget WHERE session_id = ?",
|
|
329
|
+
(session_id,),
|
|
330
|
+
).fetchone()
|
|
331
|
+
reason_row = conn.execute(
|
|
332
|
+
"SELECT attempts FROM retry_reason_budget WHERE session_id = ? AND reason = ?",
|
|
333
|
+
(session_id, reason_token),
|
|
334
|
+
).fetchone()
|
|
335
|
+
return {
|
|
336
|
+
"attempts": int(session_row["attempts"]) if session_row is not None else 0,
|
|
337
|
+
"reason_attempts": int(reason_row["attempts"]) if reason_row is not None else 0,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
def try_increment_retry_attempts(
|
|
341
|
+
self,
|
|
342
|
+
session_id: str,
|
|
343
|
+
*,
|
|
344
|
+
reason: str,
|
|
345
|
+
expected_attempts: int,
|
|
346
|
+
expected_reason_attempts: int,
|
|
347
|
+
) -> dict[str, int | bool]:
|
|
348
|
+
reason_token = str(reason or "").strip()
|
|
349
|
+
outcome: dict[str, int | bool] = {
|
|
350
|
+
"consumed": False,
|
|
351
|
+
"attempts": max(0, int(expected_attempts)),
|
|
352
|
+
"reason_attempts": max(0, int(expected_reason_attempts)),
|
|
353
|
+
}
|
|
354
|
+
if not reason_token:
|
|
355
|
+
return outcome
|
|
356
|
+
|
|
357
|
+
def _op(conn: sqlite3.Connection) -> None:
|
|
358
|
+
now = _utc_now()
|
|
359
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
360
|
+
self._ensure_retry_budget_rows(conn, session_id=session_id, reason=reason_token, now=now)
|
|
361
|
+
session_row = conn.execute(
|
|
362
|
+
"SELECT attempts FROM retry_budget WHERE session_id = ?",
|
|
363
|
+
(session_id,),
|
|
364
|
+
).fetchone()
|
|
365
|
+
reason_row = conn.execute(
|
|
366
|
+
"SELECT attempts FROM retry_reason_budget WHERE session_id = ? AND reason = ?",
|
|
367
|
+
(session_id, reason_token),
|
|
368
|
+
).fetchone()
|
|
369
|
+
session_attempts = int(session_row["attempts"]) if session_row is not None else 0
|
|
370
|
+
reason_attempts = int(reason_row["attempts"]) if reason_row is not None else 0
|
|
371
|
+
|
|
372
|
+
if (
|
|
373
|
+
session_attempts == max(0, int(expected_attempts))
|
|
374
|
+
and reason_attempts == max(0, int(expected_reason_attempts))
|
|
375
|
+
):
|
|
376
|
+
conn.execute(
|
|
377
|
+
"""
|
|
378
|
+
UPDATE retry_budget
|
|
379
|
+
SET attempts = attempts + 1, updated_at = ?
|
|
380
|
+
WHERE session_id = ?
|
|
381
|
+
""",
|
|
382
|
+
(now, session_id),
|
|
383
|
+
)
|
|
384
|
+
conn.execute(
|
|
385
|
+
"""
|
|
386
|
+
UPDATE retry_reason_budget
|
|
387
|
+
SET attempts = attempts + 1, updated_at = ?
|
|
388
|
+
WHERE session_id = ? AND reason = ?
|
|
389
|
+
""",
|
|
390
|
+
(now, session_id, reason_token),
|
|
391
|
+
)
|
|
392
|
+
session_attempts += 1
|
|
393
|
+
reason_attempts += 1
|
|
394
|
+
outcome["consumed"] = True
|
|
395
|
+
|
|
396
|
+
outcome["attempts"] = session_attempts
|
|
397
|
+
outcome["reason_attempts"] = reason_attempts
|
|
398
|
+
|
|
399
|
+
self._run_write(_op)
|
|
400
|
+
return outcome
|
|
401
|
+
|
|
402
|
+
def insert_graveyard(
|
|
403
|
+
self,
|
|
404
|
+
session_id: str | None,
|
|
405
|
+
summary: str,
|
|
406
|
+
reason: str,
|
|
407
|
+
files: list[str],
|
|
408
|
+
keywords: list[str],
|
|
409
|
+
) -> None:
|
|
410
|
+
self._execute_write(
|
|
411
|
+
"""
|
|
412
|
+
INSERT INTO graveyard (session_id, summary, reason, files_json, keywords_json, created_at)
|
|
413
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
414
|
+
""",
|
|
415
|
+
(
|
|
416
|
+
session_id,
|
|
417
|
+
summary,
|
|
418
|
+
reason,
|
|
419
|
+
json.dumps(files, sort_keys=True),
|
|
420
|
+
json.dumps(keywords, sort_keys=True),
|
|
421
|
+
_utc_now(),
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
def list_graveyard(self, limit: int = 100) -> list[dict[str, Any]]:
|
|
426
|
+
rows = self._list_graveyard_rows(limit)
|
|
427
|
+
return [self._graveyard_row_to_item(row) for row in rows]
|
|
428
|
+
|
|
429
|
+
def list_graveyard_fts_candidates(
|
|
430
|
+
self,
|
|
431
|
+
*,
|
|
432
|
+
tokens: list[str],
|
|
433
|
+
limit: int = 200,
|
|
434
|
+
candidate_limit: int = 80,
|
|
435
|
+
) -> list[dict[str, Any]] | None:
|
|
436
|
+
terms = [t for t in tokens if _FTS_TOKEN_RE.match(t)]
|
|
437
|
+
if not terms or not (rows := self._list_graveyard_rows(limit)):
|
|
438
|
+
return []
|
|
439
|
+
with self.connection() as conn:
|
|
440
|
+
try:
|
|
441
|
+
conn.execute(
|
|
442
|
+
"CREATE TEMP VIRTUAL TABLE IF NOT EXISTS _cortex_graveyard_fts "
|
|
443
|
+
"USING fts5(entry_id UNINDEXED, text)"
|
|
444
|
+
)
|
|
445
|
+
conn.execute("DELETE FROM _cortex_graveyard_fts")
|
|
446
|
+
except sqlite3.OperationalError:
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
conn.executemany(
|
|
450
|
+
"INSERT INTO _cortex_graveyard_fts(entry_id, text) VALUES (?, ?)",
|
|
451
|
+
[
|
|
452
|
+
(
|
|
453
|
+
str(row["id"]),
|
|
454
|
+
f"{row['summary']} {row['reason']} {row['keywords_json']}",
|
|
455
|
+
)
|
|
456
|
+
for row in rows
|
|
457
|
+
],
|
|
458
|
+
)
|
|
459
|
+
match_query = " OR ".join(terms[:12])
|
|
460
|
+
matched = conn.execute(
|
|
461
|
+
"""
|
|
462
|
+
SELECT entry_id
|
|
463
|
+
FROM _cortex_graveyard_fts
|
|
464
|
+
WHERE _cortex_graveyard_fts MATCH ?
|
|
465
|
+
ORDER BY bm25(_cortex_graveyard_fts), CAST(entry_id AS INTEGER) DESC
|
|
466
|
+
LIMIT ?
|
|
467
|
+
""",
|
|
468
|
+
(match_query, max(1, int(candidate_limit))),
|
|
469
|
+
).fetchall()
|
|
470
|
+
|
|
471
|
+
if not matched:
|
|
472
|
+
return []
|
|
473
|
+
row_map = {int(row["id"]): row for row in rows}
|
|
474
|
+
ordered_ids = [int(row["entry_id"]) for row in matched]
|
|
475
|
+
return [self._graveyard_row_to_item(row_map[eid]) for eid in ordered_ids if eid in row_map]
|
|
476
|
+
|
|
477
|
+
def record_invariant_result(
|
|
478
|
+
self,
|
|
479
|
+
session_id: str,
|
|
480
|
+
test_path: str,
|
|
481
|
+
status: str,
|
|
482
|
+
duration_ms: int,
|
|
483
|
+
stdout: str,
|
|
484
|
+
stderr: str,
|
|
485
|
+
graduated_from: str | None = None,
|
|
486
|
+
) -> None:
|
|
487
|
+
self._execute_write(
|
|
488
|
+
"""
|
|
489
|
+
INSERT INTO invariants
|
|
490
|
+
(session_id, test_path, status, duration_ms, stdout, stderr, graduated_from, created_at)
|
|
491
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
492
|
+
""",
|
|
493
|
+
(
|
|
494
|
+
session_id,
|
|
495
|
+
test_path,
|
|
496
|
+
status,
|
|
497
|
+
duration_ms,
|
|
498
|
+
stdout,
|
|
499
|
+
stderr,
|
|
500
|
+
graduated_from,
|
|
501
|
+
_utc_now(),
|
|
502
|
+
),
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def record_challenge_result(
|
|
506
|
+
self, session_id: str, category: str, covered: bool, evidence: dict[str, Any] | None = None
|
|
507
|
+
) -> None:
|
|
508
|
+
self._execute_write(
|
|
509
|
+
"""
|
|
510
|
+
INSERT INTO challenge_results (session_id, category, covered, evidence_json, created_at)
|
|
511
|
+
VALUES (?, ?, ?, ?, ?)
|
|
512
|
+
""",
|
|
513
|
+
(
|
|
514
|
+
session_id,
|
|
515
|
+
category,
|
|
516
|
+
1 if covered else 0,
|
|
517
|
+
json.dumps(evidence or {}, sort_keys=True),
|
|
518
|
+
_utc_now(),
|
|
519
|
+
),
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
def _execute_write(self, sql: str, params: tuple[Any, ...]) -> None:
|
|
523
|
+
self._run_write(lambda conn: conn.execute(sql, params))
|
|
524
|
+
|
|
525
|
+
def increment_session_counter(self) -> int:
|
|
526
|
+
result = {"value": 0}
|
|
527
|
+
|
|
528
|
+
def _op(conn: sqlite3.Connection) -> None:
|
|
529
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
530
|
+
conn.execute("INSERT INTO cortex_meta (key, value_text) VALUES ('session_counter', '0') ON CONFLICT(key) DO NOTHING")
|
|
531
|
+
conn.execute("UPDATE cortex_meta SET value_text = CAST(COALESCE(value_text, '0') AS INTEGER) + 1 WHERE key = 'session_counter'")
|
|
532
|
+
row = conn.execute("SELECT value_text FROM cortex_meta WHERE key = 'session_counter'").fetchone()
|
|
533
|
+
result["value"] = int(row["value_text"]) if row is not None else 0
|
|
534
|
+
|
|
535
|
+
self._run_write(_op)
|
|
536
|
+
return int(result["value"])
|
|
537
|
+
|
|
538
|
+
def allocate_session_counter(self, session_id: str) -> int:
|
|
539
|
+
token = str(session_id or "").strip()
|
|
540
|
+
if not token:
|
|
541
|
+
return self.increment_session_counter()
|
|
542
|
+
result = {"value": 0}
|
|
543
|
+
|
|
544
|
+
def _op(conn: sqlite3.Connection) -> None:
|
|
545
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
546
|
+
existing = conn.execute(
|
|
547
|
+
"SELECT counter FROM session_counter_allocations WHERE session_id = ? LIMIT 1",
|
|
548
|
+
(token,),
|
|
549
|
+
).fetchone()
|
|
550
|
+
if existing is not None:
|
|
551
|
+
result["value"] = int(existing["counter"])
|
|
552
|
+
return
|
|
553
|
+
conn.execute("INSERT INTO cortex_meta (key, value_text) VALUES ('session_counter', '0') ON CONFLICT(key) DO NOTHING")
|
|
554
|
+
conn.execute("UPDATE cortex_meta SET value_text = CAST(COALESCE(value_text, '0') AS INTEGER) + 1 WHERE key = 'session_counter'")
|
|
555
|
+
counter = conn.execute("SELECT value_text FROM cortex_meta WHERE key = 'session_counter'").fetchone()
|
|
556
|
+
allocated = int(counter["value_text"]) if counter is not None else 0
|
|
557
|
+
conn.execute(
|
|
558
|
+
"""
|
|
559
|
+
INSERT INTO session_counter_allocations (session_id, counter)
|
|
560
|
+
VALUES (?, ?)
|
|
561
|
+
ON CONFLICT(session_id) DO NOTHING
|
|
562
|
+
""",
|
|
563
|
+
(token, allocated),
|
|
564
|
+
)
|
|
565
|
+
row = conn.execute(
|
|
566
|
+
"SELECT counter FROM session_counter_allocations WHERE session_id = ? LIMIT 1",
|
|
567
|
+
(token,),
|
|
568
|
+
).fetchone()
|
|
569
|
+
result["value"] = int(row["counter"]) if row is not None else allocated
|
|
570
|
+
|
|
571
|
+
self._run_write(_op)
|
|
572
|
+
return int(result["value"])
|
|
573
|
+
|
|
574
|
+
def claim_executive_stop_signature(self, session_id: str, signature: str) -> bool:
|
|
575
|
+
sid = str(session_id or "").strip()
|
|
576
|
+
sig = str(signature or "").strip()
|
|
577
|
+
if not sid or not sig:
|
|
578
|
+
return False
|
|
579
|
+
claimed = {"value": False}
|
|
580
|
+
|
|
581
|
+
def _op(conn: sqlite3.Connection) -> None:
|
|
582
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
583
|
+
row = conn.execute(
|
|
584
|
+
"SELECT signature FROM executive_stop_signatures WHERE session_id = ? LIMIT 1",
|
|
585
|
+
(sid,),
|
|
586
|
+
).fetchone()
|
|
587
|
+
if row is not None and str(row["signature"] or "") == sig:
|
|
588
|
+
claimed["value"] = False
|
|
589
|
+
return
|
|
590
|
+
conn.execute(
|
|
591
|
+
"""
|
|
592
|
+
INSERT INTO executive_stop_signatures (session_id, signature, updated_at)
|
|
593
|
+
VALUES (?, ?, ?)
|
|
594
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
595
|
+
signature = excluded.signature,
|
|
596
|
+
updated_at = excluded.updated_at
|
|
597
|
+
""",
|
|
598
|
+
(sid, sig, _utc_now()),
|
|
599
|
+
)
|
|
600
|
+
claimed["value"] = True
|
|
601
|
+
|
|
602
|
+
self._run_write(_op)
|
|
603
|
+
return bool(claimed["value"])
|
|
604
|
+
|
|
605
|
+
def get_session_count(self) -> int:
|
|
606
|
+
with self.connection() as conn:
|
|
607
|
+
row = conn.execute(
|
|
608
|
+
"SELECT value_text FROM cortex_meta WHERE key = 'session_counter' LIMIT 1"
|
|
609
|
+
).fetchone()
|
|
610
|
+
if row is None:
|
|
611
|
+
return 0
|
|
612
|
+
try:
|
|
613
|
+
return int(row["value_text"])
|
|
614
|
+
except (TypeError, ValueError):
|
|
615
|
+
return 0
|
|
616
|
+
|
|
617
|
+
def claim_meta_once(self, key: str, value: str = "1") -> bool:
|
|
618
|
+
token = str(key or "").strip()
|
|
619
|
+
if not token:
|
|
620
|
+
return False
|
|
621
|
+
claimed = {"value": False}
|
|
622
|
+
|
|
623
|
+
def _op(conn: sqlite3.Connection) -> None:
|
|
624
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
625
|
+
existing = conn.execute(
|
|
626
|
+
"SELECT 1 FROM cortex_meta WHERE key = ? LIMIT 1",
|
|
627
|
+
(token,),
|
|
628
|
+
).fetchone()
|
|
629
|
+
if existing is not None:
|
|
630
|
+
claimed["value"] = False
|
|
631
|
+
return
|
|
632
|
+
conn.execute(
|
|
633
|
+
"INSERT INTO cortex_meta (key, value_text) VALUES (?, ?)",
|
|
634
|
+
(token, str(value)),
|
|
635
|
+
)
|
|
636
|
+
claimed["value"] = True
|
|
637
|
+
|
|
638
|
+
self._run_write(_op)
|
|
639
|
+
return bool(claimed["value"])
|
|
640
|
+
|
|
641
|
+
def record_executive_event(
|
|
642
|
+
self,
|
|
643
|
+
event_type: str,
|
|
644
|
+
trigger: str,
|
|
645
|
+
error: str,
|
|
646
|
+
resolution: str,
|
|
647
|
+
session_id: str,
|
|
648
|
+
) -> dict[str, Any]:
|
|
649
|
+
current_session = self.get_session_count()
|
|
650
|
+
row = {
|
|
651
|
+
"id": f"exec-{uuid4().hex}",
|
|
652
|
+
"type": str(event_type),
|
|
653
|
+
"trigger_pattern": str(trigger),
|
|
654
|
+
"error_pattern": str(error),
|
|
655
|
+
"resolution": str(resolution),
|
|
656
|
+
"strength": 1,
|
|
657
|
+
"created_at_session": current_session,
|
|
658
|
+
"last_accessed_at_session": current_session,
|
|
659
|
+
"source_session_id": str(session_id),
|
|
660
|
+
}
|
|
661
|
+
self._execute_write(
|
|
662
|
+
"INSERT INTO executive_memory (id, type, trigger_pattern, error_pattern, resolution, strength, created_at_session, last_accessed_at_session, source_session_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
663
|
+
(
|
|
664
|
+
row["id"],
|
|
665
|
+
row["type"],
|
|
666
|
+
row["trigger_pattern"],
|
|
667
|
+
row["error_pattern"],
|
|
668
|
+
row["resolution"],
|
|
669
|
+
row["strength"],
|
|
670
|
+
row["created_at_session"],
|
|
671
|
+
row["last_accessed_at_session"],
|
|
672
|
+
row["source_session_id"],
|
|
673
|
+
),
|
|
674
|
+
)
|
|
675
|
+
return row
|
|
676
|
+
|
|
677
|
+
def get_executive_memory(self) -> list[dict[str, Any]]:
|
|
678
|
+
with self.connection() as conn:
|
|
679
|
+
rows = conn.execute("SELECT id, type, trigger_pattern, error_pattern, resolution, strength, created_at_session, last_accessed_at_session, source_session_id FROM executive_memory ORDER BY created_at_session DESC, id ASC").fetchall()
|
|
680
|
+
return [
|
|
681
|
+
{
|
|
682
|
+
"id": str(row["id"]),
|
|
683
|
+
"type": str(row["type"]),
|
|
684
|
+
"trigger_pattern": str(row["trigger_pattern"]),
|
|
685
|
+
"error_pattern": str(row["error_pattern"]),
|
|
686
|
+
"resolution": str(row["resolution"]),
|
|
687
|
+
"strength": int(row["strength"]),
|
|
688
|
+
"created_at_session": int(row["created_at_session"]),
|
|
689
|
+
"last_accessed_at_session": int(row["last_accessed_at_session"]),
|
|
690
|
+
"source_session_id": str(row["source_session_id"]),
|
|
691
|
+
}
|
|
692
|
+
for row in rows
|
|
693
|
+
]
|
|
694
|
+
|
|
695
|
+
def update_executive_entry(
|
|
696
|
+
self,
|
|
697
|
+
entry_id: str,
|
|
698
|
+
*,
|
|
699
|
+
strength: int | None = None,
|
|
700
|
+
last_accessed_at_session: int | None = None,
|
|
701
|
+
) -> None:
|
|
702
|
+
updates: list[str] = []
|
|
703
|
+
params: list[Any] = []
|
|
704
|
+
if strength is not None:
|
|
705
|
+
updates.append("strength = ?")
|
|
706
|
+
params.append(max(1, int(strength)))
|
|
707
|
+
if last_accessed_at_session is not None:
|
|
708
|
+
updates.append("last_accessed_at_session = ?")
|
|
709
|
+
params.append(max(0, int(last_accessed_at_session)))
|
|
710
|
+
if not updates:
|
|
711
|
+
return
|
|
712
|
+
params.append(str(entry_id))
|
|
713
|
+
self._execute_write(
|
|
714
|
+
f"UPDATE executive_memory SET {', '.join(updates)} WHERE id = ?",
|
|
715
|
+
tuple(params),
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
def delete_executive_entries(self, ids: list[str]) -> None:
|
|
719
|
+
tokens = [str(item).strip() for item in ids if str(item).strip()]
|
|
720
|
+
if not tokens:
|
|
721
|
+
return
|
|
722
|
+
placeholders = ",".join(["?"] * len(tokens))
|
|
723
|
+
self._execute_write(
|
|
724
|
+
f"DELETE FROM executive_memory WHERE id IN ({placeholders})",
|
|
725
|
+
tuple(tokens),
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
@staticmethod
|
|
729
|
+
def _ensure_retry_budget_rows(conn: sqlite3.Connection, *, session_id: str, reason: str, now: str) -> None:
|
|
730
|
+
conn.execute(
|
|
731
|
+
"""
|
|
732
|
+
INSERT INTO retry_budget (session_id, attempts, updated_at)
|
|
733
|
+
VALUES (?, 0, ?)
|
|
734
|
+
ON CONFLICT(session_id) DO NOTHING
|
|
735
|
+
""",
|
|
736
|
+
(session_id, now),
|
|
737
|
+
)
|
|
738
|
+
conn.execute(
|
|
739
|
+
"""
|
|
740
|
+
INSERT INTO retry_reason_budget (session_id, reason, attempts, updated_at)
|
|
741
|
+
VALUES (?, ?, 0, ?)
|
|
742
|
+
ON CONFLICT(session_id, reason) DO NOTHING
|
|
743
|
+
""",
|
|
744
|
+
(session_id, reason, now),
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
@staticmethod
|
|
748
|
+
def _purge_transient_session_state(conn: sqlite3.Connection) -> None:
|
|
749
|
+
running = "session_id IN (SELECT session_id FROM sessions WHERE status = 'running' AND ended_at IS NULL)"
|
|
750
|
+
conn.execute(f"DELETE FROM session_counter_allocations WHERE NOT ({running})")
|
|
751
|
+
conn.execute(f"DELETE FROM executive_stop_signatures WHERE NOT ({running})")
|
|
752
|
+
|
|
753
|
+
def _list_graveyard_rows(self, limit: int) -> list[sqlite3.Row]:
|
|
754
|
+
with self.connection() as conn:
|
|
755
|
+
return conn.execute(
|
|
756
|
+
"""
|
|
757
|
+
SELECT id, session_id, summary, reason, files_json, keywords_json, created_at
|
|
758
|
+
FROM graveyard
|
|
759
|
+
ORDER BY id DESC
|
|
760
|
+
LIMIT ?
|
|
761
|
+
""",
|
|
762
|
+
(limit,),
|
|
763
|
+
).fetchall()
|
|
764
|
+
|
|
765
|
+
@staticmethod
|
|
766
|
+
def _graveyard_row_to_item(row: sqlite3.Row) -> dict[str, Any]:
|
|
767
|
+
return {
|
|
768
|
+
"id": row["id"],
|
|
769
|
+
"session_id": row["session_id"],
|
|
770
|
+
"summary": row["summary"],
|
|
771
|
+
"reason": row["reason"],
|
|
772
|
+
"files": json.loads(row["files_json"]),
|
|
773
|
+
"keywords": json.loads(row["keywords_json"]),
|
|
774
|
+
"created_at": row["created_at"],
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
@staticmethod
|
|
778
|
+
def _compact_event_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|
779
|
+
compacted = dict(payload)
|
|
780
|
+
meta: dict[str, dict[str, Any]] = {}
|
|
781
|
+
for field in _COMPACT_TEXT_FIELDS:
|
|
782
|
+
value = compacted.get(field)
|
|
783
|
+
if not isinstance(value, str) or len(value) <= _COMPACT_TEXT_MAX_LEN:
|
|
784
|
+
continue
|
|
785
|
+
meta[field] = {
|
|
786
|
+
"original_len": len(value),
|
|
787
|
+
"truncated": True,
|
|
788
|
+
"sha256": hashlib.sha256(value.encode("utf-8")).hexdigest(),
|
|
789
|
+
}
|
|
790
|
+
compacted[field] = value[:_COMPACT_TEXT_MAX_LEN]
|
|
791
|
+
if meta:
|
|
792
|
+
compacted["_payload_compaction"] = meta
|
|
793
|
+
return compacted
|