nexo-brain 5.3.26 → 5.3.27

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 (211) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/server.py +3 -0
  4. package/src/tools_sessions.py +6 -1
  5. package/src/dashboard/static/favicon 2.svg +0 -32
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +0 -40
  8. package/src/dashboard/static/style 2.css +0 -2458
  9. package/src/dashboard/templates/adaptive 2.html +0 -118
  10. package/src/dashboard/templates/artifacts 2.html +0 -133
  11. package/src/dashboard/templates/backups 2.html +0 -136
  12. package/src/dashboard/templates/base 2.html +0 -417
  13. package/src/dashboard/templates/calendar 2.html +0 -591
  14. package/src/dashboard/templates/chat 2.html +0 -356
  15. package/src/dashboard/templates/claims 2.html +0 -259
  16. package/src/dashboard/templates/cortex 2.html +0 -321
  17. package/src/dashboard/templates/credentials 2.html +0 -128
  18. package/src/dashboard/templates/crons 2.html +0 -370
  19. package/src/dashboard/templates/dashboard 2.html +0 -494
  20. package/src/dashboard/templates/dreams 2.html +0 -252
  21. package/src/dashboard/templates/email 2.html +0 -160
  22. package/src/dashboard/templates/evolution 2.html +0 -189
  23. package/src/dashboard/templates/feed 2.html +0 -249
  24. package/src/dashboard/templates/followup_health 2.html +0 -170
  25. package/src/dashboard/templates/graph 2.html +0 -201
  26. package/src/dashboard/templates/guard 2.html +0 -259
  27. package/src/dashboard/templates/inbox 2.html +0 -251
  28. package/src/dashboard/templates/memory 2.html +0 -420
  29. package/src/dashboard/templates/operations 2.html +0 -608
  30. package/src/dashboard/templates/plugins 2.html +0 -185
  31. package/src/dashboard/templates/protocol 2.html +0 -199
  32. package/src/dashboard/templates/rules 2.html +0 -246
  33. package/src/dashboard/templates/sentiment 2.html +0 -247
  34. package/src/dashboard/templates/sessions 2.html +0 -218
  35. package/src/dashboard/templates/skills 2.html +0 -329
  36. package/src/dashboard/templates/somatic 2.html +0 -73
  37. package/src/dashboard/templates/triggers 2.html +0 -133
  38. package/src/dashboard/templates/trust 2.html +0 -360
  39. package/src/db/__init__ 2.py +0 -259
  40. package/src/db/_core 2.py +0 -437
  41. package/src/db/_credentials 2.py +0 -124
  42. package/src/db/_episodic 2.py +0 -762
  43. package/src/db/_evolution 2.py +0 -54
  44. package/src/db/_fts 2.py +0 -406
  45. package/src/db/_goal_profiles 2.py +0 -376
  46. package/src/db/_hot_context 2.py +0 -660
  47. package/src/db/_outcomes 2.py +0 -800
  48. package/src/db/_personal_scripts 2.py +0 -582
  49. package/src/db/_sessions 2.py +0 -330
  50. package/src/db/_tasks 2.py +0 -91
  51. package/src/db/_watchers 2.py +0 -173
  52. package/src/doctor/formatters 2.py +0 -52
  53. package/src/doctor/models 2.py +0 -69
  54. package/src/doctor/planes 2.py +0 -87
  55. package/src/doctor/providers/__init__ 2.py +0 -1
  56. package/src/doctor/providers/deep 2.py +0 -367
  57. package/src/evolution_cycle 2.py +0 -519
  58. package/src/hooks/auto_capture 2.py +0 -208
  59. package/src/hooks/caffeinate-guard 2.sh +0 -8
  60. package/src/hooks/capture-session 2.sh +0 -21
  61. package/src/hooks/capture-tool-logs 2.sh +0 -158
  62. package/src/hooks/daily-briefing-check 2.sh +0 -33
  63. package/src/hooks/heartbeat-enforcement 2.py +0 -90
  64. package/src/hooks/heartbeat-posttool 2.sh +0 -18
  65. package/src/hooks/inbox-hook 2.sh +0 -76
  66. package/src/hooks/post-compact 2.sh +0 -152
  67. package/src/hooks/pre-compact 2.sh +0 -169
  68. package/src/hooks/protocol-guardrail 2.sh +0 -10
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +0 -9
  70. package/src/hooks/session-stop 2.sh +0 -52
  71. package/src/kg_populate 2.py +0 -292
  72. package/src/maintenance 2.py +0 -53
  73. package/src/memory_backends 2.py +0 -71
  74. package/src/migrate_embeddings 2.py +0 -124
  75. package/src/nexo_sdk 2.py +0 -103
  76. package/src/observability 2.py +0 -199
  77. package/src/plugin_loader 2.py +0 -217
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +0 -450
  80. package/src/plugins/backup 2.py +0 -127
  81. package/src/plugins/claims_tools 2.py +0 -119
  82. package/src/plugins/cognitive_memory 2.py +0 -609
  83. package/src/plugins/core_rules 2.py +0 -252
  84. package/src/plugins/cortex 2.py +0 -1155
  85. package/src/plugins/entities 2.py +0 -67
  86. package/src/plugins/episodic_memory 2.py +0 -560
  87. package/src/plugins/evolution 2.py +0 -167
  88. package/src/plugins/goal_engine 2.py +0 -142
  89. package/src/plugins/guard 2.py +0 -862
  90. package/src/plugins/impact 2.py +0 -29
  91. package/src/plugins/knowledge_graph_tools 2.py +0 -137
  92. package/src/plugins/media_memory_tools 2.py +0 -98
  93. package/src/plugins/memory_export 2.py +0 -196
  94. package/src/plugins/outcomes 2.py +0 -130
  95. package/src/plugins/personal_scripts 2.py +0 -117
  96. package/src/plugins/preferences 2.py +0 -47
  97. package/src/plugins/protocol 2.py +0 -1449
  98. package/src/plugins/simple_api 2.py +0 -106
  99. package/src/plugins/skills 2.py +0 -341
  100. package/src/plugins/state_watchers 2.py +0 -79
  101. package/src/plugins/update 2.py +0 -986
  102. package/src/plugins/user_state_tools 2.py +0 -43
  103. package/src/plugins/workflow 2.py +0 -588
  104. package/src/protocol_settings 2.py +0 -59
  105. package/src/public_contribution 2.py +0 -466
  106. package/src/public_evolution_queue 2.py +0 -241
  107. package/src/requirements 2.txt +0 -14
  108. package/src/retroactive_learnings 2.py +0 -373
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +0 -331
  111. package/src/rules/migrate 2.py +0 -207
  112. package/src/runtime_power 2.py +0 -874
  113. package/src/script_registry 2.py +0 -1559
  114. package/src/scripts/check-context 2.py +0 -272
  115. package/src/scripts/deep-sleep/apply_findings 2.py +0 -2327
  116. package/src/scripts/deep-sleep/collect 2.py +0 -928
  117. package/src/scripts/deep-sleep/extract 2.py +0 -330
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +0 -285
  119. package/src/scripts/deep-sleep/synthesize 2.py +0 -312
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +0 -336
  121. package/src/scripts/nexo-agent-run 2.py +0 -75
  122. package/src/scripts/nexo-auto-update 2.py +0 -6
  123. package/src/scripts/nexo-backup 2.sh +0 -25
  124. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  125. package/src/scripts/nexo-catchup 2.py +0 -300
  126. package/src/scripts/nexo-cognitive-decay 2.py +0 -257
  127. package/src/scripts/nexo-cortex-cycle 2.py +0 -293
  128. package/src/scripts/nexo-cron-wrapper 2.sh +0 -53
  129. package/src/scripts/nexo-daily-self-audit 2.py +0 -2161
  130. package/src/scripts/nexo-dashboard 2.sh +0 -29
  131. package/src/scripts/nexo-deep-sleep 2.sh +0 -86
  132. package/src/scripts/nexo-evolution-run 2.py +0 -1664
  133. package/src/scripts/nexo-followup-hygiene 2.py +0 -139
  134. package/src/scripts/nexo-hook-record 2.py +0 -42
  135. package/src/scripts/nexo-immune 2.py +0 -936
  136. package/src/scripts/nexo-impact-scorer 2.py +0 -117
  137. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  138. package/src/scripts/nexo-install 2.py +0 -6
  139. package/src/scripts/nexo-learning-housekeep 2.py +0 -401
  140. package/src/scripts/nexo-learning-validator 2.py +0 -266
  141. package/src/scripts/nexo-migrate 2.py +0 -260
  142. package/src/scripts/nexo-outcome-checker 2.py +0 -127
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -456
  144. package/src/scripts/nexo-pre-commit 2.py +0 -120
  145. package/src/scripts/nexo-prevent-sleep 2.sh +0 -35
  146. package/src/scripts/nexo-proactive-dashboard 2.py +0 -354
  147. package/src/scripts/nexo-reflection 2.py +0 -256
  148. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  149. package/src/scripts/nexo-sleep 2.py +0 -631
  150. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  151. package/src/scripts/nexo-sync-clients 2.py +0 -16
  152. package/src/scripts/nexo-synthesis 2.py +0 -475
  153. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  154. package/src/scripts/nexo-update 2.sh +0 -306
  155. package/src/scripts/nexo-watchdog 2.sh +0 -1207
  156. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +0 -245
  158. package/src/server 2.py +0 -1296
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +0 -43
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +0 -59
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +0 -17
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +0 -276
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +0 -58
  164. package/src/skills/run-release-final-audit/guide 2.md +0 -16
  165. package/src/skills/run-release-final-audit/script 2.py +0 -259
  166. package/src/skills/run-release-final-audit/skill 2.json +0 -77
  167. package/src/skills/run-runtime-doctor/guide 2.md +0 -12
  168. package/src/skills/run-runtime-doctor/script 2.py +0 -21
  169. package/src/skills/run-runtime-doctor/skill 2.json +0 -25
  170. package/src/skills_runtime 2.py +0 -932
  171. package/src/state_watchers_runtime 2.py +0 -475
  172. package/src/storage_router 2.py +0 -32
  173. package/src/system_catalog 2.py +0 -786
  174. package/src/tools_coordination 2.py +0 -103
  175. package/src/tools_credentials 2.py +0 -68
  176. package/src/tools_drive 2.py +0 -487
  177. package/src/tools_hot_context 2.py +0 -163
  178. package/src/tools_learnings 2.py +0 -612
  179. package/src/tools_menu 2.py +0 -229
  180. package/src/tools_reminders 2.py +0 -88
  181. package/src/tools_reminders_crud 2.py +0 -363
  182. package/src/tools_sessions 2.py +0 -1054
  183. package/src/tools_system_catalog 2.py +0 -19
  184. package/src/tools_task_history 2.py +0 -57
  185. package/src/tools_transcripts 2.py +0 -98
  186. package/src/transcript_utils 2.py +0 -412
  187. package/src/user_context 2.py +0 -46
  188. package/src/user_data_portability 2.py +0 -328
  189. package/src/user_state_model 2.py +0 -170
  190. package/templates/CLAUDE.md 2.template +0 -108
  191. package/templates/CODEX.AGENTS.md 2.template +0 -66
  192. package/templates/launchagents/README 2.md +0 -132
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +0 -39
  194. package/templates/launchagents/com.nexo.catchup 2.plist +0 -39
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +0 -40
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +0 -43
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +0 -43
  198. package/templates/launchagents/com.nexo.evolution 2.plist +0 -44
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +0 -45
  200. package/templates/launchagents/com.nexo.immune 2.plist +0 -41
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +0 -45
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +0 -47
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +0 -45
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +0 -37
  205. package/templates/nexo_helper 2.py +0 -301
  206. package/templates/openclaw 2.json +0 -13
  207. package/templates/plugin-template 2.py +0 -40
  208. package/templates/script-template 2.py +0 -59
  209. package/templates/script-template 2.sh +0 -13
  210. package/templates/skill-script-template 2.py +0 -48
  211. package/templates/skill-template 2.md +0 -33
