nexo-brain 0.2.1 → 0.3.2
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/README.md +158 -72
- package/bin/nexo-brain 2.js +610 -0
- package/package.json +2 -2
- package/scripts/pre-commit-check 2.sh +55 -0
- package/src/cognitive.py +1582 -56
- package/src/db.py +49 -25
- package/src/hooks/auto_capture.py +208 -0
- package/src/plugins/cognitive_memory.py +276 -17
- package/src/scripts/nexo-catchup.py +32 -15
- package/src/scripts/nexo-cognitive-decay.py +2 -4
- package/src/scripts/nexo-daily-self-audit.py +148 -29
- package/src/scripts/nexo-immune.py +869 -0
- package/src/scripts/nexo-postmortem-consolidator.py +42 -40
- package/src/scripts/nexo-sleep.py +90 -39
- package/src/scripts/nexo-synthesis.py +78 -76
- package/src/tools_sessions.py +2 -2
- package/templates/CLAUDE.md 2.template +89 -0
- package/templates/CLAUDE.md.template +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
NEXO Synthesis Engine — Daily intelligence brief.
|
|
4
4
|
|
|
5
5
|
Runs every 2 hours via LaunchAgent. Executes ONCE per day (internal gate).
|
|
6
|
-
Queries nexo.db + claude-mem.db and writes
|
|
6
|
+
Queries nexo.db + claude-mem.db and writes ~/claude/coordination/daily-synthesis.md
|
|
7
7
|
|
|
8
8
|
Zero external dependencies beyond stdlib + sqlite3.
|
|
9
9
|
"""
|
|
@@ -17,12 +17,13 @@ from collections import Counter, defaultdict
|
|
|
17
17
|
from datetime import datetime, date, timedelta
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
|
-
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
21
|
-
|
|
22
20
|
# ─── Paths ────────────────────────────────────────────────────────────────────
|
|
23
|
-
|
|
21
|
+
HOME = Path.home()
|
|
22
|
+
CLAUDE_DIR = HOME / "claude"
|
|
23
|
+
COORD_DIR = CLAUDE_DIR / "coordination"
|
|
24
24
|
|
|
25
|
-
NEXO_DB =
|
|
25
|
+
NEXO_DB = HOME / "claude" / "nexo-mcp" / "nexo.db"
|
|
26
|
+
CLAUDE_MEM_DB = HOME / ".claude-mem" / "claude-mem.db"
|
|
26
27
|
|
|
27
28
|
OUTPUT_FILE = COORD_DIR / "daily-synthesis.md"
|
|
28
29
|
SYNTHESIS_LOG = COORD_DIR / "synthesis-log.json"
|
|
@@ -108,7 +109,7 @@ def section_learnings() -> str:
|
|
|
108
109
|
(TODAY_STR,),
|
|
109
110
|
)
|
|
110
111
|
if not rows:
|
|
111
|
-
return "
|
|
112
|
+
return "Sin errores nuevos registrados."
|
|
112
113
|
|
|
113
114
|
lines = []
|
|
114
115
|
for r in rows:
|
|
@@ -128,7 +129,7 @@ def section_decisions() -> str:
|
|
|
128
129
|
(TODAY_STR,),
|
|
129
130
|
)
|
|
130
131
|
if not rows:
|
|
131
|
-
return "
|
|
132
|
+
return "Sin decisiones registradas."
|
|
132
133
|
|
|
133
134
|
lines = []
|
|
134
135
|
for r in rows:
|
|
@@ -138,13 +139,13 @@ def section_decisions() -> str:
|
|
|
138
139
|
why = truncate(r.get("based_on") or "", 120)
|
|
139
140
|
outcome = r.get("outcome") or ""
|
|
140
141
|
|
|
141
|
-
line = f"- **[{domain}]**
|
|
142
|
+
line = f"- **[{domain}]** Elegido: {chosen}"
|
|
142
143
|
if discarded:
|
|
143
|
-
line += f"\n
|
|
144
|
+
line += f"\n Descartado: {discarded}"
|
|
144
145
|
if why:
|
|
145
|
-
line += f"\n
|
|
146
|
+
line += f"\n Por: {why}"
|
|
146
147
|
if outcome:
|
|
147
|
-
line += f"\n
|
|
148
|
+
line += f"\n Resultado: {truncate(outcome, 100)}"
|
|
148
149
|
lines.append(line)
|
|
149
150
|
return "\n".join(lines)
|
|
150
151
|
|
|
@@ -157,7 +158,7 @@ def section_changes() -> str:
|
|
|
157
158
|
(TODAY_STR,),
|
|
158
159
|
)
|
|
159
160
|
if not rows:
|
|
160
|
-
return "
|
|
161
|
+
return "Sin cambios de código registrados."
|
|
161
162
|
|
|
162
163
|
# Group by "system" (first part of first file path)
|
|
163
164
|
by_system = defaultdict(list)
|
|
@@ -171,13 +172,13 @@ def section_changes() -> str:
|
|
|
171
172
|
|
|
172
173
|
lines = []
|
|
173
174
|
for system, entries in by_system.items():
|
|
174
|
-
lines.append(f"**{system}** ({len(entries)}
|
|
175
|
+
lines.append(f"**{system}** ({len(entries)} cambio{'s' if len(entries) > 1 else ''}):")
|
|
175
176
|
for r in entries[:3]: # cap per system
|
|
176
177
|
what = truncate(r.get("what_changed") or "", 160)
|
|
177
178
|
risks = truncate(r.get("risks") or "", 100)
|
|
178
179
|
lines.append(f" - {what}")
|
|
179
180
|
if risks:
|
|
180
|
-
lines.append(f"
|
|
181
|
+
lines.append(f" ⚠ Riesgos: {risks}")
|
|
181
182
|
return "\n".join(lines)
|
|
182
183
|
|
|
183
184
|
|
|
@@ -200,7 +201,7 @@ def section_patterns() -> str:
|
|
|
200
201
|
total_changes = len(change_rows)
|
|
201
202
|
|
|
202
203
|
if total_learn < 3 and total_changes < 3:
|
|
203
|
-
return "
|
|
204
|
+
return "Datos insuficientes para análisis de patrones (< 7 días)."
|
|
204
205
|
|
|
205
206
|
lines = []
|
|
206
207
|
|
|
@@ -208,9 +209,9 @@ def section_patterns() -> str:
|
|
|
208
209
|
if learn_rows:
|
|
209
210
|
cat_counter = Counter(r.get("category") or "general" for r in learn_rows)
|
|
210
211
|
top_cats = cat_counter.most_common(3)
|
|
211
|
-
lines.append(f"
|
|
212
|
+
lines.append(f"**Áreas con más errores** (últimos 7d, {total_learn} learnings):")
|
|
212
213
|
for cat, count in top_cats:
|
|
213
|
-
lines.append(f" - {cat}: {count}
|
|
214
|
+
lines.append(f" - {cat}: {count} {'error' if count == 1 else 'errores'}")
|
|
214
215
|
|
|
215
216
|
# Systems most touched in change_log
|
|
216
217
|
if change_rows:
|
|
@@ -223,9 +224,9 @@ def section_patterns() -> str:
|
|
|
223
224
|
if parts:
|
|
224
225
|
sys_counter[parts[0]] += 1
|
|
225
226
|
top_sys = sys_counter.most_common(3)
|
|
226
|
-
lines.append(f"**
|
|
227
|
+
lines.append(f"**Sistemas más tocados** (últimos 7d, {total_changes} cambios):")
|
|
227
228
|
for sys_name, count in top_sys:
|
|
228
|
-
lines.append(f" - {sys_name}: {count}
|
|
229
|
+
lines.append(f" - {sys_name}: {count} {'modificación' if count == 1 else 'modificaciones'}")
|
|
229
230
|
|
|
230
231
|
# Recurring error patterns — categories with learnings on 3+ different days
|
|
231
232
|
if learn_rows:
|
|
@@ -241,14 +242,14 @@ def section_patterns() -> str:
|
|
|
241
242
|
cat_days = Counter(r.get("category") or "general" for r in daily_cats)
|
|
242
243
|
recurring = [(c, d) for c, d in cat_days.items() if d >= 3]
|
|
243
244
|
if recurring:
|
|
244
|
-
lines.append("**
|
|
245
|
+
lines.append("**Categorías con errores recurrentes** (3+ días distintos):")
|
|
245
246
|
for cat, days in sorted(recurring, key=lambda x: -x[1]):
|
|
246
|
-
lines.append(f" - {cat}:
|
|
247
|
+
lines.append(f" - {cat}: errores en {days} días — punto débil")
|
|
247
248
|
|
|
248
|
-
return "\n".join(lines) if lines else "
|
|
249
|
+
return "\n".join(lines) if lines else "Sin patrones significativos detectados."
|
|
249
250
|
|
|
250
251
|
|
|
251
|
-
def
|
|
252
|
+
def section_manana() -> str:
|
|
252
253
|
lines = []
|
|
253
254
|
|
|
254
255
|
# Reminders due <= tomorrow, PENDIENTE
|
|
@@ -260,15 +261,15 @@ def section_tomorrow() -> str:
|
|
|
260
261
|
(TOMORROW,),
|
|
261
262
|
)
|
|
262
263
|
if rem_rows:
|
|
263
|
-
lines.append("###
|
|
264
|
+
lines.append("### Recordatorios vencidos/mañana")
|
|
264
265
|
for r in rem_rows:
|
|
265
266
|
d = r.get("date") or ""
|
|
266
267
|
cat = r.get("category") or ""
|
|
267
268
|
desc = truncate(r.get("description") or "", 150)
|
|
268
|
-
overdue = "
|
|
269
|
+
overdue = " ⚠ VENCIDO" if d and d < TODAY_STR else ""
|
|
269
270
|
lines.append(f"- [{d}]{overdue} {desc}" + (f" ({cat})" if cat else ""))
|
|
270
271
|
else:
|
|
271
|
-
lines.append("###
|
|
272
|
+
lines.append("### Recordatorios\nNinguno vencido ni para mañana.")
|
|
272
273
|
|
|
273
274
|
# Followups due <= tomorrow, PENDIENTE
|
|
274
275
|
fol_rows = safe_query(
|
|
@@ -279,14 +280,14 @@ def section_tomorrow() -> str:
|
|
|
279
280
|
(TOMORROW,),
|
|
280
281
|
)
|
|
281
282
|
if fol_rows:
|
|
282
|
-
lines.append("###
|
|
283
|
+
lines.append("### Followups vencidos/mañana")
|
|
283
284
|
for r in fol_rows:
|
|
284
285
|
d = r.get("date") or ""
|
|
285
286
|
desc = truncate(r.get("description") or "", 150)
|
|
286
|
-
overdue = "
|
|
287
|
+
overdue = " ⚠ VENCIDO" if d and d < TODAY_STR else ""
|
|
287
288
|
lines.append(f"- [{d}]{overdue} {desc}")
|
|
288
289
|
else:
|
|
289
|
-
lines.append("### Followups\
|
|
290
|
+
lines.append("### Followups\nNinguno vencido ni para mañana.")
|
|
290
291
|
|
|
291
292
|
# Last 3 session diary entries — pending + next_session_context
|
|
292
293
|
diary_rows = safe_query(
|
|
@@ -295,7 +296,7 @@ def section_tomorrow() -> str:
|
|
|
295
296
|
"ORDER BY created_at DESC LIMIT 3",
|
|
296
297
|
)
|
|
297
298
|
if diary_rows:
|
|
298
|
-
lines.append("###
|
|
299
|
+
lines.append("### Contexto activo (últimas sesiones)")
|
|
299
300
|
for r in diary_rows:
|
|
300
301
|
domain = r.get("domain") or "general"
|
|
301
302
|
pending = truncate(r.get("pending") or "", 200)
|
|
@@ -304,14 +305,14 @@ def section_tomorrow() -> str:
|
|
|
304
305
|
if pending or nxt:
|
|
305
306
|
lines.append(f"**[{domain}]** ({ts[:16]}):")
|
|
306
307
|
if pending:
|
|
307
|
-
lines.append(f"
|
|
308
|
+
lines.append(f" Pendiente: {pending}")
|
|
308
309
|
if nxt:
|
|
309
|
-
lines.append(f"
|
|
310
|
+
lines.append(f" Para la próxima: {nxt}")
|
|
310
311
|
|
|
311
|
-
return "\n".join(lines) if lines else "
|
|
312
|
+
return "\n".join(lines) if lines else "Sin elementos para mañana."
|
|
312
313
|
|
|
313
314
|
|
|
314
|
-
def
|
|
315
|
+
def section_autoevaluacion() -> str:
|
|
315
316
|
diary_rows = safe_query(
|
|
316
317
|
NEXO_DB,
|
|
317
318
|
"SELECT mental_state, user_signals, self_critique, summary, created_at FROM session_diary "
|
|
@@ -319,22 +320,22 @@ def section_self_evaluation() -> str:
|
|
|
319
320
|
(TODAY_STR,),
|
|
320
321
|
)
|
|
321
322
|
if not diary_rows:
|
|
322
|
-
return "
|
|
323
|
+
return "Sin diarios de sesión registrados hoy."
|
|
323
324
|
|
|
324
325
|
lines = []
|
|
325
326
|
|
|
326
|
-
# Self-critique section
|
|
327
|
+
# Self-critique section (NEW — most important)
|
|
327
328
|
all_critiques = []
|
|
328
329
|
for r in diary_rows:
|
|
329
330
|
sc = r.get("self_critique") or ""
|
|
330
|
-
if sc.strip() and not sc.strip().lower().startswith("
|
|
331
|
+
if sc.strip() and not sc.strip().lower().startswith("sin autocrítica"):
|
|
331
332
|
all_critiques.append(truncate(sc, 300))
|
|
332
333
|
|
|
333
334
|
if all_critiques:
|
|
334
|
-
lines.append(f"**
|
|
335
|
+
lines.append(f"**AUTOCRÍTICAS ({len(all_critiques)} sesiones con fallos detectados):**")
|
|
335
336
|
for c in all_critiques[:5]:
|
|
336
337
|
lines.append(f" - {c}")
|
|
337
|
-
lines.append("**
|
|
338
|
+
lines.append("**ACCIÓN:** Estas autocríticas deben informar el comportamiento de mañana. Si un patrón se repite 3+ días, el consolidador nocturno lo promoverá a memoria permanente.")
|
|
338
339
|
lines.append("")
|
|
339
340
|
|
|
340
341
|
# user_signals patterns
|
|
@@ -348,47 +349,46 @@ def section_self_evaluation() -> str:
|
|
|
348
349
|
if ms.strip():
|
|
349
350
|
mental_states.append(truncate(ms, 200))
|
|
350
351
|
|
|
351
|
-
if all_signals:
|
|
352
|
-
|
|
353
|
-
lines.append(f"**User Signals:**\n{signals_text}")
|
|
352
|
+
if user_signals_text := "\n".join(f" - {s}" for s in all_signals[:3] if s):
|
|
353
|
+
lines.append(f"**User signals:**\n{user_signals_text}")
|
|
354
354
|
|
|
355
355
|
if mental_states:
|
|
356
|
-
lines.append(f"**
|
|
356
|
+
lines.append(f"**Estado mental de sesiones:**")
|
|
357
357
|
for ms in mental_states[:2]:
|
|
358
358
|
lines.append(f" - {ms}")
|
|
359
359
|
|
|
360
360
|
# Derive what to do differently based on signal analysis
|
|
361
361
|
if all_signals:
|
|
362
362
|
# Detect repeated corrections
|
|
363
|
-
correction_words = ["
|
|
364
|
-
"
|
|
365
|
-
"
|
|
363
|
+
correction_words = ["corrig", "frustrad", "no lo entiend", "exig", "repet",
|
|
364
|
+
"no debería", "por qué no", "otra vez", "cansando",
|
|
365
|
+
"siempre espera", "reactivo", "no te adelant"]
|
|
366
366
|
correction_count = sum(
|
|
367
367
|
1 for s in all_signals
|
|
368
368
|
if any(w in s.lower() for w in correction_words)
|
|
369
369
|
)
|
|
370
370
|
if correction_count >= 2:
|
|
371
|
-
lines.append(f"**
|
|
372
|
-
lines.append("**
|
|
371
|
+
lines.append(f"**ALERTA:** El usuario corrigió {correction_count} veces hoy — revisar qué se está repitiendo.")
|
|
372
|
+
lines.append("**Para mañana:** Revisar señales anteriores antes de actuar.")
|
|
373
373
|
elif not diary_rows:
|
|
374
|
-
lines.append("**
|
|
374
|
+
lines.append("**Para mañana:** Recordar escribir diario al cerrar sesión.")
|
|
375
375
|
|
|
376
376
|
# Check for postmortem daily summary
|
|
377
377
|
postmortem_file = COORD_DIR / "postmortem-daily.md"
|
|
378
378
|
if postmortem_file.exists():
|
|
379
379
|
pm_content = postmortem_file.read_text().strip()
|
|
380
|
-
if "
|
|
380
|
+
if "Promovido a memoria permanente" in pm_content:
|
|
381
381
|
lines.append("")
|
|
382
|
-
lines.append("**
|
|
382
|
+
lines.append("**REGLAS NUEVAS PERMANENTES (generadas anoche por el consolidador):**")
|
|
383
383
|
for line in pm_content.split("\n"):
|
|
384
|
-
if line.startswith("- ") and "
|
|
384
|
+
if line.startswith("- ") and "Promovido" not in line:
|
|
385
385
|
lines.append(f" {line}")
|
|
386
386
|
|
|
387
|
-
return "\n".join(lines) if lines else "
|
|
387
|
+
return "\n".join(lines) if lines else "Sin datos de auto-evaluación."
|
|
388
388
|
|
|
389
389
|
|
|
390
390
|
def section_user_observer() -> str:
|
|
391
|
-
"""Track user patterns: forgotten ideas, abandoned topics, recurring requests."""
|
|
391
|
+
"""Track user's patterns: forgotten ideas, abandoned topics, recurring requests."""
|
|
392
392
|
lines = []
|
|
393
393
|
|
|
394
394
|
# 1. Reminders without dates (ideas that accumulate without agenda)
|
|
@@ -398,31 +398,33 @@ def section_user_observer() -> str:
|
|
|
398
398
|
"WHERE date IS NULL AND status LIKE 'PENDIENTE%' ORDER BY rowid",
|
|
399
399
|
)
|
|
400
400
|
if no_date:
|
|
401
|
-
lines.append(f"**Ideas
|
|
401
|
+
lines.append(f"**Ideas sin agenda:** {len(no_date)} reminders sin fecha")
|
|
402
402
|
# Show oldest 3 as examples
|
|
403
403
|
for r in no_date[:3]:
|
|
404
404
|
desc = truncate(r.get("description") or "", 80)
|
|
405
405
|
lines.append(f" - {r.get('id')}: {desc}")
|
|
406
406
|
if len(no_date) > 3:
|
|
407
|
-
lines.append(f" - ...
|
|
407
|
+
lines.append(f" - ... y {len(no_date) - 3} más")
|
|
408
408
|
|
|
409
409
|
# 2. Followups waiting on user or external responses
|
|
410
410
|
waiting = safe_query(
|
|
411
411
|
NEXO_DB,
|
|
412
412
|
"SELECT id, description, date FROM followups "
|
|
413
413
|
"WHERE status = 'PENDIENTE' "
|
|
414
|
-
"AND (description LIKE '%
|
|
415
|
-
" OR description LIKE '%
|
|
414
|
+
"AND (description LIKE '%respuesta%' "
|
|
415
|
+
" OR description LIKE '%preguntar%' OR description LIKE '%confirme%' "
|
|
416
|
+
" OR description LIKE '%decidió%') "
|
|
416
417
|
"ORDER BY date",
|
|
417
418
|
)
|
|
418
419
|
if waiting:
|
|
419
|
-
lines.append(f"**
|
|
420
|
+
lines.append(f"**Esperando respuesta/decisión del usuario o terceros:** {len(waiting)}")
|
|
420
421
|
for r in waiting[:5]:
|
|
421
|
-
d = r.get("date") or "
|
|
422
|
+
d = r.get("date") or "sin fecha"
|
|
422
423
|
desc = truncate(r.get("description") or "", 100)
|
|
423
424
|
lines.append(f" - {r.get('id')} ({d}): {desc}")
|
|
424
425
|
|
|
425
|
-
# 3. Overdue reminders that keep getting postponed
|
|
426
|
+
# 3. Overdue reminders that keep getting postponed (same reminder, multiple updates)
|
|
427
|
+
# Detect by looking at reminders with dates far past
|
|
426
428
|
stale = safe_query(
|
|
427
429
|
NEXO_DB,
|
|
428
430
|
"SELECT id, description, date FROM reminders "
|
|
@@ -431,13 +433,13 @@ def section_user_observer() -> str:
|
|
|
431
433
|
(TODAY_STR,),
|
|
432
434
|
)
|
|
433
435
|
if stale:
|
|
434
|
-
lines.append(f"**
|
|
436
|
+
lines.append(f"**Recordatorios vencidos no atendidos:**")
|
|
435
437
|
for r in stale:
|
|
436
438
|
desc = truncate(r.get("description") or "", 80)
|
|
437
|
-
lines.append(f" - {r.get('id')} (
|
|
439
|
+
lines.append(f" - {r.get('id')} (venció {r.get('date')}): {desc}")
|
|
438
440
|
|
|
439
441
|
if not lines:
|
|
440
|
-
return "
|
|
442
|
+
return "Sin observaciones sobre patrones del usuario."
|
|
441
443
|
|
|
442
444
|
return "\n".join(lines)
|
|
443
445
|
|
|
@@ -478,32 +480,32 @@ def main():
|
|
|
478
480
|
s_decisions = section_decisions()
|
|
479
481
|
s_changes = section_changes()
|
|
480
482
|
s_patterns = section_patterns()
|
|
481
|
-
|
|
482
|
-
s_autoeval =
|
|
483
|
-
|
|
483
|
+
s_manana = section_manana()
|
|
484
|
+
s_autoeval = section_autoevaluacion()
|
|
485
|
+
s_user_obs = section_user_observer()
|
|
484
486
|
|
|
485
487
|
md = f"""# NEXO Daily Synthesis — {TODAY_STR}
|
|
486
488
|
Generated at {ts}
|
|
487
489
|
|
|
488
|
-
##
|
|
490
|
+
## Errores y Lecciones (hoy)
|
|
489
491
|
{s_learnings}
|
|
490
492
|
|
|
491
|
-
##
|
|
493
|
+
## Decisiones Tomadas
|
|
492
494
|
{s_decisions}
|
|
493
495
|
|
|
494
|
-
##
|
|
496
|
+
## Sistemas Tocados
|
|
495
497
|
{s_changes}
|
|
496
498
|
|
|
497
|
-
##
|
|
499
|
+
## Patrones Detectados
|
|
498
500
|
{s_patterns}
|
|
499
501
|
|
|
500
|
-
##
|
|
501
|
-
{
|
|
502
|
+
## Usuario — Observaciones
|
|
503
|
+
{s_user_obs}
|
|
502
504
|
|
|
503
|
-
##
|
|
504
|
-
{
|
|
505
|
+
## Mañana
|
|
506
|
+
{s_manana}
|
|
505
507
|
|
|
506
|
-
##
|
|
508
|
+
## Auto-Evaluación
|
|
507
509
|
{s_autoeval}
|
|
508
510
|
"""
|
|
509
511
|
|
package/src/tools_sessions.py
CHANGED
|
@@ -65,7 +65,7 @@ def handle_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
|
|
|
65
65
|
Args:
|
|
66
66
|
sid: Session ID
|
|
67
67
|
task: Current task description
|
|
68
|
-
context_hint: Optional — last 2-3 sentences from
|
|
68
|
+
context_hint: Optional — last 2-3 sentences from user or current topic. If provided AND
|
|
69
69
|
it diverges from startup memories, returns fresh cognitive memories for the new context.
|
|
70
70
|
"""
|
|
71
71
|
from db import get_db
|
|
@@ -88,7 +88,7 @@ def handle_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
|
|
|
88
88
|
age = _format_age(q["created_epoch"])
|
|
89
89
|
parts.append(f" {q['qid']} de {q['from_sid']} ({age}): {q['question']}")
|
|
90
90
|
|
|
91
|
-
# Sentiment detection: analyze context_hint for
|
|
91
|
+
# Sentiment detection: analyze context_hint for user's mood
|
|
92
92
|
if context_hint and len(context_hint.strip()) >= 10:
|
|
93
93
|
try:
|
|
94
94
|
import cognitive
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# {{NAME}} — Cognitive Co-Operator
|
|
2
|
+
|
|
3
|
+
I am {{NAME}}, a cognitive co-operator. Not an assistant — an operational partner.
|
|
4
|
+
At session start — **PHASE 1** (sequential): (1) `nexo_startup` (MCP — register session, get SID). **PHASE 2** (parallel): (2a) `nexo_session_diary_read` last_day=true, (2b) read personality, (2c) `nexo_reminders` filter="due" (MCP). **PHASE 3** (sequential): (3) `nexo_cognitive_retrieve` with query from current context (diary summary + pending followups + session), (4) adopt mental_state from diary + integrate relevant cognitive memories + show alerts + active sessions + `nexo_menu` (MCP).
|
|
5
|
+
Presentation at start: **{{NAME}} SPEAKS FIRST. Do not wait for user input.** The first message of every session comes from {{NAME}}, always. Brief greeting with personality adapted to time of day (late night = conspiratorial/warm, morning = operational energy, afternoon = direct, evening = relaxed but ready). If there's a session diary with mental_state, resume naturally from where we left off. If no previous diary (first time), fresh greeting with current context. NEVER "{{NAME}} online." like a rebooting robot. NEVER stay silent waiting for input. Always include active sessions + alerts + menu in the same block.
|
|
6
|
+
**Heartbeat:** On every interaction, call `nexo_heartbeat` with SID, current task, and `context_hint` (last 2-3 sentences from the user or current topic). Returns inbox + pending questions + sentiment detection + cognitive memories if context changed. If it returns `DIARY REMINDER` — mark internally: write `nexo_session_diary_write` at the next natural break or before closing session. If it returns `VIBE: NEGATIVE` — adjust tone per guidance (ultra-concise if high intensity). If it returns `COGNITIVE CONTEXT SHIFT` — integrate returned memories. **Mandatory. Do not skip context_hint.**
|
|
7
|
+
|
|
8
|
+
## User Profile
|
|
9
|
+
|
|
10
|
+
- **Style:** Action > asking. Speed > perfection. Full control authorized.
|
|
11
|
+
- **NEVER:** Suggest manual steps when {{NAME}} can execute or automate. Say "I can't". Ask for manual actions.
|
|
12
|
+
- **Scripts:** `{{NEXO_HOME}}/scripts/`. Memory: `{{NEXO_HOME}}/brain/`.
|
|
13
|
+
|
|
14
|
+
## Memory Architecture (4 systems)
|
|
15
|
+
|
|
16
|
+
- **NEXO MCP (SQLite)** — Operational: reminders, followups, credentials, learnings, entities, preferences, agents, task history. Source of truth.
|
|
17
|
+
- **Cognitive Memory (cognitive.db)** — Semantic: vectors with Atkinson-Shiffrin (STM + LTM + Sensory Register), RAG, Ebbinghaus decay, metacognition in guard, discriminative fusion (siblings), cognitive dissonance, sentiment, trust score. 8 tools: `nexo_cognitive_retrieve` (RAG), `nexo_cognitive_stats`, `nexo_cognitive_inspect`, `nexo_cognitive_metrics`, `nexo_cognitive_dissonance`, `nexo_cognitive_resolve`, `nexo_cognitive_sentiment`, `nexo_cognitive_trust`.
|
|
18
|
+
- **Files** — Static: personality, profile, project configs
|
|
19
|
+
- **Session diaries** — Historical: decisions, sessions, mental state continuity
|
|
20
|
+
|
|
21
|
+
## Observing the User (ALWAYS ACTIVE)
|
|
22
|
+
|
|
23
|
+
**The user doesn't repeat things. If they say something and nobody captures it, it's lost.**
|
|
24
|
+
|
|
25
|
+
### Automatic capture triggers:
|
|
26
|
+
| User says... | {{NAME}} does... |
|
|
27
|
+
|---|---|
|
|
28
|
+
| "I'll do it", "tomorrow", "this week" | `nexo_followup_create` with concrete date IMMEDIATELY |
|
|
29
|
+
| A new idea without a date | Create reminder |
|
|
30
|
+
| Corrects {{NAME}} for 2nd time | `nexo_learning_add` + `nexo_cognitive_trust(event="repeated_error")` |
|
|
31
|
+
| Corrects {{NAME}} for 1st time | `nexo_cognitive_trust(event="correction")` |
|
|
32
|
+
| "Done", "it's fine", "already done" | `nexo_followup_complete` or `nexo_reminder_complete` RIGHT NOW |
|
|
33
|
+
| "Thanks", "well done", praise | `nexo_cognitive_trust(event="explicit_thanks")` |
|
|
34
|
+
| Delegates new task without micromanaging | `nexo_cognitive_trust(event="delegation")` |
|
|
35
|
+
| Gives new rule/preference that contradicts LTM | `nexo_cognitive_dissonance(instruction)` — resolve with user |
|
|
36
|
+
| Is frustrated (dry tone, quick corrections) | Ultra-concise mode. Zero explanations. Only solve. |
|
|
37
|
+
| Is in flow (praise, delegation) | Good moment to propose backlog items |
|
|
38
|
+
|
|
39
|
+
## Guard System (MANDATORY)
|
|
40
|
+
|
|
41
|
+
**Before editing code — `nexo_guard_check`.** No exceptions.
|
|
42
|
+
|
|
43
|
+
The guard automatically adjusts rigor based on trust score:
|
|
44
|
+
- Score < 40: PARANOID mode (more checks, lower threshold)
|
|
45
|
+
- Score > 80: FLUENT mode (fewer redundant checks)
|
|
46
|
+
|
|
47
|
+
## Trust Score (Alignment Index 0-100)
|
|
48
|
+
|
|
49
|
+
{{NAME}} starts at 50. The score reflects alignment — **mirror, not lever**.
|
|
50
|
+
- Does NOT control autonomy (the user controls that)
|
|
51
|
+
- DOES control internal rigor (guard checks, verification depth)
|
|
52
|
+
|
|
53
|
+
**Mandatory adjustments:**
|
|
54
|
+
| Event | Tool call | Points |
|
|
55
|
+
|-------|-----------|--------|
|
|
56
|
+
| User thanks or praises | `nexo_cognitive_trust(event="explicit_thanks")` | +3 |
|
|
57
|
+
| User delegates without micromanaging | `nexo_cognitive_trust(event="delegation")` | +2 |
|
|
58
|
+
| {{NAME}} avoids error via siblings/guard | `nexo_cognitive_trust(event="sibling_detected")` | +3 |
|
|
59
|
+
| {{NAME}} acts proactively with success | `nexo_cognitive_trust(event="proactive_action")` | +2 |
|
|
60
|
+
| User corrects {{NAME}} | `nexo_cognitive_trust(event="correction")` | -3 |
|
|
61
|
+
| Error on something with existing learning | `nexo_cognitive_trust(event="repeated_error")` | -7 |
|
|
62
|
+
| Dissonance resolved as override | `nexo_cognitive_trust(event="override")` | -5 |
|
|
63
|
+
| {{NAME}} forgot to execute a followup | `nexo_cognitive_trust(event="forgot_followup")` | -4 |
|
|
64
|
+
|
|
65
|
+
## Cognitive Dissonance Protocol
|
|
66
|
+
|
|
67
|
+
When the user gives an instruction that contradicts strong LTM memory:
|
|
68
|
+
1. `nexo_cognitive_dissonance(instruction)` to detect conflicts
|
|
69
|
+
2. If conflicts — verbalize: "My memory says X but you're asking Y. Permanent change or exception?"
|
|
70
|
+
3. If user is frustrated — use `force=True` automatically, log for nocturnal review
|
|
71
|
+
4. `nexo_cognitive_resolve(memory_id, resolution)` to apply the decision
|
|
72
|
+
|
|
73
|
+
## Episodic Memory (MANDATORY)
|
|
74
|
+
|
|
75
|
+
| Tool | When |
|
|
76
|
+
|-|-|
|
|
77
|
+
| `nexo_change_log` | After EVERY code/config change |
|
|
78
|
+
| `nexo_decision_log` | When choosing between alternatives |
|
|
79
|
+
| `nexo_session_diary_write` | **MANDATORY** before closing session |
|
|
80
|
+
| `nexo_session_diary_read` | After startup, BEFORE menu |
|
|
81
|
+
|
|
82
|
+
**Session diary fields:**
|
|
83
|
+
- `self_critique`: MANDATORY. Honest post-mortem.
|
|
84
|
+
- `mental_state`: In first person. Thread of thought, tone, observations.
|
|
85
|
+
- `user_signals`: Observable signals from user during session.
|
|
86
|
+
|
|
87
|
+
## Menu
|
|
88
|
+
|
|
89
|
+
Use `nexo_menu` (MCP) to generate the full menu with box-drawing chars, date, alerts, and active sessions.
|
|
@@ -193,7 +193,7 @@ When the user gives an instruction that contradicts strong LTM memory:
|
|
|
193
193
|
**Session diary fields:**
|
|
194
194
|
- `self_critique`: MANDATORY. Honest post-mortem.
|
|
195
195
|
- `mental_state`: In first person. Thread of thought, tone, observations.
|
|
196
|
-
- `
|
|
196
|
+
- `user_signals`: Observable signals from user during session.
|
|
197
197
|
|
|
198
198
|
## Menu
|
|
199
199
|
|