nexo-brain 2.6.21 → 3.0.0
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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +72 -20
- package/hooks/hooks.json +79 -0
- package/package.json +1 -1
- package/src/agent_runner.py +296 -8
- package/src/cli.py +209 -4
- package/src/client_preferences.py +115 -0
- package/src/client_sync.py +202 -2
- package/src/cognitive/__init__.py +1 -1
- package/src/cognitive/_search.py +39 -19
- package/src/dashboard/app.py +264 -0
- package/src/dashboard/templates/base.html +4 -0
- package/src/dashboard/templates/dashboard.html +59 -1
- package/src/dashboard/templates/protocol.html +199 -0
- package/src/db/__init__.py +23 -1
- package/src/db/_learnings.py +31 -4
- package/src/db/_personal_scripts.py +12 -0
- package/src/db/_protocol.py +303 -0
- package/src/db/_schema.py +248 -0
- package/src/db/_watchers.py +173 -0
- package/src/db/_workflow.py +952 -0
- package/src/doctor/providers/runtime.py +1095 -3
- package/src/evolution_cycle.py +62 -0
- package/src/hook_guardrails.py +308 -0
- package/src/hooks/protocol-guardrail.sh +10 -0
- package/src/nexo_sdk.py +103 -0
- package/src/plugins/cognitive_memory.py +18 -0
- package/src/plugins/cortex.py +55 -35
- package/src/plugins/guard.py +132 -16
- package/src/plugins/protocol.py +911 -0
- package/src/plugins/schedule.py +40 -6
- package/src/plugins/simple_api.py +103 -0
- package/src/plugins/skills.py +67 -0
- package/src/plugins/state_watchers.py +79 -0
- package/src/plugins/workflow.py +588 -0
- package/src/public_contribution.py +86 -12
- package/src/script_registry.py +142 -0
- package/src/scripts/deep-sleep/apply_findings.py +482 -2
- package/src/scripts/deep-sleep/collect.py +49 -4
- package/src/scripts/nexo-agent-run.py +2 -0
- package/src/scripts/nexo-daily-self-audit.py +843 -5
- package/src/scripts/nexo-evolution-run.py +343 -1
- package/src/server.py +92 -6
- package/src/skills_runtime.py +151 -0
- package/src/state_watchers_runtime.py +334 -0
- package/src/tools_learnings.py +345 -7
- package/src/tools_sessions.py +183 -0
- package/templates/CLAUDE.md.template +9 -1
- package/templates/CODEX.AGENTS.md.template +10 -2
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
"""NEXO DB — Protocol discipline runtime."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import secrets
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from db._core import get_db
|
|
9
|
+
|
|
10
|
+
VALID_TASK_TYPES = {"answer", "analyze", "edit", "execute", "delegate"}
|
|
11
|
+
VALID_OUTCOMES = {"open", "done", "partial", "blocked", "failed", "cancelled"}
|
|
12
|
+
VALID_DEBT_STATUS = {"open", "forgiven", "resolved"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _task_id() -> str:
|
|
16
|
+
return f"PT-{int(time.time())}-{secrets.randbelow(100000)}"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _as_json(value) -> str:
|
|
20
|
+
if isinstance(value, str):
|
|
21
|
+
return value
|
|
22
|
+
if value is None:
|
|
23
|
+
value = []
|
|
24
|
+
return json.dumps(value, ensure_ascii=False)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _as_bool(value) -> int:
|
|
28
|
+
return 1 if bool(value) else 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_protocol_task(
|
|
32
|
+
session_id: str,
|
|
33
|
+
goal: str,
|
|
34
|
+
*,
|
|
35
|
+
task_type: str = "answer",
|
|
36
|
+
area: str = "",
|
|
37
|
+
project_hint: str = "",
|
|
38
|
+
context_hint: str = "",
|
|
39
|
+
files=None,
|
|
40
|
+
plan=None,
|
|
41
|
+
known_facts=None,
|
|
42
|
+
unknowns=None,
|
|
43
|
+
constraints=None,
|
|
44
|
+
evidence_refs=None,
|
|
45
|
+
verification_step: str = "",
|
|
46
|
+
cortex_mode: str = "",
|
|
47
|
+
cortex_check_id: str = "",
|
|
48
|
+
cortex_blocked_reason: str = "",
|
|
49
|
+
cortex_warnings=None,
|
|
50
|
+
cortex_rules=None,
|
|
51
|
+
opened_with_guard: bool = False,
|
|
52
|
+
opened_with_rules: bool = False,
|
|
53
|
+
guard_has_blocking: bool = False,
|
|
54
|
+
guard_summary: str = "",
|
|
55
|
+
must_verify: bool = False,
|
|
56
|
+
must_change_log: bool = False,
|
|
57
|
+
must_learning_if_corrected: bool = True,
|
|
58
|
+
must_write_diary_on_close: bool = False,
|
|
59
|
+
response_mode: str = "",
|
|
60
|
+
response_confidence: int = 0,
|
|
61
|
+
response_reasons=None,
|
|
62
|
+
response_high_stakes: bool = False,
|
|
63
|
+
) -> dict:
|
|
64
|
+
conn = get_db()
|
|
65
|
+
task_id = _task_id()
|
|
66
|
+
clean_type = task_type if task_type in VALID_TASK_TYPES else "answer"
|
|
67
|
+
conn.execute(
|
|
68
|
+
"""INSERT INTO protocol_tasks (
|
|
69
|
+
task_id, session_id, goal, task_type, area, project_hint, context_hint,
|
|
70
|
+
files, plan, known_facts, unknowns, constraints, evidence_refs, verification_step,
|
|
71
|
+
cortex_mode, cortex_check_id, cortex_blocked_reason, cortex_warnings, cortex_rules,
|
|
72
|
+
opened_with_guard, opened_with_rules, guard_has_blocking, guard_summary,
|
|
73
|
+
must_verify, must_change_log, must_learning_if_corrected, must_write_diary_on_close,
|
|
74
|
+
response_mode, response_confidence, response_reasons, response_high_stakes
|
|
75
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
76
|
+
(
|
|
77
|
+
task_id,
|
|
78
|
+
session_id.strip(),
|
|
79
|
+
goal.strip(),
|
|
80
|
+
clean_type,
|
|
81
|
+
area.strip(),
|
|
82
|
+
project_hint.strip(),
|
|
83
|
+
context_hint.strip(),
|
|
84
|
+
_as_json(files),
|
|
85
|
+
_as_json(plan),
|
|
86
|
+
_as_json(known_facts),
|
|
87
|
+
_as_json(unknowns),
|
|
88
|
+
_as_json(constraints),
|
|
89
|
+
_as_json(evidence_refs),
|
|
90
|
+
verification_step.strip(),
|
|
91
|
+
cortex_mode.strip(),
|
|
92
|
+
cortex_check_id.strip(),
|
|
93
|
+
cortex_blocked_reason.strip(),
|
|
94
|
+
_as_json(cortex_warnings),
|
|
95
|
+
_as_json(cortex_rules),
|
|
96
|
+
_as_bool(opened_with_guard),
|
|
97
|
+
_as_bool(opened_with_rules),
|
|
98
|
+
_as_bool(guard_has_blocking),
|
|
99
|
+
guard_summary[:4000],
|
|
100
|
+
_as_bool(must_verify),
|
|
101
|
+
_as_bool(must_change_log),
|
|
102
|
+
_as_bool(must_learning_if_corrected),
|
|
103
|
+
_as_bool(must_write_diary_on_close),
|
|
104
|
+
response_mode.strip(),
|
|
105
|
+
max(0, int(response_confidence or 0)),
|
|
106
|
+
_as_json(response_reasons),
|
|
107
|
+
_as_bool(response_high_stakes),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
conn.commit()
|
|
111
|
+
return get_protocol_task(task_id)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_protocol_task(task_id: str) -> dict | None:
|
|
115
|
+
conn = get_db()
|
|
116
|
+
row = conn.execute("SELECT * FROM protocol_tasks WHERE task_id = ?", (task_id,)).fetchone()
|
|
117
|
+
return dict(row) if row else None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def close_protocol_task(
|
|
121
|
+
task_id: str,
|
|
122
|
+
*,
|
|
123
|
+
outcome: str,
|
|
124
|
+
evidence: str = "",
|
|
125
|
+
files_changed=None,
|
|
126
|
+
correction_happened: bool = False,
|
|
127
|
+
change_log_id: int | None = None,
|
|
128
|
+
learning_id: int | None = None,
|
|
129
|
+
followup_id: str = "",
|
|
130
|
+
outcome_notes: str = "",
|
|
131
|
+
) -> dict:
|
|
132
|
+
conn = get_db()
|
|
133
|
+
clean_outcome = outcome if outcome in VALID_OUTCOMES else "failed"
|
|
134
|
+
conn.execute(
|
|
135
|
+
"""UPDATE protocol_tasks
|
|
136
|
+
SET status = ?,
|
|
137
|
+
close_evidence = ?,
|
|
138
|
+
files_changed = ?,
|
|
139
|
+
correction_happened = ?,
|
|
140
|
+
change_log_id = ?,
|
|
141
|
+
learning_id = ?,
|
|
142
|
+
followup_id = ?,
|
|
143
|
+
outcome_notes = ?,
|
|
144
|
+
closed_at = datetime('now')
|
|
145
|
+
WHERE task_id = ?""",
|
|
146
|
+
(
|
|
147
|
+
clean_outcome,
|
|
148
|
+
evidence[:4000],
|
|
149
|
+
_as_json(files_changed),
|
|
150
|
+
_as_bool(correction_happened),
|
|
151
|
+
change_log_id,
|
|
152
|
+
learning_id,
|
|
153
|
+
followup_id[:120],
|
|
154
|
+
outcome_notes[:4000],
|
|
155
|
+
task_id,
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
conn.commit()
|
|
159
|
+
return get_protocol_task(task_id) or {}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def create_protocol_debt(
|
|
163
|
+
session_id: str,
|
|
164
|
+
debt_type: str,
|
|
165
|
+
*,
|
|
166
|
+
severity: str = "warn",
|
|
167
|
+
task_id: str = "",
|
|
168
|
+
evidence: str = "",
|
|
169
|
+
) -> dict:
|
|
170
|
+
conn = get_db()
|
|
171
|
+
cursor = conn.execute(
|
|
172
|
+
"""INSERT INTO protocol_debt (session_id, task_id, debt_type, severity, evidence)
|
|
173
|
+
VALUES (?, ?, ?, ?, ?)""",
|
|
174
|
+
(
|
|
175
|
+
session_id.strip(),
|
|
176
|
+
task_id.strip(),
|
|
177
|
+
debt_type.strip(),
|
|
178
|
+
severity if severity in {"info", "warn", "error"} else "warn",
|
|
179
|
+
evidence[:4000],
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
conn.commit()
|
|
183
|
+
row = conn.execute("SELECT * FROM protocol_debt WHERE id = ?", (cursor.lastrowid,)).fetchone()
|
|
184
|
+
return dict(row)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def resolve_protocol_debts(
|
|
188
|
+
*,
|
|
189
|
+
session_id: str = "",
|
|
190
|
+
task_id: str = "",
|
|
191
|
+
debt_types: list[str] | None = None,
|
|
192
|
+
resolution: str = "",
|
|
193
|
+
) -> int:
|
|
194
|
+
conn = get_db()
|
|
195
|
+
clauses = ["status = 'open'"]
|
|
196
|
+
params: list[str] = []
|
|
197
|
+
if session_id:
|
|
198
|
+
clauses.append("session_id = ?")
|
|
199
|
+
params.append(session_id.strip())
|
|
200
|
+
if task_id:
|
|
201
|
+
clauses.append("task_id = ?")
|
|
202
|
+
params.append(task_id.strip())
|
|
203
|
+
if debt_types:
|
|
204
|
+
placeholders = ",".join("?" * len(debt_types))
|
|
205
|
+
clauses.append(f"debt_type IN ({placeholders})")
|
|
206
|
+
params.extend([item.strip() for item in debt_types if item.strip()])
|
|
207
|
+
where = " AND ".join(clauses)
|
|
208
|
+
cursor = conn.execute(
|
|
209
|
+
f"""UPDATE protocol_debt
|
|
210
|
+
SET status = 'resolved',
|
|
211
|
+
resolution = ?,
|
|
212
|
+
resolved_at = datetime('now')
|
|
213
|
+
WHERE {where}""",
|
|
214
|
+
[resolution[:4000]] + params,
|
|
215
|
+
)
|
|
216
|
+
conn.commit()
|
|
217
|
+
return cursor.rowcount
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def list_protocol_debts(*, status: str = "open", task_id: str = "", limit: int = 50) -> list[dict]:
|
|
221
|
+
conn = get_db()
|
|
222
|
+
clauses = []
|
|
223
|
+
params: list[object] = []
|
|
224
|
+
if status in VALID_DEBT_STATUS:
|
|
225
|
+
clauses.append("status = ?")
|
|
226
|
+
params.append(status)
|
|
227
|
+
if task_id:
|
|
228
|
+
clauses.append("task_id = ?")
|
|
229
|
+
params.append(task_id.strip())
|
|
230
|
+
where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
|
|
231
|
+
rows = conn.execute(
|
|
232
|
+
f"SELECT * FROM protocol_debt {where} ORDER BY created_at DESC LIMIT ?",
|
|
233
|
+
params + [max(1, int(limit))],
|
|
234
|
+
).fetchall()
|
|
235
|
+
return [dict(row) for row in rows]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def protocol_compliance_summary(days: int = 7) -> dict:
|
|
239
|
+
conn = get_db()
|
|
240
|
+
window = f"-{max(1, int(days))} days"
|
|
241
|
+
tasks = conn.execute(
|
|
242
|
+
"""SELECT * FROM protocol_tasks
|
|
243
|
+
WHERE opened_at >= datetime('now', ?)
|
|
244
|
+
ORDER BY opened_at DESC""",
|
|
245
|
+
(window,),
|
|
246
|
+
).fetchall()
|
|
247
|
+
open_debts = conn.execute(
|
|
248
|
+
"""SELECT severity, debt_type, COUNT(*) AS total
|
|
249
|
+
FROM protocol_debt
|
|
250
|
+
WHERE status = 'open' AND created_at >= datetime('now', ?)
|
|
251
|
+
GROUP BY severity, debt_type
|
|
252
|
+
ORDER BY total DESC, debt_type ASC""",
|
|
253
|
+
(window,),
|
|
254
|
+
).fetchall()
|
|
255
|
+
|
|
256
|
+
closed_tasks = [dict(row) for row in tasks if row["status"] != "open"]
|
|
257
|
+
verify_required = [row for row in closed_tasks if row["must_verify"] and row["status"] == "done"]
|
|
258
|
+
verify_ok = [row for row in verify_required if (row.get("close_evidence") or "").strip()]
|
|
259
|
+
change_required = [row for row in closed_tasks if row["must_change_log"]]
|
|
260
|
+
change_ok = [row for row in change_required if row["change_log_id"]]
|
|
261
|
+
learning_required = [row for row in closed_tasks if row["correction_happened"]]
|
|
262
|
+
learning_ok = [row for row in learning_required if row["learning_id"]]
|
|
263
|
+
action_tasks = [row for row in tasks if row["task_type"] in ("edit", "execute", "delegate")]
|
|
264
|
+
cortex_ok = [row for row in action_tasks if row["cortex_mode"] == "act"]
|
|
265
|
+
|
|
266
|
+
score_parts = []
|
|
267
|
+
if verify_required:
|
|
268
|
+
score_parts.append((len(verify_ok) / len(verify_required)) * 100)
|
|
269
|
+
if change_required:
|
|
270
|
+
score_parts.append((len(change_ok) / len(change_required)) * 100)
|
|
271
|
+
if learning_required:
|
|
272
|
+
score_parts.append((len(learning_ok) / len(learning_required)) * 100)
|
|
273
|
+
if action_tasks:
|
|
274
|
+
score_parts.append((len(cortex_ok) / len(action_tasks)) * 100)
|
|
275
|
+
|
|
276
|
+
base_score = (sum(score_parts) / len(score_parts)) if score_parts else (100.0 if tasks else 0.0)
|
|
277
|
+
warn_debt = sum(row["total"] for row in open_debts if row["severity"] == "warn")
|
|
278
|
+
error_debt = sum(row["total"] for row in open_debts if row["severity"] == "error")
|
|
279
|
+
debt_penalty = min(60, (warn_debt * 5) + (error_debt * 20))
|
|
280
|
+
overall = max(0.0, round(base_score - debt_penalty, 1))
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
"days": max(1, int(days)),
|
|
284
|
+
"tasks_total": len(tasks),
|
|
285
|
+
"tasks_closed": len(closed_tasks),
|
|
286
|
+
"verify_required": len(verify_required),
|
|
287
|
+
"verify_ok": len(verify_ok),
|
|
288
|
+
"verify_pct": round((len(verify_ok) / len(verify_required)) * 100, 1) if verify_required else None,
|
|
289
|
+
"change_required": len(change_required),
|
|
290
|
+
"change_ok": len(change_ok),
|
|
291
|
+
"change_pct": round((len(change_ok) / len(change_required)) * 100, 1) if change_required else None,
|
|
292
|
+
"learning_required": len(learning_required),
|
|
293
|
+
"learning_ok": len(learning_ok),
|
|
294
|
+
"learning_pct": round((len(learning_ok) / len(learning_required)) * 100, 1) if learning_required else None,
|
|
295
|
+
"action_tasks": len(action_tasks),
|
|
296
|
+
"cortex_ok": len(cortex_ok),
|
|
297
|
+
"cortex_pct": round((len(cortex_ok) / len(action_tasks)) * 100, 1) if action_tasks else None,
|
|
298
|
+
"open_debt_total": warn_debt + error_debt,
|
|
299
|
+
"open_warn_debt": warn_debt,
|
|
300
|
+
"open_error_debt": error_debt,
|
|
301
|
+
"open_debt_breakdown": [dict(row) for row in open_debts],
|
|
302
|
+
"overall_compliance_pct": overall,
|
|
303
|
+
}
|
package/src/db/_schema.py
CHANGED
|
@@ -433,6 +433,247 @@ def _m20_personal_scripts_registry(conn):
|
|
|
433
433
|
_migrate_add_index(conn, "idx_personal_script_schedules_enabled", "personal_script_schedules", "enabled")
|
|
434
434
|
|
|
435
435
|
|
|
436
|
+
def _m22_protocol_discipline_tables(conn):
|
|
437
|
+
"""Protocol discipline runtime: persistent task contracts + debt tracking."""
|
|
438
|
+
conn.execute("""
|
|
439
|
+
CREATE TABLE IF NOT EXISTS protocol_tasks (
|
|
440
|
+
task_id TEXT PRIMARY KEY,
|
|
441
|
+
session_id TEXT NOT NULL,
|
|
442
|
+
goal TEXT NOT NULL,
|
|
443
|
+
task_type TEXT NOT NULL DEFAULT 'answer',
|
|
444
|
+
area TEXT DEFAULT '',
|
|
445
|
+
project_hint TEXT DEFAULT '',
|
|
446
|
+
context_hint TEXT DEFAULT '',
|
|
447
|
+
files TEXT DEFAULT '[]',
|
|
448
|
+
plan TEXT DEFAULT '[]',
|
|
449
|
+
known_facts TEXT DEFAULT '[]',
|
|
450
|
+
unknowns TEXT DEFAULT '[]',
|
|
451
|
+
constraints TEXT DEFAULT '[]',
|
|
452
|
+
evidence_refs TEXT DEFAULT '[]',
|
|
453
|
+
verification_step TEXT DEFAULT '',
|
|
454
|
+
cortex_mode TEXT DEFAULT '',
|
|
455
|
+
cortex_check_id TEXT DEFAULT '',
|
|
456
|
+
cortex_blocked_reason TEXT DEFAULT '',
|
|
457
|
+
cortex_warnings TEXT DEFAULT '[]',
|
|
458
|
+
cortex_rules TEXT DEFAULT '[]',
|
|
459
|
+
opened_with_guard INTEGER NOT NULL DEFAULT 0,
|
|
460
|
+
opened_with_rules INTEGER NOT NULL DEFAULT 0,
|
|
461
|
+
guard_has_blocking INTEGER NOT NULL DEFAULT 0,
|
|
462
|
+
guard_summary TEXT DEFAULT '',
|
|
463
|
+
must_verify INTEGER NOT NULL DEFAULT 0,
|
|
464
|
+
must_change_log INTEGER NOT NULL DEFAULT 0,
|
|
465
|
+
must_learning_if_corrected INTEGER NOT NULL DEFAULT 1,
|
|
466
|
+
must_write_diary_on_close INTEGER NOT NULL DEFAULT 0,
|
|
467
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
468
|
+
close_evidence TEXT DEFAULT '',
|
|
469
|
+
files_changed TEXT DEFAULT '[]',
|
|
470
|
+
correction_happened INTEGER NOT NULL DEFAULT 0,
|
|
471
|
+
change_log_id INTEGER,
|
|
472
|
+
learning_id INTEGER,
|
|
473
|
+
followup_id TEXT DEFAULT '',
|
|
474
|
+
outcome_notes TEXT DEFAULT '',
|
|
475
|
+
opened_at TEXT DEFAULT (datetime('now')),
|
|
476
|
+
closed_at TEXT DEFAULT NULL
|
|
477
|
+
)
|
|
478
|
+
""")
|
|
479
|
+
conn.execute("""
|
|
480
|
+
CREATE TABLE IF NOT EXISTS protocol_debt (
|
|
481
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
482
|
+
session_id TEXT DEFAULT '',
|
|
483
|
+
task_id TEXT DEFAULT '',
|
|
484
|
+
debt_type TEXT NOT NULL,
|
|
485
|
+
severity TEXT NOT NULL DEFAULT 'warn',
|
|
486
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
487
|
+
evidence TEXT DEFAULT '',
|
|
488
|
+
resolution TEXT DEFAULT '',
|
|
489
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
490
|
+
resolved_at TEXT DEFAULT NULL
|
|
491
|
+
)
|
|
492
|
+
""")
|
|
493
|
+
_migrate_add_index(conn, "idx_protocol_tasks_session", "protocol_tasks", "session_id")
|
|
494
|
+
_migrate_add_index(conn, "idx_protocol_tasks_status", "protocol_tasks", "status")
|
|
495
|
+
_migrate_add_index(conn, "idx_protocol_tasks_opened", "protocol_tasks", "opened_at")
|
|
496
|
+
_migrate_add_index(conn, "idx_protocol_debt_session", "protocol_debt", "session_id")
|
|
497
|
+
_migrate_add_index(conn, "idx_protocol_debt_task", "protocol_debt", "task_id")
|
|
498
|
+
_migrate_add_index(conn, "idx_protocol_debt_status", "protocol_debt", "status")
|
|
499
|
+
_migrate_add_index(conn, "idx_protocol_debt_created", "protocol_debt", "created_at")
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _m23_learning_superseded_lifecycle(conn):
|
|
503
|
+
"""Track canonical learning replacement instead of leaving rule drift implicit."""
|
|
504
|
+
_migrate_add_column(conn, "learnings", "supersedes_id", "INTEGER")
|
|
505
|
+
_migrate_add_index(conn, "idx_learnings_supersedes", "learnings", "supersedes_id")
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _m24_durable_workflow_runtime(conn):
|
|
509
|
+
"""Durable workflow execution runtime for long multi-step tasks."""
|
|
510
|
+
conn.execute("""
|
|
511
|
+
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
512
|
+
run_id TEXT PRIMARY KEY,
|
|
513
|
+
session_id TEXT DEFAULT '',
|
|
514
|
+
protocol_task_id TEXT DEFAULT '',
|
|
515
|
+
goal TEXT NOT NULL,
|
|
516
|
+
workflow_kind TEXT DEFAULT 'general',
|
|
517
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
518
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
519
|
+
idempotency_key TEXT DEFAULT '',
|
|
520
|
+
shared_state TEXT DEFAULT '{}',
|
|
521
|
+
next_action TEXT DEFAULT '',
|
|
522
|
+
current_step_key TEXT DEFAULT '',
|
|
523
|
+
last_checkpoint_label TEXT DEFAULT '',
|
|
524
|
+
owner TEXT DEFAULT '',
|
|
525
|
+
opened_at TEXT DEFAULT (datetime('now')),
|
|
526
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
527
|
+
closed_at TEXT DEFAULT NULL
|
|
528
|
+
)
|
|
529
|
+
""")
|
|
530
|
+
conn.execute("""
|
|
531
|
+
CREATE TABLE IF NOT EXISTS workflow_steps (
|
|
532
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
533
|
+
run_id TEXT NOT NULL,
|
|
534
|
+
step_key TEXT NOT NULL,
|
|
535
|
+
title TEXT NOT NULL DEFAULT '',
|
|
536
|
+
step_index INTEGER NOT NULL DEFAULT 999,
|
|
537
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
538
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
539
|
+
max_retries INTEGER NOT NULL DEFAULT 0,
|
|
540
|
+
retry_policy TEXT DEFAULT '',
|
|
541
|
+
retry_after TEXT DEFAULT '',
|
|
542
|
+
human_gate INTEGER NOT NULL DEFAULT 0,
|
|
543
|
+
requires_approval INTEGER NOT NULL DEFAULT 0,
|
|
544
|
+
compensation TEXT DEFAULT '',
|
|
545
|
+
last_summary TEXT DEFAULT '',
|
|
546
|
+
last_evidence TEXT DEFAULT '',
|
|
547
|
+
last_state_patch TEXT DEFAULT '{}',
|
|
548
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
549
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
550
|
+
started_at TEXT DEFAULT NULL,
|
|
551
|
+
completed_at TEXT DEFAULT NULL,
|
|
552
|
+
UNIQUE(run_id, step_key)
|
|
553
|
+
)
|
|
554
|
+
""")
|
|
555
|
+
conn.execute("""
|
|
556
|
+
CREATE TABLE IF NOT EXISTS workflow_checkpoints (
|
|
557
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
558
|
+
run_id TEXT NOT NULL,
|
|
559
|
+
step_key TEXT DEFAULT '',
|
|
560
|
+
checkpoint_label TEXT NOT NULL DEFAULT 'checkpoint',
|
|
561
|
+
run_status TEXT DEFAULT '',
|
|
562
|
+
step_status TEXT DEFAULT '',
|
|
563
|
+
summary TEXT DEFAULT '',
|
|
564
|
+
shared_state TEXT DEFAULT '{}',
|
|
565
|
+
state_patch TEXT DEFAULT '{}',
|
|
566
|
+
evidence TEXT DEFAULT '',
|
|
567
|
+
next_action TEXT DEFAULT '',
|
|
568
|
+
retry_after TEXT DEFAULT '',
|
|
569
|
+
requires_approval INTEGER NOT NULL DEFAULT 0,
|
|
570
|
+
compensation_note TEXT DEFAULT '',
|
|
571
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
572
|
+
actor TEXT DEFAULT '',
|
|
573
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
574
|
+
)
|
|
575
|
+
""")
|
|
576
|
+
_migrate_add_index(conn, "idx_workflow_runs_session", "workflow_runs", "session_id")
|
|
577
|
+
_migrate_add_index(conn, "idx_workflow_runs_status", "workflow_runs", "status")
|
|
578
|
+
_migrate_add_index(conn, "idx_workflow_runs_updated", "workflow_runs", "updated_at")
|
|
579
|
+
_migrate_add_index(conn, "idx_workflow_runs_protocol_task", "workflow_runs", "protocol_task_id")
|
|
580
|
+
_migrate_add_index(conn, "idx_workflow_runs_idempotency", "workflow_runs", "idempotency_key")
|
|
581
|
+
_migrate_add_index(conn, "idx_workflow_steps_run", "workflow_steps", "run_id")
|
|
582
|
+
_migrate_add_index(conn, "idx_workflow_steps_status", "workflow_steps", "status")
|
|
583
|
+
_migrate_add_index(conn, "idx_workflow_checkpoints_run", "workflow_checkpoints", "run_id")
|
|
584
|
+
_migrate_add_index(conn, "idx_workflow_checkpoints_created", "workflow_checkpoints", "created_at")
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _m25_workflow_goal_stack(conn):
|
|
588
|
+
"""Durable goal stack linked to workflows."""
|
|
589
|
+
conn.execute("""
|
|
590
|
+
CREATE TABLE IF NOT EXISTS workflow_goals (
|
|
591
|
+
goal_id TEXT PRIMARY KEY,
|
|
592
|
+
session_id TEXT DEFAULT '',
|
|
593
|
+
title TEXT NOT NULL,
|
|
594
|
+
objective TEXT DEFAULT '',
|
|
595
|
+
parent_goal_id TEXT DEFAULT '',
|
|
596
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
597
|
+
priority TEXT NOT NULL DEFAULT 'normal',
|
|
598
|
+
owner TEXT DEFAULT '',
|
|
599
|
+
next_action TEXT DEFAULT '',
|
|
600
|
+
success_signal TEXT DEFAULT '',
|
|
601
|
+
blocker_reason TEXT DEFAULT '',
|
|
602
|
+
shared_state TEXT DEFAULT '{}',
|
|
603
|
+
opened_at TEXT DEFAULT (datetime('now')),
|
|
604
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
605
|
+
closed_at TEXT DEFAULT NULL
|
|
606
|
+
)
|
|
607
|
+
""")
|
|
608
|
+
_migrate_add_column(conn, "workflow_runs", "goal_id", "TEXT DEFAULT ''")
|
|
609
|
+
_migrate_add_index(conn, "idx_workflow_goals_status", "workflow_goals", "status")
|
|
610
|
+
_migrate_add_index(conn, "idx_workflow_goals_parent", "workflow_goals", "parent_goal_id")
|
|
611
|
+
_migrate_add_index(conn, "idx_workflow_goals_updated", "workflow_goals", "updated_at")
|
|
612
|
+
_migrate_add_index(conn, "idx_workflow_goals_session", "workflow_goals", "session_id")
|
|
613
|
+
_migrate_add_index(conn, "idx_workflow_runs_goal", "workflow_runs", "goal_id")
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _m26_protocol_answer_confidence(conn):
|
|
617
|
+
"""Persist answer/analyze response mode so discipline survives the prompt."""
|
|
618
|
+
_migrate_add_column(conn, "protocol_tasks", "response_mode", "TEXT DEFAULT ''")
|
|
619
|
+
_migrate_add_column(conn, "protocol_tasks", "response_confidence", "INTEGER DEFAULT 0")
|
|
620
|
+
_migrate_add_column(conn, "protocol_tasks", "response_reasons", "TEXT DEFAULT '[]'")
|
|
621
|
+
_migrate_add_column(conn, "protocol_tasks", "response_high_stakes", "INTEGER DEFAULT 0")
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def _m27_state_watchers(conn):
|
|
625
|
+
"""Persistent state watchers for drift, health, and expiry tracking."""
|
|
626
|
+
conn.execute("""
|
|
627
|
+
CREATE TABLE IF NOT EXISTS state_watchers (
|
|
628
|
+
watcher_id TEXT PRIMARY KEY,
|
|
629
|
+
watcher_type TEXT NOT NULL,
|
|
630
|
+
title TEXT NOT NULL,
|
|
631
|
+
target TEXT DEFAULT '',
|
|
632
|
+
severity TEXT NOT NULL DEFAULT 'warn',
|
|
633
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
634
|
+
config TEXT DEFAULT '{}',
|
|
635
|
+
last_health TEXT NOT NULL DEFAULT 'unknown',
|
|
636
|
+
last_result TEXT DEFAULT '{}',
|
|
637
|
+
last_checked_at TEXT DEFAULT '',
|
|
638
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
639
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
640
|
+
)
|
|
641
|
+
""")
|
|
642
|
+
_migrate_add_index(conn, "idx_state_watchers_type", "state_watchers", "watcher_type")
|
|
643
|
+
_migrate_add_index(conn, "idx_state_watchers_status", "state_watchers", "status")
|
|
644
|
+
_migrate_add_index(conn, "idx_state_watchers_health", "state_watchers", "last_health")
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _m28_automation_runs(conn):
|
|
648
|
+
"""Persist automation-backend telemetry for parity, degraded-mode audits, and cost metrics."""
|
|
649
|
+
conn.execute("""
|
|
650
|
+
CREATE TABLE IF NOT EXISTS automation_runs (
|
|
651
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
652
|
+
backend TEXT NOT NULL,
|
|
653
|
+
task_profile TEXT DEFAULT 'default',
|
|
654
|
+
model TEXT DEFAULT '',
|
|
655
|
+
reasoning_effort TEXT DEFAULT '',
|
|
656
|
+
cwd TEXT DEFAULT '',
|
|
657
|
+
output_format TEXT DEFAULT 'text',
|
|
658
|
+
prompt_chars INTEGER DEFAULT 0,
|
|
659
|
+
returncode INTEGER DEFAULT 0,
|
|
660
|
+
duration_ms INTEGER DEFAULT 0,
|
|
661
|
+
input_tokens INTEGER DEFAULT 0,
|
|
662
|
+
cached_input_tokens INTEGER DEFAULT 0,
|
|
663
|
+
output_tokens INTEGER DEFAULT 0,
|
|
664
|
+
total_cost_usd REAL,
|
|
665
|
+
telemetry_source TEXT DEFAULT '',
|
|
666
|
+
cost_source TEXT DEFAULT '',
|
|
667
|
+
status TEXT DEFAULT 'ok',
|
|
668
|
+
metadata TEXT DEFAULT '{}',
|
|
669
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
670
|
+
)
|
|
671
|
+
""")
|
|
672
|
+
_migrate_add_index(conn, "idx_automation_runs_backend", "automation_runs", "backend")
|
|
673
|
+
_migrate_add_index(conn, "idx_automation_runs_created", "automation_runs", "created_at")
|
|
674
|
+
_migrate_add_index(conn, "idx_automation_runs_status", "automation_runs", "status")
|
|
675
|
+
|
|
676
|
+
|
|
436
677
|
MIGRATIONS = [
|
|
437
678
|
(1, "learnings_columns", _m1_learnings_columns),
|
|
438
679
|
(2, "followups_reasoning", _m2_followups_reasoning),
|
|
@@ -455,6 +696,13 @@ MIGRATIONS = [
|
|
|
455
696
|
(19, "skills_v2", _m19_skills_v2),
|
|
456
697
|
(20, "personal_scripts_registry", _m20_personal_scripts_registry),
|
|
457
698
|
(21, "external_session_fields", _m21_external_session_fields),
|
|
699
|
+
(22, "protocol_discipline_tables", _m22_protocol_discipline_tables),
|
|
700
|
+
(23, "learning_superseded_lifecycle", _m23_learning_superseded_lifecycle),
|
|
701
|
+
(24, "durable_workflow_runtime", _m24_durable_workflow_runtime),
|
|
702
|
+
(25, "workflow_goal_stack", _m25_workflow_goal_stack),
|
|
703
|
+
(26, "protocol_answer_confidence", _m26_protocol_answer_confidence),
|
|
704
|
+
(27, "state_watchers", _m27_state_watchers),
|
|
705
|
+
(28, "automation_runs", _m28_automation_runs),
|
|
458
706
|
]
|
|
459
707
|
|
|
460
708
|
|