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,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
- ]