@@ -1,29 +0,0 @@
1
- """Impact scoring plugin — prioritize followups by expected impact, not only by date."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
-
7
- from db import get_followup, score_followup
8
-
9
-
10
- def handle_impact_score(followup_id: str) -> str:
11
- """Compute and persist Impact Scoring v1 for one followup."""
12
- row = get_followup(followup_id)
13
- if not row:
14
- return f"ERROR: Followup {followup_id} not found."
15
- scored = score_followup(followup_id)
16
- if "error" in scored:
17
- return f"ERROR: {scored['error']}"
18
- payload = {
19
- "followup_id": followup_id,
20
- "impact_score": scored.get("impact_score", 0),
21
- "factors": scored.get("impact_factors", {}),
22
- "reasoning": scored.get("impact_reasoning", ""),
23
- }
24
- return json.dumps(payload, ensure_ascii=False, indent=2)
25
-
26
-
27
- TOOLS = [
28
- (handle_impact_score, "nexo_impact_score", "Compute and persist Impact Scoring v1 for a followup so queues can prioritize by expected impact."),
29
- ]
@@ -1,137 +0,0 @@
1
- """Knowledge Graph MCP tools — query, path, neighbors, stats."""
2
- import os
3
- import sys
4
-
5
- sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
6
- import knowledge_graph as kg
7
-
8
-
9
- def _find_node(node_type: str, node_ref: str):
10
- """Find a node, trying both raw ref and type-prefixed ref (area:X, file:X)."""
11
- node = kg.get_node(node_type, node_ref)
12
- if not node:
13
- node = kg.get_node(node_type, f"{node_type}:{node_ref}")
14
- return node
15
-
16
-
17
- def handle_kg_query(node_type: str, node_ref: str, depth: int = 2, relation: str = "") -> str:
18
- """Traverse the knowledge graph from a node up to `depth` hops."""
19
- node = _find_node(node_type, node_ref)
20
- if not node:
21
- return f"Node not found: {node_type}/{node_ref}"
22
- result = kg.traverse(node["id"], max_depth=depth, relation_filter=relation or None)
23
- nodes = result["nodes"][:30]
24
- edges = result["edges"][:30]
25
- lines = [f"KG TRAVERSE — {node['label']} ({node_type}/{node_ref}) depth={depth}"]
26
- lines.append(f"Nodes: {len(result['nodes'])} Edges: {len(result['edges'])}")
27
- lines.append("")
28
- lines.append("NODES:")
29
- for n in nodes:
30
- indent = " " * n.get("depth", 0)
31
- lines.append(f" {indent}[{n['id']}] ({n['node_type']}) {n['label']} — {n['node_ref']}")
32
- lines.append("")
33
- lines.append("EDGES:")
34
- for e in edges[:20]:
35
- lines.append(f" [{e['source_id']}] --{e['relation']}--> [{e['target_id']}] w={e['weight']}")
36
- return "\n".join(lines)
37
-
38
-
39
- def handle_kg_path(from_type: str, from_ref: str, to_type: str, to_ref: str) -> str:
40
- """Find the shortest path between two nodes in the knowledge graph."""
41
- from_node = _find_node(from_type, from_ref)
42
- if not from_node:
43
- return f"Source node not found: {from_type}/{from_ref}"
44
- to_node = _find_node(to_type, to_ref)
45
- if not to_node:
46
- return f"Target node not found: {to_type}/{to_ref}"
47
- path_ids = kg.shortest_path(from_node["id"], to_node["id"])
48
- if not path_ids:
49
- return f"No path found between {from_ref} and {to_ref}"
50
- lines = [f"PATH ({len(path_ids) - 1} hops): {from_ref} → {to_ref}"]
51
- for i, nid in enumerate(path_ids):
52
- node = kg.get_node_by_id(nid)
53
- label = node["label"] if node else f"[{nid}]"
54
- ntype = node["node_type"] if node else "?"
55
- lines.append(f" {i}. [{nid}] ({ntype}) {label}")
56
- return "\n".join(lines)
57
-
58
-
59
- def handle_kg_neighbors(node_type: str, node_ref: str, relation: str = "") -> str:
60
- """Get direct neighbors of a node, optionally filtered by relation type."""
61
- node = _find_node(node_type, node_ref)
62
- if not node:
63
- return f"Node not found: {node_type}/{node_ref}"
64
- neighbors = kg.get_neighbors(node["id"], relation=relation or None)
65
- if not neighbors:
66
- rel_info = f" (relation={relation})" if relation else ""
67
- return f"No neighbors found for {node['label']}{rel_info}"
68
- lines = [f"NEIGHBORS of [{node['id']}] {node['label']} ({len(neighbors)} total):"]
69
- for n in neighbors[:30]:
70
- direction = n.get("direction", "?")
71
- arrow = "-->" if direction == "outgoing" else "<--"
72
- lines.append(f" {arrow} [{n['id']}] {n['label']} ({n['node_type']}) rel={n['relation']} w={n['weight']}")
73
- if len(neighbors) > 30:
74
- lines.append(f" ... +{len(neighbors) - 30} more")
75
- return "\n".join(lines)
76
-
77
-
78
- def handle_kg_stats() -> str:
79
- """Return knowledge graph statistics: node counts, edge counts, top connected nodes."""
80
- s = kg.stats()
81
- lines = ["KNOWLEDGE GRAPH STATS"]
82
- lines.append(f" Nodes: {s['nodes']}")
83
- lines.append(f" Edges (active): {s['edges_active']}")
84
- lines.append(f" Edges (historical): {s['edges_historical']}")
85
- if s["node_types"]:
86
- lines.append("\nNODE TYPES:")
87
- for t, cnt in sorted(s["node_types"].items(), key=lambda x: -x[1]):
88
- lines.append(f" {t}: {cnt}")
89
- if s["relation_types"]:
90
- lines.append("\nRELATION TYPES:")
91
- for r, cnt in sorted(s["relation_types"].items(), key=lambda x: -x[1])[:20]:
92
- lines.append(f" {r}: {cnt}")
93
- if s["most_connected"]:
94
- lines.append("\nMOST CONNECTED:")
95
- for n in s["most_connected"][:10]:
96
- lines.append(f" [{n['id']}] {n['label']} ({n['node_type']}) — {n['connections']} connections")
97
- return "\n".join(lines)
98
-
99
-
100
- def handle_kg_export(format: str = "jsonld", as_of: str = "") -> str:
101
- """Export the bitemporal knowledge graph to a standard interchange format.
102
-
103
- Closes Fase 5 item 1 of NEXO-AUDIT-2026-04-11. The KG was already
104
- bitemporal (kg_edges has valid_from and valid_until and the
105
- upsert/delete helpers maintain them), but had no way to emit the
106
- graph in a format external tools can ingest. This tool wraps the
107
- two canonical exporters in cognitive.knowledge_graph.
108
-
109
- Args:
110
- format: 'jsonld' (default, semantic web / human-readable) or
111
- 'graphml' (igraph, Gephi, NetworkX, Cytoscape).
112
- as_of: Optional ISO timestamp. If empty, exports the active
113
- snapshot. If provided, exports the historical snapshot
114
- that was valid at that instant.
115
- """
116
- import json as _json
117
- import knowledge_graph as kg
118
-
119
- fmt = (format or "jsonld").strip().lower()
120
- if fmt == "jsonld":
121
- payload = kg.export_to_jsonld(as_of=as_of)
122
- return _json.dumps(payload, ensure_ascii=False, indent=2)
123
- if fmt == "graphml":
124
- return kg.export_to_graphml(as_of=as_of)
125
- return _json.dumps(
126
- {"ok": False, "error": f"unsupported format: {format!r} (use jsonld or graphml)"},
127
- ensure_ascii=False,
128
- )
129
-
130
-
131
- TOOLS = [
132
- (handle_kg_query, "nexo_kg_query", "Query knowledge graph — traverse from a node"),
133
- (handle_kg_path, "nexo_kg_path", "Find shortest path between two nodes"),
134
- (handle_kg_neighbors, "nexo_kg_neighbors", "Get direct neighbors of a node"),
135
- (handle_kg_stats, "nexo_kg_stats", "Knowledge graph statistics"),
136
- (handle_kg_export, "nexo_kg_export", "Export the bitemporal KG to JSON-LD or GraphML (active snapshot or historical via as_of)"),
137
- ]
@@ -1,98 +0,0 @@
1
- """Multimodal memory reference tools."""
2
-
3
- import media_memory
4
-
5
-
6
- def handle_media_memory_add(
7
- file_path: str = "",
8
- url: str = "",
9
- title: str = "",
10
- description: str = "",
11
- tags: str = "",
12
- domain: str = "",
13
- source_type: str = "",
14
- source_id: str = "",
15
- metadata: str = "",
16
- ) -> str:
17
- result = media_memory.add_media_memory(
18
- file_path=file_path,
19
- url=url,
20
- title=title,
21
- description=description,
22
- tags=tags,
23
- domain=domain,
24
- source_type=source_type,
25
- source_id=source_id,
26
- metadata=metadata,
27
- )
28
- if result.get("error"):
29
- return f"ERROR: {result['error']}"
30
- location = result.get("file_path") or result.get("url") or "n/a"
31
- return f"Media memory #{result['id']} [{result['media_type']}] stored: {location}"
32
-
33
-
34
- def handle_media_memory_search(
35
- query: str = "",
36
- media_type: str = "",
37
- domain: str = "",
38
- tag: str = "",
39
- limit: int = 20,
40
- ) -> str:
41
- items = media_memory.search_media_memories(
42
- query=query,
43
- media_type=media_type,
44
- domain=domain,
45
- tag=tag,
46
- limit=limit,
47
- )
48
- if not items:
49
- return "No media memories found."
50
- lines = [f"MEDIA MEMORIES — {len(items)} result(s):", ""]
51
- for item in items:
52
- lines.append(f" #{item['id']} [{item['media_type']}] {item['title'][:120]}")
53
- lines.append(f" {item.get('file_path') or item.get('url') or 'n/a'}")
54
- if item.get("description"):
55
- lines.append(f" {item['description'][:180]}")
56
- if item.get("tags"):
57
- lines.append(f" tags: {item['tags']}")
58
- return "\n".join(lines)
59
-
60
-
61
- def handle_media_memory_get(media_id: int) -> str:
62
- item = media_memory.get_media_memory(media_id)
63
- if not item:
64
- return f"Media memory #{media_id} not found."
65
- lines = [
66
- f"MEDIA MEMORY #{item['id']}",
67
- f" type: {item['media_type']}",
68
- f" title: {item['title']}",
69
- f" location: {item.get('file_path') or item.get('url') or 'n/a'}",
70
- f" domain: {item.get('domain') or 'n/a'}",
71
- f" source: {item.get('source_type') or 'n/a'}:{item.get('source_id') or ''}",
72
- ]
73
- if item.get("description"):
74
- lines.append(f" description: {item['description']}")
75
- if item.get("tags"):
76
- lines.append(f" tags: {item['tags']}")
77
- if item.get("metadata"):
78
- lines.append(f" metadata: {item['metadata']}")
79
- return "\n".join(lines)
80
-
81
-
82
- def handle_media_memory_stats() -> str:
83
- stats = media_memory.media_memory_stats()
84
- return (
85
- "MEDIA MEMORY STATS\n"
86
- f" total: {stats['total']}\n"
87
- f" backend: {stats['backend']}\n"
88
- f" by_type: {stats['by_type']}\n"
89
- f" by_domain: {stats['by_domain']}"
90
- )
91
-
92
-
93
- TOOLS = [
94
- (handle_media_memory_add, "nexo_media_memory_add", "Store a non-text artifact as first-class media memory metadata."),
95
- (handle_media_memory_search, "nexo_media_memory_search", "Search media memories by text, type, tag, or domain."),
96
- (handle_media_memory_get, "nexo_media_memory_get", "Inspect one stored media memory."),
97
- (handle_media_memory_stats, "nexo_media_memory_stats", "Stats for the multimodal/media memory layer."),
98
- ]
@@ -1,196 +0,0 @@
1
- """Readable export and auto-flush inspection tools."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import os
7
- from datetime import datetime
8
- from pathlib import Path
9
-
10
- import cognitive
11
- import claim_graph
12
- import compaction_memory
13
- import media_memory
14
- import user_state_model
15
- from db import get_db
16
- from memory_backends import get_backend, list_backends
17
-
18
-
19
- def _nexo_home() -> Path:
20
- return Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo"))).expanduser()
21
-
22
-
23
- def _write(path: Path, text: str) -> None:
24
- path.parent.mkdir(parents=True, exist_ok=True)
25
- path.write_text(text, encoding="utf-8")
26
-
27
-
28
- def handle_auto_flush_recent(limit: int = 20, session_id: str = "") -> str:
29
- rows = compaction_memory.list_auto_flushes(session_id=session_id, limit=limit)
30
- if not rows:
31
- return "No auto-flush records."
32
- lines = [f"AUTO-FLUSH — {len(rows)} record(s):", ""]
33
- for row in rows:
34
- lines.append(f" #{row['id']} {row['created_at']} [{row.get('session_id','unknown')}]")
35
- lines.append(f" {row.get('summary','')[:220]}")
36
- if row.get("next_step"):
37
- lines.append(f" next: {row['next_step'][:160]}")
38
- return "\n".join(lines)
39
-
40
-
41
- def handle_auto_flush_stats(days: int = 7) -> str:
42
- stats = compaction_memory.auto_flush_stats(days=days)
43
- return (
44
- f"AUTO-FLUSH STATS — {stats['window_days']}d\n"
45
- f" total: {stats['total']}\n"
46
- f" backend: {stats['backend']}\n"
47
- f" by_source: {stats['by_source']}"
48
- )
49
-
50
-
51
- def handle_memory_backend_status() -> str:
52
- active = get_backend()
53
- backends = list_backends()
54
- lines = [
55
- f"MEMORY BACKEND: {active.key} — {active.label}",
56
- f"Description: {active.description}",
57
- f"Supports: {', '.join(active.supports)}",
58
- "",
59
- "Registered backends:",
60
- ]
61
- for item in backends:
62
- marker = "*" if item["active"] else "-"
63
- lines.append(f" {marker} {item['key']} [{item['maturity']}] {item['label']}")
64
- return "\n".join(lines)
65
-
66
-
67
- def handle_memory_export(format: str = "markdown", output_dir: str = "") -> str:
68
- if format.strip().lower() != "markdown":
69
- return "ERROR: only markdown export is supported for now."
70
-
71
- stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
72
- root = Path(output_dir).expanduser() if output_dir.strip() else (_nexo_home() / "exports" / "memory" / stamp)
73
- root.mkdir(parents=True, exist_ok=True)
74
-
75
- conn = get_db()
76
- learnings = [dict(r) for r in conn.execute("SELECT id, category, title, status, prevention, updated_at FROM learnings ORDER BY updated_at DESC LIMIT 50").fetchall()]
77
- decisions = [dict(r) for r in conn.execute("SELECT id, domain, decision, confidence, status, created_at FROM decisions ORDER BY created_at DESC LIMIT 50").fetchall()]
78
- claims = claim_graph.search_claims(limit=100)
79
- claim_lint = claim_graph.lint_claims(limit=50)
80
- media = media_memory.list_media_memories(limit=100)
81
- flushes = compaction_memory.list_auto_flushes(limit=100)
82
- user_state = user_state_model.build_user_state(days=7, persist=False)
83
- user_history = user_state_model.list_user_state_snapshots(limit=30)
84
- cognitive_stats = cognitive.get_stats()
85
-
86
- _write(
87
- root / "README.md",
88
- "\n".join(
89
- [
90
- "# NEXO Memory Export",
91
- "",
92
- f"- Generated: {datetime.now().isoformat(timespec='seconds')}",
93
- f"- Backend: {get_backend().key}",
94
- f"- Learnings: {len(learnings)}",
95
- f"- Decisions: {len(decisions)}",
96
- f"- Claims: {len(claims)}",
97
- f"- Media memories: {len(media)}",
98
- f"- Auto-flush records: {len(flushes)}",
99
- "",
100
- "Files:",
101
- "- `learnings.md`",
102
- "- `decisions.md`",
103
- "- `claims.md`",
104
- "- `media.md`",
105
- "- `auto-flush.md`",
106
- "- `user-state.md`",
107
- "- `cognitive.json`",
108
- ]
109
- ),
110
- )
111
- _write(
112
- root / "learnings.md",
113
- "\n".join(
114
- ["# Learnings", ""]
115
- + [
116
- f"- #{item['id']} [{item.get('category','general')}] {item['title']} "
117
- f"({item.get('status','active')}, updated {item.get('updated_at','')})"
118
- for item in learnings
119
- ]
120
- ),
121
- )
122
- _write(
123
- root / "decisions.md",
124
- "\n".join(
125
- ["# Decisions", ""]
126
- + [
127
- f"- #{item['id']} [{item.get('domain','other')}] {item['decision']} "
128
- f"({item.get('confidence','medium')}, {item.get('status','pending_review')})"
129
- for item in decisions
130
- ]
131
- ),
132
- )
133
- _write(
134
- root / "claims.md",
135
- "\n".join(
136
- ["# Claims", ""]
137
- + [
138
- f"- #{item['id']} [{item.get('verification_status','unverified')}] "
139
- f"{item.get('freshness_state','?')}({item.get('freshness_score',0)}): {item['text']}"
140
- for item in claims
141
- ]
142
- + ["", "## Attention", ""]
143
- + [
144
- f"- #{item['id']} [{', '.join(item.get('lint_reasons', []))}] {item['text']}"
145
- for item in claim_lint
146
- ]
147
- ),
148
- )
149
- _write(
150
- root / "media.md",
151
- "\n".join(
152
- ["# Media Memory", ""]
153
- + [
154
- f"- #{item['id']} [{item['media_type']}] {item['title']} :: {item.get('file_path') or item.get('url') or 'n/a'}"
155
- for item in media
156
- ]
157
- ),
158
- )
159
- _write(
160
- root / "auto-flush.md",
161
- "\n".join(
162
- ["# Auto Flush", ""]
163
- + [
164
- f"- #{item['id']} [{item.get('session_id','unknown')}] {item.get('created_at','')}: "
165
- f"{item.get('summary','')}"
166
- for item in flushes
167
- ]
168
- ),
169
- )
170
- _write(
171
- root / "user-state.md",
172
- "\n".join(
173
- [
174
- "# User State",
175
- "",
176
- f"- Current: {user_state['state_label']} ({user_state['confidence']})",
177
- f"- Trust: {user_state['trust_score']}",
178
- f"- Guidance: {user_state['guidance']}",
179
- "",
180
- "## Signals",
181
- ]
182
- + [f"- {key}: {value}" for key, value in user_state["signals"].items()]
183
- + ["", "## History", ""]
184
- + [f"- {item['created_at']} :: {item['state_label']} ({item['confidence']})" for item in user_history]
185
- ),
186
- )
187
- _write(root / "cognitive.json", json.dumps(cognitive_stats, indent=2, sort_keys=True))
188
- return f"Memory export written to {root}"
189
-
190
-
191
- TOOLS = [
192
- (handle_auto_flush_recent, "nexo_auto_flush_recent", "Show recent structured auto-flush records written before compaction."),
193
- (handle_auto_flush_stats, "nexo_auto_flush_stats", "Stats for pre-compaction auto-flush activity."),
194
- (handle_memory_backend_status, "nexo_memory_backend_status", "Show the active memory backend contract and registered backend list."),
195
- (handle_memory_export, "nexo_memory_export", "Export a readable markdown snapshot of key NEXO memory layers."),
196
- ]
@@ -1,130 +0,0 @@
1
- """Outcome tracker plugin — close action -> expected result -> actual result loops."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
-
7
- from db import (
8
- create_outcome,
9
- get_outcome,
10
- list_outcomes,
11
- cancel_outcome,
12
- evaluate_outcome,
13
- list_outcome_pattern_candidates,
14
- capture_outcome_pattern,
15
- )
16
-
17
-
18
- def _format_outcome_row(row: dict) -> str:
19
- actual = row.get("actual_value")
20
- actual_display = (
21
- str(actual)
22
- if actual is not None
23
- else (row.get("actual_value_text") or "—")
24
- )
25
- return (
26
- f"#{row['id']} [{row.get('status','pending')}] "
27
- f"{row.get('action_type','custom')}:{row.get('action_id','—') or '—'} "
28
- f"deadline={row.get('deadline','—')} "
29
- f"expected={row.get('expected_result','')[:80]} "
30
- f"actual={str(actual_display)[:80]}"
31
- )
32
-
33
-
34
- def handle_outcome_register(
35
- action_type: str,
36
- description: str,
37
- expected_result: str,
38
- metric_source: str = "manual",
39
- metric_query: str = "",
40
- baseline: float | None = None,
41
- target: float | None = None,
42
- target_op: str = "gte",
43
- deadline: str = "",
44
- action_id: str = "",
45
- session_id: str = "",
46
- notes: str = "",
47
- ) -> str:
48
- """Register an expected outcome for an action so NEXO can verify it later."""
49
- result = create_outcome(
50
- action_type=action_type,
51
- description=description,
52
- expected_result=expected_result,
53
- metric_source=metric_source,
54
- metric_query=metric_query,
55
- baseline_value=baseline,
56
- target_value=target,
57
- target_operator=target_op,
58
- deadline=deadline,
59
- action_id=action_id,
60
- session_id=session_id,
61
- notes=notes,
62
- )
63
- if "error" in result:
64
- return f"ERROR: {result['error']}"
65
- return (
66
- f"Outcome #{result['id']} registered [{result['status']}]. "
67
- f"deadline={result['deadline']} metric_source={result['metric_source']}"
68
- )
69
-
70
-
71
- def handle_outcome_check(
72
- id: int,
73
- actual_value: float | None = None,
74
- actual_value_text: str = "",
75
- create_learning_on_miss: bool = True,
76
- ) -> str:
77
- """Check one outcome now and update its status using linked state or supplied evidence."""
78
- result = evaluate_outcome(
79
- int(id),
80
- actual_value=actual_value,
81
- actual_value_text=actual_value_text,
82
- create_learning_on_miss=bool(create_learning_on_miss),
83
- )
84
- if "error" in result:
85
- return f"ERROR: {result['error']}"
86
- notes = result.get("notes") or ""
87
- learning = f" learning_id={result.get('learning_id')}" if result.get("learning_id") else ""
88
- return f"{_format_outcome_row(result)}{learning}\nnotes={notes[:300]}"
89
-
90
-
91
- def handle_outcome_list(status: str = "", action_type: str = "", limit: int = 20) -> str:
92
- """List outcomes by status and/or action type."""
93
- rows = list_outcomes(status=status, action_type=action_type, limit=limit)
94
- if not rows:
95
- scope = f"status={status or 'any'} action_type={action_type or 'any'}"
96
- return f"No outcomes found ({scope})."
97
- header = f"OUTCOMES ({len(rows)})"
98
- return "\n".join([header] + [f" {_format_outcome_row(row)}" for row in rows])
99
-
100
-
101
- def handle_outcome_cancel(id: int, reason: str = "") -> str:
102
- """Cancel a pending outcome so it no longer blocks reviews or release loops."""
103
- result = cancel_outcome(int(id), reason=reason)
104
- if "error" in result:
105
- return f"ERROR: {result['error']}"
106
- return f"Outcome #{result['id']} cancelled."
107
-
108
-
109
- def handle_outcome_pattern_candidates(min_resolved: int = 3, limit: int = 10) -> str:
110
- """List repeated resolved outcome patterns that are strong enough to become reusable knowledge."""
111
- candidates = list_outcome_pattern_candidates(min_resolved=min_resolved, limit=limit)
112
- return json.dumps({"ok": True, "candidates": candidates}, ensure_ascii=False, indent=2)
113
-
114
-
115
- def handle_outcome_pattern_capture(pattern_key: str, target: str = "learning", category: str = "outcomes") -> str:
116
- """Materialize one repeated outcome pattern into a reusable artifact (currently a learning)."""
117
- result = capture_outcome_pattern(pattern_key=pattern_key, target=target, category=category)
118
- if "error" in result:
119
- return json.dumps({"ok": False, "error": result["error"]}, ensure_ascii=False, indent=2)
120
- return json.dumps(result, ensure_ascii=False, indent=2)
121
-
122
-
123
- TOOLS = [
124
- (handle_outcome_register, "nexo_outcome_register", "Register an expected action outcome with metric source, deadline, and optional link to decision/followup/task."),
125
- (handle_outcome_check, "nexo_outcome_check", "Check a tracked outcome now using linked state or supplied evidence; marks pending/met/missed and may create a learning on miss."),
126
- (handle_outcome_list, "nexo_outcome_list", "List tracked outcomes filtered by status and/or action type."),
127
- (handle_outcome_cancel, "nexo_outcome_cancel", "Cancel an outcome so it stops blocking pending/missed reviews."),
128
- (handle_outcome_pattern_candidates, "nexo_outcome_pattern_candidates", "List repeated resolved outcome patterns from cortex-linked decisions that are consistent enough to become reusable knowledge."),
129
- (handle_outcome_pattern_capture, "nexo_outcome_pattern_capture", "Capture one repeated outcome pattern as a reusable artifact (currently a learning) once the evidence is consistent."),
130
- ]