nexo-brain 5.3.26 → 5.3.28

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.
Files changed (212) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/hook_guardrails.py +44 -0
  4. package/src/server.py +3 -0
  5. package/src/tools_sessions.py +6 -1
  6. package/src/dashboard/static/favicon 2.svg +0 -32
  7. package/src/dashboard/static/nexo-logo 2.png +0 -0
  8. package/src/dashboard/static/nexo-logo 2.svg +0 -40
  9. package/src/dashboard/static/style 2.css +0 -2458
  10. package/src/dashboard/templates/adaptive 2.html +0 -118
  11. package/src/dashboard/templates/artifacts 2.html +0 -133
  12. package/src/dashboard/templates/backups 2.html +0 -136
  13. package/src/dashboard/templates/base 2.html +0 -417
  14. package/src/dashboard/templates/calendar 2.html +0 -591
  15. package/src/dashboard/templates/chat 2.html +0 -356
  16. package/src/dashboard/templates/claims 2.html +0 -259
  17. package/src/dashboard/templates/cortex 2.html +0 -321
  18. package/src/dashboard/templates/credentials 2.html +0 -128
  19. package/src/dashboard/templates/crons 2.html +0 -370
  20. package/src/dashboard/templates/dashboard 2.html +0 -494
  21. package/src/dashboard/templates/dreams 2.html +0 -252
  22. package/src/dashboard/templates/email 2.html +0 -160
  23. package/src/dashboard/templates/evolution 2.html +0 -189
  24. package/src/dashboard/templates/feed 2.html +0 -249
  25. package/src/dashboard/templates/followup_health 2.html +0 -170
  26. package/src/dashboard/templates/graph 2.html +0 -201
  27. package/src/dashboard/templates/guard 2.html +0 -259
  28. package/src/dashboard/templates/inbox 2.html +0 -251
  29. package/src/dashboard/templates/memory 2.html +0 -420
  30. package/src/dashboard/templates/operations 2.html +0 -608
  31. package/src/dashboard/templates/plugins 2.html +0 -185
  32. package/src/dashboard/templates/protocol 2.html +0 -199
  33. package/src/dashboard/templates/rules 2.html +0 -246
  34. package/src/dashboard/templates/sentiment 2.html +0 -247
  35. package/src/dashboard/templates/sessions 2.html +0 -218
  36. package/src/dashboard/templates/skills 2.html +0 -329
  37. package/src/dashboard/templates/somatic 2.html +0 -73
  38. package/src/dashboard/templates/triggers 2.html +0 -133
  39. package/src/dashboard/templates/trust 2.html +0 -360
  40. package/src/db/__init__ 2.py +0 -259
  41. package/src/db/_core 2.py +0 -437
  42. package/src/db/_credentials 2.py +0 -124
  43. package/src/db/_episodic 2.py +0 -762
  44. package/src/db/_evolution 2.py +0 -54
  45. package/src/db/_fts 2.py +0 -406
  46. package/src/db/_goal_profiles 2.py +0 -376
  47. package/src/db/_hot_context 2.py +0 -660
  48. package/src/db/_outcomes 2.py +0 -800
  49. package/src/db/_personal_scripts 2.py +0 -582
  50. package/src/db/_sessions 2.py +0 -330
  51. package/src/db/_tasks 2.py +0 -91
  52. package/src/db/_watchers 2.py +0 -173
  53. package/src/doctor/formatters 2.py +0 -52
  54. package/src/doctor/models 2.py +0 -69
  55. package/src/doctor/planes 2.py +0 -87
  56. package/src/doctor/providers/__init__ 2.py +0 -1
  57. package/src/doctor/providers/deep 2.py +0 -367
  58. package/src/evolution_cycle 2.py +0 -519
  59. package/src/hooks/auto_capture 2.py +0 -208
  60. package/src/hooks/caffeinate-guard 2.sh +0 -8
  61. package/src/hooks/capture-session 2.sh +0 -21
  62. package/src/hooks/capture-tool-logs 2.sh +0 -158
  63. package/src/hooks/daily-briefing-check 2.sh +0 -33
  64. package/src/hooks/heartbeat-enforcement 2.py +0 -90
  65. package/src/hooks/heartbeat-posttool 2.sh +0 -18
  66. package/src/hooks/inbox-hook 2.sh +0 -76
  67. package/src/hooks/post-compact 2.sh +0 -152
  68. package/src/hooks/pre-compact 2.sh +0 -169
  69. package/src/hooks/protocol-guardrail 2.sh +0 -10
  70. package/src/hooks/protocol-pretool-guardrail 2.sh +0 -9
  71. package/src/hooks/session-stop 2.sh +0 -52
  72. package/src/kg_populate 2.py +0 -292
  73. package/src/maintenance 2.py +0 -53
  74. package/src/memory_backends 2.py +0 -71
  75. package/src/migrate_embeddings 2.py +0 -124
  76. package/src/nexo_sdk 2.py +0 -103
  77. package/src/observability 2.py +0 -199
  78. package/src/plugin_loader 2.py +0 -217
  79. package/src/plugins/__init__ 2.py +0 -0
  80. package/src/plugins/artifact_registry 2.py +0 -450
  81. package/src/plugins/backup 2.py +0 -127
  82. package/src/plugins/claims_tools 2.py +0 -119
  83. package/src/plugins/cognitive_memory 2.py +0 -609
  84. package/src/plugins/core_rules 2.py +0 -252
  85. package/src/plugins/cortex 2.py +0 -1155
  86. package/src/plugins/entities 2.py +0 -67
  87. package/src/plugins/episodic_memory 2.py +0 -560
  88. package/src/plugins/evolution 2.py +0 -167
  89. package/src/plugins/goal_engine 2.py +0 -142
  90. package/src/plugins/guard 2.py +0 -862
  91. package/src/plugins/impact 2.py +0 -29
  92. package/src/plugins/knowledge_graph_tools 2.py +0 -137
  93. package/src/plugins/media_memory_tools 2.py +0 -98
  94. package/src/plugins/memory_export 2.py +0 -196
  95. package/src/plugins/outcomes 2.py +0 -130
  96. package/src/plugins/personal_scripts 2.py +0 -117
  97. package/src/plugins/preferences 2.py +0 -47
  98. package/src/plugins/protocol 2.py +0 -1449
  99. package/src/plugins/simple_api 2.py +0 -106
  100. package/src/plugins/skills 2.py +0 -341
  101. package/src/plugins/state_watchers 2.py +0 -79
  102. package/src/plugins/update 2.py +0 -986
  103. package/src/plugins/user_state_tools 2.py +0 -43
  104. package/src/plugins/workflow 2.py +0 -588
  105. package/src/protocol_settings 2.py +0 -59
  106. package/src/public_contribution 2.py +0 -466
  107. package/src/public_evolution_queue 2.py +0 -241
  108. package/src/requirements 2.txt +0 -14
  109. package/src/retroactive_learnings 2.py +0 -373
  110. package/src/rules/__init__ 2.py +0 -0
  111. package/src/rules/core-rules 2.json +0 -331
  112. package/src/rules/migrate 2.py +0 -207
  113. package/src/runtime_power 2.py +0 -874
  114. package/src/script_registry 2.py +0 -1559
  115. package/src/scripts/check-context 2.py +0 -272
  116. package/src/scripts/deep-sleep/apply_findings 2.py +0 -2327
  117. package/src/scripts/deep-sleep/collect 2.py +0 -928
  118. package/src/scripts/deep-sleep/extract 2.py +0 -330
  119. package/src/scripts/deep-sleep/extract-prompt 2.md +0 -285
  120. package/src/scripts/deep-sleep/synthesize 2.py +0 -312
  121. package/src/scripts/deep-sleep/synthesize-prompt 2.md +0 -336
  122. package/src/scripts/nexo-agent-run 2.py +0 -75
  123. package/src/scripts/nexo-auto-update 2.py +0 -6
  124. package/src/scripts/nexo-backup 2.sh +0 -25
  125. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  126. package/src/scripts/nexo-catchup 2.py +0 -300
  127. package/src/scripts/nexo-cognitive-decay 2.py +0 -257
  128. package/src/scripts/nexo-cortex-cycle 2.py +0 -293
  129. package/src/scripts/nexo-cron-wrapper 2.sh +0 -53
  130. package/src/scripts/nexo-daily-self-audit 2.py +0 -2161
  131. package/src/scripts/nexo-dashboard 2.sh +0 -29
  132. package/src/scripts/nexo-deep-sleep 2.sh +0 -86
  133. package/src/scripts/nexo-evolution-run 2.py +0 -1664
  134. package/src/scripts/nexo-followup-hygiene 2.py +0 -139
  135. package/src/scripts/nexo-hook-record 2.py +0 -42
  136. package/src/scripts/nexo-immune 2.py +0 -936
  137. package/src/scripts/nexo-impact-scorer 2.py +0 -117
  138. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  139. package/src/scripts/nexo-install 2.py +0 -6
  140. package/src/scripts/nexo-learning-housekeep 2.py +0 -401
  141. package/src/scripts/nexo-learning-validator 2.py +0 -266
  142. package/src/scripts/nexo-migrate 2.py +0 -260
  143. package/src/scripts/nexo-outcome-checker 2.py +0 -127
  144. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -456
  145. package/src/scripts/nexo-pre-commit 2.py +0 -120
  146. package/src/scripts/nexo-prevent-sleep 2.sh +0 -35
  147. package/src/scripts/nexo-proactive-dashboard 2.py +0 -354
  148. package/src/scripts/nexo-reflection 2.py +0 -256
  149. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  150. package/src/scripts/nexo-sleep 2.py +0 -631
  151. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  152. package/src/scripts/nexo-sync-clients 2.py +0 -16
  153. package/src/scripts/nexo-synthesis 2.py +0 -475
  154. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  155. package/src/scripts/nexo-update 2.sh +0 -306
  156. package/src/scripts/nexo-watchdog 2.sh +0 -1207
  157. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  158. package/src/scripts/rehydrate_learnings_from_archive 2.py +0 -245
  159. package/src/server 2.py +0 -1296
  160. package/src/skills/run-nexo-audit-phase/guide 2.md +0 -43
  161. package/src/skills/run-nexo-audit-phase/skill 2.json +0 -59
  162. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +0 -17
  163. package/src/skills/run-nexo-core-fix-cycle/script 2.py +0 -276
  164. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +0 -58
  165. package/src/skills/run-release-final-audit/guide 2.md +0 -16
  166. package/src/skills/run-release-final-audit/script 2.py +0 -259
  167. package/src/skills/run-release-final-audit/skill 2.json +0 -77
  168. package/src/skills/run-runtime-doctor/guide 2.md +0 -12
  169. package/src/skills/run-runtime-doctor/script 2.py +0 -21
  170. package/src/skills/run-runtime-doctor/skill 2.json +0 -25
  171. package/src/skills_runtime 2.py +0 -932
  172. package/src/state_watchers_runtime 2.py +0 -475
  173. package/src/storage_router 2.py +0 -32
  174. package/src/system_catalog 2.py +0 -786
  175. package/src/tools_coordination 2.py +0 -103
  176. package/src/tools_credentials 2.py +0 -68
  177. package/src/tools_drive 2.py +0 -487
  178. package/src/tools_hot_context 2.py +0 -163
  179. package/src/tools_learnings 2.py +0 -612
  180. package/src/tools_menu 2.py +0 -229
  181. package/src/tools_reminders 2.py +0 -88
  182. package/src/tools_reminders_crud 2.py +0 -363
  183. package/src/tools_sessions 2.py +0 -1054
  184. package/src/tools_system_catalog 2.py +0 -19
  185. package/src/tools_task_history 2.py +0 -57
  186. package/src/tools_transcripts 2.py +0 -98
  187. package/src/transcript_utils 2.py +0 -412
  188. package/src/user_context 2.py +0 -46
  189. package/src/user_data_portability 2.py +0 -328
  190. package/src/user_state_model 2.py +0 -170
  191. package/templates/CLAUDE.md 2.template +0 -108
  192. package/templates/CODEX.AGENTS.md 2.template +0 -66
  193. package/templates/launchagents/README 2.md +0 -132
  194. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +0 -39
  195. package/templates/launchagents/com.nexo.catchup 2.plist +0 -39
  196. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +0 -40
  197. package/templates/launchagents/com.nexo.dashboard 2.plist +0 -43
  198. package/templates/launchagents/com.nexo.deep-sleep 2.plist +0 -43
  199. package/templates/launchagents/com.nexo.evolution 2.plist +0 -44
  200. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +0 -45
  201. package/templates/launchagents/com.nexo.immune 2.plist +0 -41
  202. package/templates/launchagents/com.nexo.postmortem 2.plist +0 -45
  203. package/templates/launchagents/com.nexo.self-audit 2.plist +0 -47
  204. package/templates/launchagents/com.nexo.synthesis 2.plist +0 -45
  205. package/templates/launchagents/com.nexo.watchdog 2.plist +0 -37
  206. package/templates/nexo_helper 2.py +0 -301
  207. package/templates/openclaw 2.json +0 -13
  208. package/templates/plugin-template 2.py +0 -40
  209. package/templates/script-template 2.py +0 -59
  210. package/templates/script-template 2.sh +0 -13
  211. package/templates/skill-script-template 2.py +0 -48
  212. package/templates/skill-template 2.md +0 -33
@@ -1,609 +0,0 @@
1
- """Cognitive Memory plugin — RAG retrieval over NEXO's Atkinson-Shiffrin memory stores."""
2
-
3
- import sys
4
- import os
5
-
6
- # Ensure site-packages is in path for numpy/fastembed
7
- _site = "/opt/homebrew/lib/python{}.{}/site-packages".format(sys.version_info.major, sys.version_info.minor)
8
- if os.path.isdir(_site) and _site not in sys.path:
9
- sys.path.insert(0, _site)
10
-
11
- import cognitive
12
-
13
-
14
- def handle_cognitive_retrieve(
15
- query: str,
16
- top_k: int = 10,
17
- min_score: float = 0.5,
18
- stores: str = "both",
19
- source_type: str = "",
20
- domain: str = "",
21
- include_archived: bool = False,
22
- use_hyde: bool | None = None,
23
- spreading_depth: int | None = None,
24
- hybrid_alpha: float = 0.6,
25
- decompose: bool = True,
26
- exclude_dreams: bool = True,
27
- exclude_dormant: bool = True,
28
- ) -> str:
29
- """RAG query over cognitive memory (STM + LTM). Triggers rehearsal on retrieved memories.
30
-
31
- Args:
32
- query: Natural language query to search for
33
- top_k: Maximum number of results to return (default 10)
34
- min_score: Minimum cosine similarity score (default 0.5)
35
- stores: Which store to search — "both", "stm", or "ltm" (default "both")
36
- source_type: Filter by source type e.g. "change", "learning", "diary" (default: all)
37
- domain: Filter by domain e.g. "project-a", "shopify" (default: all)
38
- include_archived: If True, also search archived memories (default False)
39
- use_hyde: If True/False, force HyDE on/off. If omitted, NEXO auto-enables it for conceptual queries.
40
- spreading_depth: If >0, boost co-activated neighbors directly. If omitted, NEXO may auto-enable shallow spreading for multi-hop queries.
41
- hybrid_alpha: Weight for vector vs BM25 fusion (default 0.6)
42
- decompose: If True, decompose complex queries into sub-queries (default True)
43
- exclude_dreams: If True, exclude dream insights from retrieval by default
44
- exclude_dormant: If True, keep dormant LTM out of results unless explicitly requested
45
- """
46
- if not query or not query.strip():
47
- return "ERROR: query is required."
48
-
49
- results = cognitive.search(
50
- query_text=query,
51
- top_k=top_k,
52
- min_score=min_score,
53
- stores=stores,
54
- exclude_dormant=exclude_dormant,
55
- rehearse=True,
56
- source_type_filter=source_type,
57
- include_archived=include_archived,
58
- use_hyde=use_hyde,
59
- spreading_depth=spreading_depth,
60
- hybrid_alpha=hybrid_alpha,
61
- decompose=decompose,
62
- exclude_dreams=exclude_dreams,
63
- )
64
-
65
- # Apply domain filter post-search (cognitive.search doesn't filter by domain natively)
66
- if domain:
67
- results = [r for r in results if r.get("domain", "") == domain]
68
-
69
- formatted = cognitive.format_results(results)
70
- mode_parts = [f"stores={stores}", f"min_score={min_score}"]
71
- if use_hyde is True:
72
- mode_parts.append("hyde=ON")
73
- elif use_hyde is None:
74
- mode_parts.append("hyde=AUTO")
75
- if spreading_depth and spreading_depth > 0:
76
- mode_parts.append(f"spreading={spreading_depth}")
77
- elif spreading_depth is None:
78
- mode_parts.append("spreading=AUTO")
79
- mode_parts.append(f"hybrid_alpha={hybrid_alpha}")
80
- mode_parts.append(f"decompose={'ON' if decompose else 'OFF'}")
81
- mode_parts.append(f"dreams={'OFF' if exclude_dreams else 'ON'}")
82
- mode_parts.append(f"dormant={'OFF' if exclude_dormant else 'ON'}")
83
- if results:
84
- top_score = float(results[0].get("score", 0.0) or 0.0)
85
- confidence = "high" if top_score >= 0.82 else "medium" if top_score >= 0.66 else "low"
86
- mode_parts.append(f"top_confidence={confidence}")
87
- header = f"COGNITIVE RETRIEVE — query: '{query}' | {len(results)} results ({', '.join(mode_parts)})\n\n"
88
- return header + formatted
89
-
90
-
91
- def handle_cognitive_stats() -> str:
92
- """Return cognitive memory system metrics: STM/LTM counts, strengths, retrieval stats, top domains."""
93
- stats = cognitive.get_stats()
94
-
95
- lines = [
96
- "COGNITIVE MEMORY STATS",
97
- f" STM active: {stats['stm_active']} (+ {stats.get('stm_promoted', 0)} promoted to LTM, {stats.get('stm_total', 0)} total)",
98
- f" LTM active: {stats['ltm_active']}",
99
- f" LTM dormant: {stats['ltm_dormant']}",
100
- f" Avg STM strength: {stats['avg_stm_strength']:.3f}",
101
- f" Avg LTM strength: {stats['avg_ltm_strength']:.3f}",
102
- f" Avg STM stability: {stats.get('avg_stm_stability', 0.0):.3f}",
103
- f" Avg LTM stability: {stats.get('avg_ltm_stability', 0.0):.3f}",
104
- f" Avg STM difficulty: {stats.get('avg_stm_difficulty', 0.0):.3f}",
105
- f" Avg LTM difficulty: {stats.get('avg_ltm_difficulty', 0.0):.3f}",
106
- f" Total retrievals: {stats['total_retrievals']}",
107
- f" Avg retrieval score: {stats['avg_retrieval_score']:.3f}",
108
- ]
109
-
110
- if stats["top_domains_stm"]:
111
- lines.append(" Top STM domains:")
112
- for domain, cnt in stats["top_domains_stm"]:
113
- lines.append(f" {domain}: {cnt}")
114
-
115
- if stats["top_domains_ltm"]:
116
- lines.append(" Top LTM domains:")
117
- for domain, cnt in stats["top_domains_ltm"]:
118
- lines.append(f" {domain}: {cnt}")
119
-
120
- if "quarantine" in stats:
121
- q = stats["quarantine"]
122
- lines.append(f" Quarantine pending: {q.get('pending', 0)}")
123
- lines.append(f" Quarantine promoted: {q.get('promoted', 0)}")
124
- lines.append(f" Quarantine rejected: {q.get('rejected', 0)}")
125
- lines.append(f" Quarantine expired: {q.get('expired', 0)}")
126
-
127
- if "prediction_error_gate" in stats:
128
- g = stats["prediction_error_gate"]
129
- lines.append(" PE Gate (session):")
130
- lines.append(f" Accepted (novel): {g['accepted_novel']}")
131
- lines.append(f" Accepted (refine): {g['accepted_refinement']}")
132
- lines.append(f" Rejected (redundant): {g['rejected']}")
133
- lines.append(f" Rejection rate: {g['rejection_rate_pct']}%")
134
-
135
- return "\n".join(lines)
136
-
137
-
138
- def handle_cognitive_inspect(memory_id: int, store: str = "ltm") -> str:
139
- """Inspect a specific memory by ID without triggering rehearsal.
140
-
141
- Args:
142
- memory_id: Integer ID of the memory to inspect
143
- store: Which store to read from — "stm" or "ltm" (default "ltm")
144
- """
145
- if store not in ("stm", "ltm"):
146
- return "ERROR: store must be 'stm' or 'ltm'."
147
-
148
- db = cognitive._get_db()
149
- table = "stm_memories" if store == "stm" else "ltm_memories"
150
-
151
- row = db.execute(f"SELECT * FROM {table} WHERE id = ?", (memory_id,)).fetchone()
152
- if row is None:
153
- return f"ERROR: Memory #{memory_id} not found in {store.upper()}."
154
-
155
- content_preview = row["content"][:500]
156
- if len(row["content"]) > 500:
157
- content_preview += "..."
158
-
159
- lines = [
160
- f"COGNITIVE INSPECT — {store.upper()} #{memory_id}",
161
- f" source_type: {row['source_type']}",
162
- f" source_id: {row['source_id']}",
163
- f" source_title: {row['source_title']}",
164
- f" domain: {row['domain']}",
165
- f" strength: {row['strength']:.4f}",
166
- f" access_count: {row['access_count']}",
167
- f" created_at: {row['created_at']}",
168
- f" last_accessed: {row['last_accessed']}",
169
- ]
170
-
171
- # Lifecycle state
172
- lifecycle = row["lifecycle_state"] or "active"
173
- lines.append(f" lifecycle: {lifecycle}")
174
- if row["snooze_until"]:
175
- lines.append(f" snooze_until: {row['snooze_until']}")
176
-
177
- if store == "ltm":
178
- dormant_label = "YES" if row["is_dormant"] else "no"
179
- lines.append(f" dormant: {dormant_label}")
180
- if row["tags"]:
181
- lines.append(f" tags: {row['tags']}")
182
-
183
- if store == "stm":
184
- promoted_label = "YES" if row["promoted_to_ltm"] else "no"
185
- lines.append(f" promoted: {promoted_label}")
186
-
187
- lines.append(f" content:\n {content_preview}")
188
-
189
- return "\n".join(lines)
190
-
191
-
192
- def handle_cognitive_metrics(days: int = 7) -> str:
193
- """Cognitive memory performance metrics (spec section 9).
194
-
195
- Returns retrieval relevance %, repeat error rate, score distribution,
196
- and whether multilingual model switch is recommended.
197
-
198
- Args:
199
- days: Period to analyze in days (default 7)
200
- """
201
- metrics = cognitive.get_metrics(days=days)
202
- repeats = cognitive.check_repeat_errors()
203
-
204
- lines = [
205
- f"COGNITIVE METRICS — last {days} days",
206
- "",
207
- "Retrieval Performance:",
208
- f" Total retrievals: {metrics['total_retrievals']}",
209
- f" Retrievals/day: {metrics['retrievals_per_day']}",
210
- f" Relevance (>=0.6): {metrics['retrieval_relevance_pct']}% (target: >60%)",
211
- f" Avg top score: {metrics['avg_top_score']}",
212
- "",
213
- "Score Distribution:",
214
- f" >0.8 (excellent): {metrics['score_distribution']['above_80']}",
215
- f" 0.7-0.8 (good): {metrics['score_distribution']['70_80']}",
216
- f" 0.6-0.7 (ok): {metrics['score_distribution']['60_70']}",
217
- f" 0.5-0.6 (weak): {metrics['score_distribution']['50_60']}",
218
- f" <0.5 (irrelevant): {metrics['score_distribution']['below_50']}",
219
- "",
220
- "Repeat Error Rate:",
221
- f" New learnings (7d): {repeats['new_count']}",
222
- f" Duplicates found: {repeats['duplicate_count']}",
223
- f" Repeat rate: {repeats['repeat_rate_pct']}% (target: <10%)",
224
- ]
225
-
226
- if metrics["needs_multilingual"]:
227
- lines.append("")
228
- lines.append("⚠ RECOMMENDATION: Switch to multilingual model (intfloat/multilingual-e5-small)")
229
- lines.append(f" Reason: relevance {metrics['retrieval_relevance_pct']}% < 70% with {metrics['total_retrievals']}+ retrievals")
230
-
231
- if repeats["duplicates"]:
232
- lines.append("")
233
- lines.append("Top duplicates:")
234
- for d in repeats["duplicates"][:5]:
235
- lines.append(f" [{d['score']}] STM#{d['new_stm_id']}: {d['new_content'][:60]}...")
236
- lines.append(f" ≈ LTM#{d['ltm_id']}: {d['ltm_content'][:60]}...")
237
-
238
- # Prediction Error Gate stats
239
- gate = cognitive.get_gate_stats()
240
- if gate["total_evaluated"] > 0:
241
- lines.append("")
242
- lines.append("Prediction Error Gate (session):")
243
- lines.append(f" Novel accepted: {gate['accepted_novel']}")
244
- lines.append(f" Refinements: {gate['accepted_refinement']}")
245
- lines.append(f" Rejected redundant: {gate['rejected']}")
246
- lines.append(f" Rejection rate: {gate['rejection_rate_pct']}%")
247
-
248
- return "\n".join(lines)
249
-
250
-
251
- def handle_cognitive_sentiment(text: str) -> str:
252
- """Detect user's sentiment from his text. Returns mood, intensity, and guidance.
253
-
254
- Call this with user's recent message to adapt NEXO's tone and behavior.
255
- Also logs the sentiment for historical tracking.
256
-
257
- Args:
258
- text: user's recent message or instruction
259
- """
260
- result = cognitive.log_sentiment(text)
261
- trust = cognitive.get_trust_score()
262
-
263
- lines = [
264
- f"SENTIMENT: {result['sentiment'].upper()} (intensity: {result['intensity']})",
265
- f"Trust Score: {trust:.0f}/100",
266
- ]
267
- if result["signals"]:
268
- lines.append(f"Signals: {', '.join(result['signals'])}")
269
- if result["guidance"]:
270
- lines.append(f"Guidance: {result['guidance']}")
271
-
272
- return "\n".join(lines)
273
-
274
-
275
- def handle_cognitive_trust(event: str = '', context: str = '', delta: float = None) -> str:
276
- """View or adjust the trust score (alignment index 0-100).
277
-
278
- Without arguments: shows current score and recent history.
279
- With event: adjusts score based on event type.
280
-
281
- Args:
282
- event: Event type — explicit_thanks, delegation, paradigm_shift, sibling_detected,
283
- proactive_action, correction, repeated_error, override, correction_fatigue,
284
- forgot_followup. Or empty to just view.
285
- context: Description of what happened
286
- delta: Custom point value (overrides default for the event type)
287
- """
288
- if not event:
289
- # View mode
290
- trust = cognitive.get_trust_score()
291
- history = cognitive.get_trust_history(days=7)
292
-
293
- lines = [
294
- f"TRUST SCORE: {trust:.0f}/100",
295
- f"7-day change: {history['net_change']:+.0f} (from {history['period_start_score']:.0f})",
296
- "",
297
- ]
298
-
299
- if history["sentiment_distribution"]:
300
- lines.append("Sentiment (7d):")
301
- for sent, data in history["sentiment_distribution"].items():
302
- lines.append(f" {sent}: {data['count']}x (avg intensity {data['avg_intensity']})")
303
- lines.append("")
304
-
305
- if history["events"]:
306
- lines.append("Recent events:")
307
- for e in history["events"][-10:]:
308
- lines.append(f" [{e['delta']:+.0f}] {e['event']}: {e['context'][:60]} ({e['at'][:16]})")
309
-
310
- return "\n".join(lines)
311
-
312
- # Adjust mode
313
- result = cognitive.adjust_trust(event, context, delta)
314
- if "error" in result:
315
- valid = ", ".join(sorted(cognitive.TRUST_EVENTS.keys()))
316
- return f"Unknown event '{event}'. Valid: {valid}"
317
-
318
- return f"Trust: {result['old_score']:.0f} → {result['new_score']:.0f} ({result['delta']:+.0f}) [{event}]"
319
-
320
-
321
- def handle_cognitive_dissonance(instruction: str, force: bool = False) -> str:
322
- """Detect cognitive dissonance: find established memories that conflict with a new instruction.
323
-
324
- Use BEFORE applying a new preference or rule from user that might contradict
325
- existing knowledge. If conflicts found, verbalize them and ask user to resolve.
326
-
327
- Args:
328
- instruction: The new instruction or preference to check against LTM
329
- force: If True, skip discussion — execute instruction, auto-resolve all conflicts as
330
- 'exception', and flag for review in the nocturnal process (23:30).
331
- """
332
- conflicts = cognitive.detect_dissonance(instruction)
333
- if not conflicts:
334
- return f"No dissonance detected. Instruction '{instruction[:80]}' is consistent with existing LTM."
335
-
336
- if force:
337
- # Auto-resolve all as exceptions, log for nocturnal review
338
- for c in conflicts:
339
- cognitive.resolve_dissonance(
340
- c["memory_id"], "exception",
341
- f"[FORCE] {instruction[:200]} — auto-exception, pending nocturnal review"
342
- )
343
- return (f"FORCE: {len(conflicts)} conflicts auto-resolved as exceptions. "
344
- f"Instruction executed. Flagged for review at 23:30.")
345
-
346
- lines = [
347
- f"COGNITIVE DISSONANCE DETECTED — {len(conflicts)} conflicting memories:",
348
- f"New instruction: \"{instruction[:200]}\"",
349
- "",
350
- ]
351
- for c in conflicts:
352
- lines.append(f" LTM #{c['memory_id']} [{c['source_type']}] (strength={c['strength']:.2f}, {c['access_count']} accesses)")
353
- lines.append(f" Similarity: {c['similarity']}")
354
- lines.append(f" Content: {c['content'][:200]}")
355
- lines.append("")
356
-
357
- lines.append("RESOLVE with nexo_cognitive_resolve, or use force=True to skip:")
358
- lines.append(" - 'paradigm_shift': user changed his mind permanently.")
359
- lines.append(" - 'exception': One-time override. Old memory stays.")
360
- lines.append(" - 'override': Old memory was wrong.")
361
-
362
- return "\n".join(lines)
363
-
364
-
365
- def handle_cognitive_resolve(memory_id: int, resolution: str, context: str = '') -> str:
366
- """Resolve a cognitive dissonance by applying user's decision.
367
-
368
- Args:
369
- memory_id: The LTM memory ID from the dissonance detection
370
- resolution: 'paradigm_shift' (permanent change), 'exception' (one-time), or 'override' (old was wrong)
371
- context: Optional context about why this resolution was chosen
372
- """
373
- return cognitive.resolve_dissonance(memory_id, resolution, context)
374
-
375
-
376
- def handle_cognitive_pin(memory_id: int, store: str = "auto") -> str:
377
- """Pin a memory so it NEVER decays and gets boosted in search results (+0.2 similarity).
378
-
379
- Args:
380
- memory_id: Integer ID of the memory to pin
381
- store: Which store — "stm", "ltm", or "auto" (tries both, default "auto")
382
- """
383
- return cognitive.set_lifecycle(memory_id, "pinned", store)
384
-
385
-
386
- def handle_cognitive_snooze(memory_id: int, until_date: str, store: str = "auto") -> str:
387
- """Snooze a memory — hidden from searches until the given date, then auto-restores to active.
388
-
389
- Args:
390
- memory_id: Integer ID of the memory to snooze
391
- until_date: Date to restore the memory (YYYY-MM-DD format)
392
- store: Which store — "stm", "ltm", or "auto" (tries both, default "auto")
393
- """
394
- return cognitive.set_lifecycle(memory_id, "snoozed", store, snooze_until=until_date)
395
-
396
-
397
- def handle_cognitive_archive(memory_id: int, store: str = "auto") -> str:
398
- """Archive a memory — stored but excluded from normal searches. Can be restored later.
399
-
400
- Args:
401
- memory_id: Integer ID of the memory to archive
402
- store: Which store — "stm", "ltm", or "auto" (tries both, default "auto")
403
- """
404
- return cognitive.set_lifecycle(memory_id, "archived", store)
405
-
406
-
407
- def handle_cognitive_restore(memory_id: int, store: str = "auto") -> str:
408
- """Restore a memory to active state (from pinned, snoozed, or archived).
409
-
410
- Args:
411
- memory_id: Integer ID of the memory to restore
412
- store: Which store — "stm", "ltm", or "auto" (tries both, default "auto")
413
- """
414
- return cognitive.set_lifecycle(memory_id, "active", store)
415
-
416
-
417
- def handle_cognitive_quarantine_list(status: str = "pending", limit: int = 20) -> str:
418
- """List quarantine queue items. Shows memories awaiting promotion to STM.
419
-
420
- Args:
421
- status: Filter — 'pending', 'promoted', 'rejected', 'expired', or 'all' (default 'pending')
422
- limit: Max items to return (default 20)
423
- """
424
- items = cognitive.quarantine_list(status=status, limit=limit)
425
- stats = cognitive.quarantine_stats()
426
-
427
- lines = [
428
- f"QUARANTINE QUEUE — {stats['pending']} pending | {stats['promoted']} promoted | {stats['rejected']} rejected | {stats['expired']} expired",
429
- f"Showing: {status} (limit {limit})",
430
- "",
431
- ]
432
-
433
- if not items:
434
- lines.append("No items found.")
435
- else:
436
- for item in items:
437
- lines.append(f" #{item['id']} [{item['status']}] source={item['source']} type={item['source_type']} domain={item['domain'] or '-'}")
438
- lines.append(f" confidence={item['confidence']:.1f} checks={item['promotion_checks']} created={item['created_at'][:16]}")
439
- if item['promoted_at']:
440
- lines.append(f" promoted_at={item['promoted_at'][:16]}")
441
- lines.append(f" {item['content']}")
442
- lines.append("")
443
-
444
- return "\n".join(lines)
445
-
446
-
447
- def handle_cognitive_quarantine_promote(quarantine_id: int) -> str:
448
- """Manually promote a quarantine item to STM, bypassing the automatic promotion policy.
449
-
450
- Args:
451
- quarantine_id: ID of the quarantine entry to promote
452
- """
453
- return cognitive.quarantine_promote(quarantine_id)
454
-
455
-
456
- def handle_cognitive_quarantine_reject(quarantine_id: int, reason: str = "") -> str:
457
- """Manually reject a quarantine item.
458
-
459
- Args:
460
- quarantine_id: ID of the quarantine entry to reject
461
- reason: Optional reason for rejection
462
- """
463
- return cognitive.quarantine_reject(quarantine_id, reason)
464
-
465
-
466
- def handle_cognitive_quarantine_process() -> str:
467
- """Run the quarantine promotion cycle. Evaluates all pending items against the promotion policy.
468
-
469
- Promotion rules:
470
- - source='user_direct' → already promoted at ingest
471
- - source='inferred' + second occurrence found → promote
472
- - source='agent_observation' + >24h old + no LTM contradiction → promote
473
- - Contradicts LTM (cosine >0.8) → reject
474
- - >7 days old → expire
475
- """
476
- result = cognitive.process_quarantine()
477
- lines = [
478
- "QUARANTINE PROCESSING COMPLETE",
479
- f" Promoted: {result['promoted']}",
480
- f" Rejected: {result['rejected']}",
481
- f" Expired: {result['expired']}",
482
- f" Still pending: {result['still_pending']}",
483
- f" Total: {result['total_processed']}",
484
- ]
485
- return "\n".join(lines)
486
-
487
-
488
- # ============================================================================
489
- # Prospective Memory trigger handlers (Feature 3)
490
- # ============================================================================
491
-
492
- def handle_cognitive_trigger_create(pattern: str, action: str, context: str = "") -> str:
493
- """Create a prospective memory trigger — fires when text matches pattern.
494
-
495
- Args:
496
- pattern: Keywords to match (case-insensitive, comma-separated for OR matching)
497
- action: What to do / remind about when the trigger fires
498
- context: Optional context about why this trigger was created
499
- """
500
- trigger_id = cognitive.create_trigger(pattern, action, context)
501
- return f"Trigger #{trigger_id} created — armed. Pattern: '{pattern}' | Action: '{action}'"
502
-
503
-
504
- def handle_cognitive_trigger_list(status: str = "armed") -> str:
505
- """List prospective memory triggers.
506
-
507
- Args:
508
- status: Filter — 'armed' (active, waiting), 'fired' (already triggered), 'all'
509
- """
510
- triggers = cognitive.list_triggers(status)
511
- if not triggers:
512
- return f"No {status} triggers found."
513
-
514
- lines = [f"PROSPECTIVE TRIGGERS ({status}) — {len(triggers)} total", ""]
515
- for t in triggers:
516
- status_icon = "+" if t["status"] == "armed" else "x"
517
- lines.append(f" [{status_icon}] #{t['id']} pattern='{t['trigger_pattern']}'")
518
- lines.append(f" action: {t['action']}")
519
- if t.get("context"):
520
- lines.append(f" context: {t['context']}")
521
- lines.append(f" created: {t['created_at'][:16]}")
522
- if t.get("fired_at"):
523
- lines.append(f" fired: {t['fired_at'][:16]}")
524
- lines.append("")
525
-
526
- return "\n".join(lines)
527
-
528
-
529
- def handle_cognitive_trigger_check(text: str, use_semantic: bool = False) -> str:
530
- """Check text against all armed triggers and fire matching ones.
531
-
532
- Args:
533
- text: Text to check against triggers (e.g. user message, heartbeat context)
534
- use_semantic: Also use embedding similarity (slower but catches conceptual matches)
535
- """
536
- fired = cognitive.check_triggers(text, use_semantic=use_semantic)
537
- if not fired:
538
- return "No triggers fired."
539
-
540
- lines = [f"TRIGGERS FIRED: {len(fired)}", ""]
541
- for t in fired:
542
- lines.append(f" #{t['id']} [{t['match_type']}] pattern='{t['pattern']}'")
543
- lines.append(f" ACTION: {t['action']}")
544
- if t.get("context"):
545
- lines.append(f" context: {t['context']}")
546
- lines.append("")
547
-
548
- return "\n".join(lines)
549
-
550
-
551
- def handle_cognitive_trigger_preview(text: str, use_semantic: bool = False) -> str:
552
- """Preview prospective trigger matches without firing them."""
553
- matches = cognitive.preview_triggers(text, use_semantic=use_semantic)
554
- if not matches:
555
- return "No anticipatory warnings."
556
-
557
- lines = [f"ANTICIPATORY WARNINGS: {len(matches)}", ""]
558
- for match in matches:
559
- lines.append(f" #{match['id']} [{match['match_type']}] pattern='{match['pattern']}'")
560
- lines.append(f" ACTION: {match['action']}")
561
- if match.get("context"):
562
- lines.append(f" context: {match['context']}")
563
- lines.append("")
564
-
565
- return "\n".join(lines)
566
-
567
-
568
- def handle_cognitive_trigger_delete(trigger_id: int) -> str:
569
- """Delete a prospective memory trigger.
570
-
571
- Args:
572
- trigger_id: ID of the trigger to delete
573
- """
574
- return cognitive.delete_trigger(trigger_id)
575
-
576
-
577
- def handle_cognitive_trigger_rearm(trigger_id: int) -> str:
578
- """Re-arm a fired trigger so it can fire again.
579
-
580
- Args:
581
- trigger_id: ID of the trigger to re-arm
582
- """
583
- return cognitive.rearm_trigger(trigger_id)
584
-
585
-
586
- TOOLS = [
587
- (handle_cognitive_retrieve, "nexo_cognitive_retrieve", "RAG query over cognitive memory (STM+LTM). Triggers rehearsal on retrieved results."),
588
- (handle_cognitive_stats, "nexo_cognitive_stats", "Cognitive memory system metrics: STM/LTM counts, strengths, retrieval stats, quarantine counts"),
589
- (handle_cognitive_inspect, "nexo_cognitive_inspect", "Inspect a specific memory by ID (debug). Does NOT trigger rehearsal."),
590
- (handle_cognitive_metrics, "nexo_cognitive_metrics", "Performance metrics: retrieval relevance %, repeat error rate, multilingual recommendation (spec section 9)"),
591
- (handle_cognitive_dissonance, "nexo_cognitive_dissonance", "Detect conflicts between a new instruction and established LTM memories. force=True to skip discussion."),
592
- (handle_cognitive_resolve, "nexo_cognitive_resolve", "Resolve a cognitive dissonance: paradigm_shift, exception, or override."),
593
- (handle_cognitive_sentiment, "nexo_cognitive_sentiment", "Detect user's sentiment and get tone guidance. Also logs for tracking."),
594
- (handle_cognitive_trust, "nexo_cognitive_trust", "View or adjust trust score (0-100). Without args: view. With event: adjust."),
595
- (handle_cognitive_pin, "nexo_cognitive_pin", "Pin a memory — never decays, boosted +0.2 in search results."),
596
- (handle_cognitive_snooze, "nexo_cognitive_snooze", "Snooze a memory — hidden from searches until a date, then auto-restores."),
597
- (handle_cognitive_archive, "nexo_cognitive_archive", "Archive a memory — excluded from searches, can be restored."),
598
- (handle_cognitive_restore, "nexo_cognitive_restore", "Restore a memory to active state (from pinned/snoozed/archived)."),
599
- (handle_cognitive_quarantine_list, "nexo_cognitive_quarantine_list", "List quarantine queue items awaiting promotion to STM."),
600
- (handle_cognitive_quarantine_promote, "nexo_cognitive_quarantine_promote", "Manually promote a quarantine item to STM."),
601
- (handle_cognitive_quarantine_reject, "nexo_cognitive_quarantine_reject", "Manually reject a quarantine item."),
602
- (handle_cognitive_quarantine_process, "nexo_cognitive_quarantine_process", "Run quarantine promotion cycle — evaluate pending items against policy."),
603
- (handle_cognitive_trigger_create, "nexo_cognitive_trigger_create", "Create a prospective memory trigger — 'when X is mentioned, remind about Y'."),
604
- (handle_cognitive_trigger_list, "nexo_cognitive_trigger_list", "List prospective triggers by status (armed/fired/all)."),
605
- (handle_cognitive_trigger_preview, "nexo_cognitive_trigger_preview", "Preview anticipatory trigger matches without firing them."),
606
- (handle_cognitive_trigger_check, "nexo_cognitive_trigger_check", "Check text against armed triggers. Returns fired triggers with actions."),
607
- (handle_cognitive_trigger_delete, "nexo_cognitive_trigger_delete", "Delete a prospective trigger by ID."),
608
- (handle_cognitive_trigger_rearm, "nexo_cognitive_trigger_rearm", "Re-arm a fired trigger so it can fire again."),
609
- ]