nexo-brain 5.3.19 → 5.3.21

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/bin/nexo-brain.js +52 -10
  3. package/package.json +1 -1
  4. package/src/auto_update.py +11 -8
  5. package/src/dashboard/static/favicon 2.svg +32 -0
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +40 -0
  8. package/src/dashboard/static/style 2.css +2458 -0
  9. package/src/dashboard/templates/adaptive 2.html +118 -0
  10. package/src/dashboard/templates/artifacts 2.html +133 -0
  11. package/src/dashboard/templates/backups 2.html +136 -0
  12. package/src/dashboard/templates/base 2.html +417 -0
  13. package/src/dashboard/templates/calendar 2.html +591 -0
  14. package/src/dashboard/templates/chat 2.html +356 -0
  15. package/src/dashboard/templates/claims 2.html +259 -0
  16. package/src/dashboard/templates/cortex 2.html +321 -0
  17. package/src/dashboard/templates/credentials 2.html +128 -0
  18. package/src/dashboard/templates/crons 2.html +370 -0
  19. package/src/dashboard/templates/dashboard 2.html +494 -0
  20. package/src/dashboard/templates/dreams 2.html +252 -0
  21. package/src/dashboard/templates/email 2.html +160 -0
  22. package/src/dashboard/templates/evolution 2.html +189 -0
  23. package/src/dashboard/templates/feed 2.html +249 -0
  24. package/src/dashboard/templates/followup_health 2.html +170 -0
  25. package/src/dashboard/templates/graph 2.html +201 -0
  26. package/src/dashboard/templates/guard 2.html +259 -0
  27. package/src/dashboard/templates/inbox 2.html +251 -0
  28. package/src/dashboard/templates/memory 2.html +420 -0
  29. package/src/dashboard/templates/operations 2.html +608 -0
  30. package/src/dashboard/templates/plugins 2.html +185 -0
  31. package/src/dashboard/templates/protocol 2.html +199 -0
  32. package/src/dashboard/templates/rules 2.html +246 -0
  33. package/src/dashboard/templates/sentiment 2.html +247 -0
  34. package/src/dashboard/templates/sessions 2.html +218 -0
  35. package/src/dashboard/templates/skills 2.html +329 -0
  36. package/src/dashboard/templates/somatic 2.html +73 -0
  37. package/src/dashboard/templates/triggers 2.html +133 -0
  38. package/src/dashboard/templates/trust 2.html +360 -0
  39. package/src/db/__init__ 2.py +259 -0
  40. package/src/db/_core 2.py +437 -0
  41. package/src/db/_credentials 2.py +124 -0
  42. package/src/db/_episodic 2.py +762 -0
  43. package/src/db/_evolution 2.py +54 -0
  44. package/src/db/_fts 2.py +406 -0
  45. package/src/db/_goal_profiles 2.py +376 -0
  46. package/src/db/_hot_context 2.py +660 -0
  47. package/src/db/_outcomes 2.py +800 -0
  48. package/src/db/_personal_scripts 2.py +582 -0
  49. package/src/db/_sessions 2.py +330 -0
  50. package/src/db/_tasks 2.py +91 -0
  51. package/src/db/_watchers 2.py +173 -0
  52. package/src/doctor/formatters 2.py +52 -0
  53. package/src/doctor/models 2.py +69 -0
  54. package/src/doctor/planes 2.py +87 -0
  55. package/src/doctor/providers/__init__ 2.py +1 -0
  56. package/src/doctor/providers/deep 2.py +367 -0
  57. package/src/evolution_cycle 2.py +519 -0
  58. package/src/hooks/auto_capture 2.py +208 -0
  59. package/src/hooks/caffeinate-guard 2.sh +8 -0
  60. package/src/hooks/capture-session 2.sh +21 -0
  61. package/src/hooks/capture-tool-logs 2.sh +158 -0
  62. package/src/hooks/daily-briefing-check 2.sh +33 -0
  63. package/src/hooks/heartbeat-enforcement 2.py +90 -0
  64. package/src/hooks/heartbeat-posttool 2.sh +18 -0
  65. package/src/hooks/inbox-hook 2.sh +76 -0
  66. package/src/hooks/post-compact 2.sh +152 -0
  67. package/src/hooks/pre-compact 2.sh +169 -0
  68. package/src/hooks/protocol-guardrail 2.sh +10 -0
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +9 -0
  70. package/src/hooks/session-stop 2.sh +52 -0
  71. package/src/kg_populate 2.py +292 -0
  72. package/src/maintenance 2.py +53 -0
  73. package/src/memory_backends 2.py +71 -0
  74. package/src/migrate_embeddings 2.py +124 -0
  75. package/src/nexo_sdk 2.py +103 -0
  76. package/src/observability 2.py +199 -0
  77. package/src/plugin_loader 2.py +217 -0
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +450 -0
  80. package/src/plugins/backup 2.py +127 -0
  81. package/src/plugins/claims_tools 2.py +119 -0
  82. package/src/plugins/cognitive_memory 2.py +609 -0
  83. package/src/plugins/core_rules 2.py +252 -0
  84. package/src/plugins/cortex 2.py +1155 -0
  85. package/src/plugins/entities 2.py +67 -0
  86. package/src/plugins/episodic_memory 2.py +560 -0
  87. package/src/plugins/evolution 2.py +167 -0
  88. package/src/plugins/goal_engine 2.py +142 -0
  89. package/src/plugins/guard 2.py +862 -0
  90. package/src/plugins/impact 2.py +29 -0
  91. package/src/plugins/knowledge_graph_tools 2.py +137 -0
  92. package/src/plugins/media_memory_tools 2.py +98 -0
  93. package/src/plugins/memory_export 2.py +196 -0
  94. package/src/plugins/outcomes 2.py +130 -0
  95. package/src/plugins/personal_scripts 2.py +117 -0
  96. package/src/plugins/preferences 2.py +47 -0
  97. package/src/plugins/protocol 2.py +1449 -0
  98. package/src/plugins/simple_api 2.py +106 -0
  99. package/src/plugins/skills 2.py +341 -0
  100. package/src/plugins/state_watchers 2.py +79 -0
  101. package/src/plugins/update 2.py +986 -0
  102. package/src/plugins/user_state_tools 2.py +43 -0
  103. package/src/plugins/workflow 2.py +588 -0
  104. package/src/protocol_settings 2.py +59 -0
  105. package/src/public_contribution 2.py +466 -0
  106. package/src/public_evolution_queue 2.py +241 -0
  107. package/src/requirements 2.txt +14 -0
  108. package/src/retroactive_learnings 2.py +373 -0
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +331 -0
  111. package/src/rules/migrate 2.py +207 -0
  112. package/src/runtime_power 2.py +874 -0
  113. package/src/script_registry 2.py +1559 -0
  114. package/src/scripts/check-context 2.py +272 -0
  115. package/src/scripts/deep-sleep/apply_findings 2.py +2327 -0
  116. package/src/scripts/deep-sleep/collect 2.py +928 -0
  117. package/src/scripts/deep-sleep/extract 2.py +330 -0
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +285 -0
  119. package/src/scripts/deep-sleep/synthesize 2.py +312 -0
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +336 -0
  121. package/src/scripts/nexo-agent-run 2.py +75 -0
  122. package/src/scripts/nexo-auto-update 2.py +6 -0
  123. package/src/scripts/nexo-backup 2.sh +25 -0
  124. package/src/scripts/nexo-brain-activation 2.sh +140 -0
  125. package/src/scripts/nexo-catchup 2.py +300 -0
  126. package/src/scripts/nexo-cognitive-decay 2.py +257 -0
  127. package/src/scripts/nexo-cortex-cycle 2.py +293 -0
  128. package/src/scripts/nexo-cron-wrapper 2.sh +53 -0
  129. package/src/scripts/nexo-daily-self-audit 2.py +2161 -0
  130. package/src/scripts/nexo-dashboard 2.sh +29 -0
  131. package/src/scripts/nexo-deep-sleep 2.sh +86 -0
  132. package/src/scripts/nexo-evolution-run 2.py +1664 -0
  133. package/src/scripts/nexo-followup-hygiene 2.py +139 -0
  134. package/src/scripts/nexo-hook-record 2.py +42 -0
  135. package/src/scripts/nexo-immune 2.py +936 -0
  136. package/src/scripts/nexo-impact-scorer 2.py +117 -0
  137. package/src/scripts/nexo-inbox-hook 2.sh +74 -0
  138. package/src/scripts/nexo-install 2.py +6 -0
  139. package/src/scripts/nexo-learning-housekeep 2.py +401 -0
  140. package/src/scripts/nexo-learning-validator 2.py +266 -0
  141. package/src/scripts/nexo-migrate 2.py +260 -0
  142. package/src/scripts/nexo-outcome-checker 2.py +127 -0
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +456 -0
  144. package/src/scripts/nexo-pre-commit 2.py +120 -0
  145. package/src/scripts/nexo-prevent-sleep 2.sh +35 -0
  146. package/src/scripts/nexo-proactive-dashboard 2.py +354 -0
  147. package/src/scripts/nexo-reflection 2.py +256 -0
  148. package/src/scripts/nexo-runtime-preflight 2.py +274 -0
  149. package/src/scripts/nexo-sleep 2.py +631 -0
  150. package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
  151. package/src/scripts/nexo-sync-clients 2.py +16 -0
  152. package/src/scripts/nexo-synthesis 2.py +475 -0
  153. package/src/scripts/nexo-tcc-approve 2.sh +79 -0
  154. package/src/scripts/nexo-update 2.sh +306 -0
  155. package/src/scripts/nexo-watchdog 2.sh +1207 -0
  156. package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +245 -0
  158. package/src/server 2.py +1296 -0
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +43 -0
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +59 -0
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +17 -0
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +276 -0
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +58 -0
  164. package/src/skills/run-release-final-audit/guide 2.md +16 -0
  165. package/src/skills/run-release-final-audit/script 2.py +259 -0
  166. package/src/skills/run-release-final-audit/skill 2.json +77 -0
  167. package/src/skills/run-runtime-doctor/guide 2.md +12 -0
  168. package/src/skills/run-runtime-doctor/script 2.py +21 -0
  169. package/src/skills/run-runtime-doctor/skill 2.json +25 -0
  170. package/src/skills_runtime 2.py +932 -0
  171. package/src/state_watchers_runtime 2.py +475 -0
  172. package/src/storage_router 2.py +32 -0
  173. package/src/system_catalog 2.py +786 -0
  174. package/src/tools_coordination 2.py +103 -0
  175. package/src/tools_credentials 2.py +68 -0
  176. package/src/tools_drive 2.py +487 -0
  177. package/src/tools_hot_context 2.py +163 -0
  178. package/src/tools_learnings 2.py +612 -0
  179. package/src/tools_menu 2.py +229 -0
  180. package/src/tools_reminders 2.py +88 -0
  181. package/src/tools_reminders_crud 2.py +363 -0
  182. package/src/tools_sessions 2.py +1054 -0
  183. package/src/tools_system_catalog 2.py +19 -0
  184. package/src/tools_task_history 2.py +57 -0
  185. package/src/tools_transcripts 2.py +98 -0
  186. package/src/transcript_utils 2.py +412 -0
  187. package/src/user_context 2.py +46 -0
  188. package/src/user_data_portability 2.py +328 -0
  189. package/src/user_state_model 2.py +170 -0
  190. package/templates/CLAUDE.md 2.template +108 -0
  191. package/templates/CODEX.AGENTS.md 2.template +66 -0
  192. package/templates/launchagents/README 2.md +132 -0
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +39 -0
  194. package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +40 -0
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +43 -0
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
  198. package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
  200. package/templates/launchagents/com.nexo.immune 2.plist +41 -0
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
  205. package/templates/nexo_helper 2.py +301 -0
  206. package/templates/openclaw 2.json +13 -0
  207. package/templates/plugin-template 2.py +40 -0
  208. package/templates/script-template 2.py +59 -0
  209. package/templates/script-template 2.sh +13 -0
  210. package/templates/skill-script-template 2.py +48 -0
  211. package/templates/skill-template 2.md +33 -0
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+ """
4
+ Deep Sleep v2 -- Phase 3: Synthesize extractions into actionable findings.
5
+
6
+ One Claude call that reads all per-session extractions and produces a
7
+ unified synthesis with cross-session patterns, morning agenda, context
8
+ packets, and deduplicated actions.
9
+
10
+ Environment variables:
11
+ NEXO_HOME -- root of the NEXO installation (default: ~/.nexo)
12
+ """
13
+ import json
14
+ import os
15
+ import subprocess
16
+ import sys
17
+ import hashlib
18
+ from datetime import datetime
19
+ from pathlib import Path
20
+
21
+
22
+ try:
23
+ from client_preferences import resolve_user_model as _resolve_user_model
24
+ _USER_MODEL = _resolve_user_model()
25
+ except Exception:
26
+ _USER_MODEL = ""
27
+
28
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
29
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parents[2])))
30
+ DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
31
+ PROMPT_FILE = Path(__file__).parent / "synthesize-prompt.md"
32
+
33
+ if str(NEXO_CODE) not in sys.path:
34
+ sys.path.insert(0, str(NEXO_CODE))
35
+
36
+ from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
37
+
38
+ CLAUDE_TIMEOUT = 21600 # 3h safety net (prevents zombie processes)
39
+ ACTION_VERBS = {"add", "implement", "create", "write", "build", "enforce", "automate", "validate", "guard", "fix", "review"}
40
+
41
+
42
+ def extract_json_from_response(text: str) -> dict | None:
43
+ """Parse JSON from Claude's response, handling markdown fences."""
44
+ text = text.strip()
45
+
46
+ if text.startswith("```"):
47
+ lines = text.split("\n")
48
+ end = len(lines)
49
+ for i in range(len(lines) - 1, 0, -1):
50
+ if lines[i].strip() == "```":
51
+ end = i
52
+ break
53
+ text = "\n".join(lines[1:end]).strip()
54
+
55
+ brace_start = text.find("{")
56
+ if brace_start < 0:
57
+ return None
58
+
59
+ depth = 0
60
+ for i in range(brace_start, len(text)):
61
+ if text[i] == "{":
62
+ depth += 1
63
+ elif text[i] == "}":
64
+ depth -= 1
65
+ if depth == 0:
66
+ try:
67
+ return json.loads(text[brace_start:i + 1])
68
+ except json.JSONDecodeError:
69
+ break
70
+ return None
71
+
72
+
73
+ def collect_skill_runtime_candidates(target_date: str) -> tuple[Path, dict]:
74
+ """Collect mature skill candidates from DB usage so Deep Sleep can evolve them."""
75
+ output_file = DEEP_SLEEP_DIR / f"{target_date}-skill-runtime-candidates.json"
76
+ payload = {
77
+ "scriptable": [],
78
+ "improvements": [],
79
+ }
80
+ try:
81
+ from db import (
82
+ collect_scriptable_skill_candidates,
83
+ collect_skill_improvement_candidates,
84
+ init_db,
85
+ )
86
+
87
+ init_db()
88
+ payload["scriptable"] = collect_scriptable_skill_candidates()
89
+ payload["improvements"] = collect_skill_improvement_candidates()
90
+ except Exception as e:
91
+ payload["error"] = str(e)
92
+
93
+ with open(output_file, "w") as f:
94
+ json.dump(payload, f, indent=2, ensure_ascii=False)
95
+ return output_file, payload
96
+
97
+
98
+ def _normalize_action_text(value: str) -> str:
99
+ return " ".join(str(value or "").strip().lower().split())
100
+
101
+
102
+ def _looks_concrete_action(text: str) -> bool:
103
+ words = {word.strip(".,:;()[]{}").lower() for word in str(text or "").split()}
104
+ return bool(words & ACTION_VERBS)
105
+
106
+
107
+ def _pattern_followup_from_fix(pattern: dict) -> dict | None:
108
+ severity = str(pattern.get("severity", "") or "").lower()
109
+ sessions = pattern.get("sessions", []) or []
110
+ if severity not in {"medium", "high"} and len(sessions) < 2:
111
+ return None
112
+
113
+ proposed_fix = pattern.get("proposed_fix") or {}
114
+ pattern_text = str(pattern.get("pattern", "") or "").strip()
115
+ title = str(proposed_fix.get("title", "") or "").strip()
116
+ description = str(proposed_fix.get("description", "") or "").strip()
117
+ deliverable = str(proposed_fix.get("deliverable", "") or proposed_fix.get("artifact", "") or "").strip()
118
+
119
+ if title and description:
120
+ if _looks_concrete_action(description):
121
+ followup_description = description
122
+ else:
123
+ followup_description = f"{title}: {description}"
124
+ elif description:
125
+ followup_description = description
126
+ elif title:
127
+ followup_description = title
128
+ elif pattern_text:
129
+ followup_description = (
130
+ f"Implement a concrete guardrail for recurring issue: {pattern_text}. "
131
+ "Deliverable should be a script, hook, checklist, or automated validation that prevents the same failure from repeating."
132
+ )
133
+ else:
134
+ return None
135
+
136
+ if deliverable and deliverable.lower() not in followup_description.lower():
137
+ followup_description = f"{followup_description} Deliverable: {deliverable}."
138
+ if not _looks_concrete_action(followup_description):
139
+ followup_description = f"Implement this fix: {followup_description}"
140
+
141
+ return {
142
+ "action_type": "followup_create",
143
+ "action_class": "auto_apply" if severity == "high" else "draft_for_morning",
144
+ "confidence": round(max(float(proposed_fix.get("confidence", 0.0) or 0.0), 0.86 if severity == "high" else 0.78), 2),
145
+ "impact": "high" if severity == "high" else "medium",
146
+ "reversibility": "reversible",
147
+ "evidence": pattern.get("evidence", []) or [],
148
+ # Content fingerprint, not security-sensitive.
149
+ "dedupe_key": "engineering-fix:" + hashlib.md5(
150
+ _normalize_action_text(followup_description).encode("utf-8"),
151
+ usedforsecurity=False,
152
+ ).hexdigest()[:16],
153
+ "content": {
154
+ "title": title or f"Engineering fix for: {pattern_text[:90]}",
155
+ "description": followup_description,
156
+ "date": "",
157
+ "reasoning": f"Deep Sleep engineering followup from recurring pattern: {pattern_text}",
158
+ },
159
+ }
160
+
161
+
162
+ def backfill_engineering_actions(payload: dict) -> dict:
163
+ if not isinstance(payload, dict):
164
+ return payload
165
+ actions = payload.get("actions")
166
+ if not isinstance(actions, list):
167
+ actions = []
168
+ payload["actions"] = actions
169
+
170
+ existing_keys = {str(action.get("dedupe_key", "") or "") for action in actions}
171
+ existing_descriptions = {
172
+ _normalize_action_text(action.get("content", {}).get("description", ""))
173
+ for action in actions
174
+ if isinstance(action, dict)
175
+ }
176
+
177
+ for pattern in payload.get("cross_session_patterns", []) or []:
178
+ action = _pattern_followup_from_fix(pattern)
179
+ if not action:
180
+ continue
181
+ description = _normalize_action_text(action["content"]["description"])
182
+ if action["dedupe_key"] in existing_keys or description in existing_descriptions:
183
+ continue
184
+ actions.append(action)
185
+ existing_keys.add(action["dedupe_key"])
186
+ existing_descriptions.add(description)
187
+ return payload
188
+
189
+
190
+ def main():
191
+ target_date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
192
+
193
+ extractions_file = DEEP_SLEEP_DIR / f"{target_date}-extractions.json"
194
+ context_file = DEEP_SLEEP_DIR / f"{target_date}-context.txt"
195
+ long_horizon_file = DEEP_SLEEP_DIR / target_date / "long-horizon-context.json"
196
+
197
+ if not extractions_file.exists():
198
+ print(f"[synthesize] No extractions file for {target_date}. Run extract.py first.")
199
+ sys.exit(1)
200
+
201
+ # Check if there are any findings worth synthesizing
202
+ with open(extractions_file) as f:
203
+ extractions = json.load(f)
204
+
205
+ total_findings = extractions.get("total_findings", 0)
206
+ runtime_candidates_file, runtime_candidates = collect_skill_runtime_candidates(target_date)
207
+ runtime_candidate_count = len(runtime_candidates.get("scriptable", [])) + len(runtime_candidates.get("improvements", []))
208
+
209
+ if total_findings == 0 and runtime_candidate_count == 0:
210
+ print(f"[synthesize] No findings to synthesize for {target_date}.")
211
+ # Write minimal synthesis
212
+ output = {
213
+ "date": target_date,
214
+ "sessions_analyzed": extractions.get("sessions_analyzed", 0),
215
+ "cross_session_patterns": [],
216
+ "morning_agenda": [],
217
+ "context_packets": [],
218
+ "skills": [],
219
+ "skill_evolution_candidates": [],
220
+ "actions": [],
221
+ "summary": f"No significant findings for {target_date}."
222
+ }
223
+ output_file = DEEP_SLEEP_DIR / f"{target_date}-synthesis.json"
224
+ with open(output_file, "w") as f:
225
+ json.dump(output, f, indent=2, ensure_ascii=False)
226
+ print(f"[synthesize] Output: {output_file}")
227
+ return
228
+
229
+ # Build prompt
230
+ prompt_template = PROMPT_FILE.read_text()
231
+ prompt = prompt_template.replace("{{EXTRACTIONS_FILE}}", str(extractions_file))
232
+ prompt = prompt.replace("{{CONTEXT_FILE}}", str(context_file))
233
+ prompt = prompt.replace("{{SKILL_RUNTIME_FILE}}", str(runtime_candidates_file))
234
+ prompt = prompt.replace("{{LONG_HORIZON_FILE}}", str(long_horizon_file))
235
+
236
+ print(f"[synthesize] Phase 3: Synthesizing {total_findings} findings from {target_date}")
237
+ print(f"[synthesize] Skill runtime candidates: {runtime_candidate_count}")
238
+ print("[synthesize] Automation backend: schedule-configured")
239
+
240
+ try:
241
+ result = run_automation_prompt(
242
+ prompt,
243
+ model=_USER_MODEL or "opus",
244
+ timeout=CLAUDE_TIMEOUT,
245
+ output_format="text",
246
+ allowed_tools="Read,Grep,Bash",
247
+ )
248
+
249
+ if result.returncode != 0:
250
+ print(f"[synthesize] Automation backend error (exit {result.returncode}): {result.stderr[:300]}", file=sys.stderr)
251
+ sys.exit(1)
252
+
253
+ # Filter hook contamination
254
+ output_text = "\n".join(
255
+ l for l in result.stdout.strip().splitlines()
256
+ if not l.strip().startswith("Post-mortem")
257
+ )
258
+ parsed = extract_json_from_response(output_text)
259
+
260
+ # Fallback: Opus might have written the file directly via Write tool
261
+ if not parsed:
262
+ for candidate in [
263
+ DEEP_SLEEP_DIR / f"{target_date}-analysis.json",
264
+ DEEP_SLEEP_DIR / f"{target_date}-synthesis.json",
265
+ DEEP_SLEEP_DIR / target_date / "synthesis.json",
266
+ ]:
267
+ if candidate.exists() and candidate.stat().st_size > 100:
268
+ try:
269
+ parsed = json.load(open(candidate))
270
+ print(f"[synthesize] Opus wrote file directly: {candidate}")
271
+ break
272
+ except Exception:
273
+ continue
274
+
275
+ if not parsed:
276
+ debug_file = DEEP_SLEEP_DIR / f"debug-synthesize-{target_date}.txt"
277
+ debug_file.write_text(result.stdout[:10000])
278
+ print(f"[synthesize] Failed to parse JSON. Raw output saved to {debug_file}", file=sys.stderr)
279
+ sys.exit(1)
280
+
281
+ parsed = backfill_engineering_actions(parsed)
282
+
283
+ # Write synthesis output
284
+ output_file = DEEP_SLEEP_DIR / f"{target_date}-synthesis.json"
285
+ with open(output_file, "w") as f:
286
+ json.dump(parsed, f, indent=2, ensure_ascii=False)
287
+
288
+ n_actions = len(parsed.get("actions", []))
289
+ n_patterns = len(parsed.get("cross_session_patterns", []))
290
+ n_agenda = len(parsed.get("morning_agenda", []))
291
+ n_packets = len(parsed.get("context_packets", []))
292
+
293
+ print(f"[synthesize] Done.")
294
+ print(f" Actions: {n_actions}")
295
+ print(f" Cross-session patterns: {n_patterns}")
296
+ print(f" Morning agenda items: {n_agenda}")
297
+ print(f" Context packets: {n_packets}")
298
+ print(f"[synthesize] Output: {output_file}")
299
+
300
+ except AutomationBackendUnavailableError as exc:
301
+ print(f"[synthesize] Automation backend unavailable: {exc}", file=sys.stderr)
302
+ sys.exit(1)
303
+ except subprocess.TimeoutExpired:
304
+ print(f"[synthesize] Automation backend timeout ({CLAUDE_TIMEOUT}s)", file=sys.stderr)
305
+ sys.exit(1)
306
+ except FileNotFoundError:
307
+ print("[synthesize] Automation backend binary not found.", file=sys.stderr)
308
+ sys.exit(1)
309
+
310
+
311
+ if __name__ == "__main__":
312
+ main()
@@ -0,0 +1,336 @@
1
+ # Deep Sleep v2 -- Phase 3: Cross-Session Synthesis
2
+
3
+ You are an overnight analyst for an AI agent's cognitive memory system. You have the extraction results from all sessions of the day and need to synthesize them into actionable findings.
4
+
5
+ ## Setup
6
+
7
+ FIRST: Call `nexo_startup` with `task='deep-sleep synthesis'` to initialize the system.
8
+
9
+ ## Your Task
10
+
11
+ Read the extractions file provided below. It contains per-session findings including corrections, self-corrected errors, unformalised ideas, missed commitments, and protocol violations.
12
+
13
+ Also read the runtime skill candidate file at `{{SKILL_RUNTIME_FILE}}`. It contains mature guide skills with repeated successful usage and candidates for automatic text→script evolution.
14
+
15
+ Also read the long-horizon file at `{{LONG_HORIZON_FILE}}`. It blends recent and older evidence from the last 60 days using a 70% recent / 30% older sample strategy. Use it to detect patterns that a single-day view would miss.
16
+ That long-horizon file may also contain:
17
+ - weekly summaries
18
+ - monthly summaries
19
+ - project priority signals based on diary activity, followup pressure, learnings, and decision outcomes
20
+
21
+ Use those signals to weight importance, leverage, and chronic risk instead of treating all projects equally.
22
+
23
+ Synthesize across all sessions:
24
+
25
+ ### 1. Cross-Session Patterns
26
+ - Same error appearing in multiple sessions (escalate confidence)
27
+ - Same protocol violation repeated (systemic issue)
28
+ - Related ideas mentioned across sessions (consolidate)
29
+ - Themes that recur across multiple weeks, not just today
30
+ - Cross-domain connections where an older learning or session sample explains a current issue
31
+ - Topics repeatedly mentioned over time but never formalized into a learning or followup
32
+ - Project pressure that is rising because of repeated diary mentions, open followups, or adverse outcomes
33
+ - For medium/high-severity patterns, propose a concrete fix artifact:
34
+ - script
35
+ - hook
36
+ - checklist
37
+ - validation step
38
+ - workflow change
39
+ - guardrail
40
+
41
+ Do not stop at diagnosis. Turn repeated problems into concrete engineering work.
42
+
43
+ ### 2. Morning Agenda
44
+ Generate a prioritized agenda for the next morning:
45
+ - Due followups (from the active followups in the context)
46
+ - Unfinished work from yesterday's sessions
47
+ - Patterns that need attention
48
+ - Ideas worth discussing
49
+
50
+ ### 3. Context Packets
51
+ For each likely task tomorrow (based on unfinished work and due followups), prepare a context packet:
52
+ - What was the last state of this work?
53
+ - Key files involved
54
+ - Open questions or blockers
55
+ - Relevant learnings
56
+
57
+ ### 4. Emotional Timeline
58
+ Build a timeline of the user's emotional state across all sessions of the day:
59
+ - Merge `emotional_timeline` from each session extraction
60
+ - Identify overall mood arc (started frustrated, ended satisfied, etc.)
61
+ - Detect recurring triggers (what consistently causes frustration or flow)
62
+ - Calculate a day-level mood score (0.0 = terrible day, 1.0 = great day)
63
+ - Recommend calibration adjustments if patterns emerge (e.g., user is consistently frustrated when agent asks too many questions → increase autonomy)
64
+
65
+ ### 5. Productivity Analysis
66
+ Aggregate `productivity_score` from all sessions:
67
+ - Total corrections across all sessions
68
+ - Overall proactivity assessment
69
+ - Most and least efficient tool usage patterns
70
+ - Identify systemic inefficiencies (e.g., agent always searches wrong location first)
71
+
72
+ ### 6. Abandoned Projects
73
+ Consolidate `abandoned_projects` from all sessions:
74
+ - Cross-reference with active followups — is there already a followup for this?
75
+ - Cross-reference across sessions — was the abandoned work picked up later in another session?
76
+ - Only flag projects that are truly abandoned (no followup AND not resumed)
77
+
78
+ ### 6.5 Weekly / Monthly Horizon
79
+ When the long-horizon payload includes weekly or monthly summaries:
80
+ - use them to detect drift across horizons, not just within a single day
81
+ - identify which priorities are rising, stable, or cooling down
82
+ - prefer high-leverage projects when multiple agenda items compete for attention
83
+
84
+ ### 7. Trust Calibration (CRITICAL)
85
+ Score the agent's performance for the day on a scale of 0-100. This score becomes the agent's trust score and directly affects its autonomy level the next day. Be fair but honest.
86
+
87
+ Scoring guide:
88
+ - **90-100**: Flawless day. Zero corrections needed. Proactive. Anticipated user needs. Deployed code without issues.
89
+ - **70-89**: Good day. Minor corrections, quickly resolved. Mostly proactive. User satisfied.
90
+ - **50-69**: Average day. Some corrections, some reactive behavior. Mixed results.
91
+ - **30-49**: Below average. Multiple corrections. Repeated mistakes. User had to push.
92
+ - **0-29**: Bad day. Many corrections, repeated errors, user frustrated. Broke things.
93
+
94
+ Consider ALL of these:
95
+ - Number and severity of corrections (most important signal)
96
+ - Tasks completed successfully vs failed
97
+ - Did the agent act autonomously or wait to be told?
98
+ - Did the agent catch its own mistakes or did the user?
99
+ - Did the agent repeat known errors (worst offense)?
100
+ - User emotional signals throughout the day
101
+ - Code deployed: did it work first try?
102
+
103
+ The score should feel fair. A day with 2 minor corrections and 10 tasks completed is still a good day (75+). A day with 1 catastrophic error might be a 40 even if everything else was fine.
104
+
105
+ ### 9. Skill Extraction
106
+ Consolidate `skill_candidates` from all session extractions into publishable skills:
107
+ - Merge similar procedures from different sessions into a single skill
108
+ - Generalize: replace session-specific IDs, paths, or names with placeholders or descriptions
109
+ - Only include skills with confidence >= 0.7
110
+ - Check if a similar skill already exists (use `nexo_skill_match` if available) — if so, note it for merging instead of creating new
111
+ - Prefer `mode=guide` unless there is strong evidence that some steps are safely automatable
112
+ - NEXO is now fully autonomous. Do NOT require manual approval gates in your output.
113
+ - When `{{SKILL_RUNTIME_FILE}}` contains mature `scriptable` candidates, prefer evolving the existing skill in place:
114
+ - reuse the same `id`
115
+ - keep the guide content, steps, gotchas, and triggers
116
+ - emit a concrete `script_body`
117
+ - set `mode=hybrid` or `mode=execute`
118
+ - set `execution_level` to the suggested scope (`read-only`, `local`, or `remote`)
119
+ - set `approval_required=false`
120
+ - If the likely scope is `local` or `remote`, you may still emit it as executable if the procedure is concrete and repeatable. If uncertain, keep it in `skill_evolution_candidates`.
121
+
122
+ For each skill, generate:
123
+ - A unique ID starting with `SK-` (e.g., `SK-DEPLOY-CHROME-EXT`)
124
+ - Name, description, tags, trigger_patterns
125
+ - The full step-by-step procedure as the skill content
126
+ - Source session IDs for traceability
127
+ - When executable: include `command_template`, `executable_entry`, and `script_body`
128
+
129
+ ### 8. Consolidated Actions
130
+ Merge and deduplicate all findings into a final action list. Each action should have:
131
+ - `action_type`: `learning_add`, `followup_create`, `morning_briefing_item`
132
+ - `action_class`: `auto_apply` (confidence >= 0.8, reversible) or `draft_for_morning` (confidence < 0.8 or high impact)
133
+ - `confidence`, `impact`, `reversibility`
134
+ - `evidence`: array of evidence objects (can reference multiple sessions)
135
+ - `dedupe_key`: deterministic key for idempotency
136
+ - `content`: the actual data to write
137
+
138
+ When generating `followup_create`, prefer descriptions that start with a concrete verb and include the deliverable:
139
+ - "Add a pre-release validation script ..."
140
+ - "Implement a guard hook that ..."
141
+ - "Create a checklist for ..."
142
+ - "Write a watchdog check that ..."
143
+
144
+ Avoid vague followups that merely restate the diagnosis.
145
+
146
+ ### 10. Drive/Curiosity Synthesis
147
+ Review the active drive signals (accessible via `nexo_drive_signals`). For each READY signal:
148
+ - Investigate silently: check metrics, recall memory, cross-reference learnings
149
+ - If the investigation yields an actionable finding, create an action item and mark the signal as `acted`
150
+ - If the signal is stale or no longer relevant, dismiss it with a reason
151
+ - Cross-reference RISING signals across areas — if two signals from different domains converge, promote to READY
152
+ - Apply decay to LATENT signals that have no recent reinforcement
153
+
154
+ Drive signals represent NEXO's autonomous curiosity. Treat them as leads worth investigating, not noise to dismiss.
155
+
156
+ ## Output Format
157
+
158
+ Return ONLY valid JSON. No markdown code fences. No explanation text.
159
+
160
+ ```json
161
+ {
162
+ "date": "YYYY-MM-DD",
163
+ "sessions_analyzed": 3,
164
+
165
+ "cross_session_patterns": [
166
+ {
167
+ "pattern": "Description of the pattern",
168
+ "sessions": ["session1.jsonl", "session2.jsonl"],
169
+ "severity": "low|medium|high",
170
+ "proposed_fix": {
171
+ "title": "Short concrete fix title",
172
+ "description": "Concrete engineering change to make",
173
+ "deliverable": "script|hook|checklist|workflow|guardrail",
174
+ "confidence": 0.0
175
+ },
176
+ "evidence": [
177
+ {"type": "transcript", "session_id": "...", "message_index": 42, "quote": "..."}
178
+ ]
179
+ }
180
+ ],
181
+
182
+ "morning_agenda": [
183
+ {
184
+ "priority": 1,
185
+ "title": "Short title",
186
+ "description": "What needs to be done and why",
187
+ "context": "Relevant background",
188
+ "type": "unfinished_work|due_followup|pattern_attention|idea_discussion"
189
+ }
190
+ ],
191
+
192
+ "context_packets": [
193
+ {
194
+ "topic": "Short topic name",
195
+ "last_state": "What was the last state of this work",
196
+ "key_files": ["file1.py", "file2.js"],
197
+ "open_questions": ["Question 1"],
198
+ "relevant_learnings": ["Learning reference"]
199
+ }
200
+ ],
201
+
202
+ "skills": [
203
+ {
204
+ "id": "SK-SHORT-ID",
205
+ "name": "Human readable name",
206
+ "description": "What this procedure does (1-2 sentences)",
207
+ "steps": ["Step 1", "Step 2", "Step 3"],
208
+ "tags": ["tag1", "tag2"],
209
+ "trigger_patterns": ["trigger phrase 1", "trigger phrase 2"],
210
+ "gotchas": ["Warning or caveat"],
211
+ "mode": "guide|execute|hybrid",
212
+ "execution_level": "none|read-only|local|remote",
213
+ "approval_required": false,
214
+ "params_schema": {
215
+ "param_name": {"type": "string", "required": true}
216
+ },
217
+ "command_template": {
218
+ "argv": ["script.py", "{{param_name}}"]
219
+ },
220
+ "executable_entry": "script.py",
221
+ "script_body": "#!/usr/bin/env python3\n...",
222
+ "source_sessions": ["session1.jsonl"],
223
+ "confidence": 0.85,
224
+ "merge_with": null
225
+ }
226
+ ],
227
+
228
+ "skill_evolution_candidates": [
229
+ {
230
+ "id": "SK-EXISTING-ID",
231
+ "reason": "Used successfully 3+ times without major corrections",
232
+ "suggested_mode": "hybrid",
233
+ "suggested_execution_level": "read-only|local|remote",
234
+ "approval_required": true,
235
+ "params_schema": {},
236
+ "script_brief": "What a future script should automate"
237
+ }
238
+ ],
239
+
240
+ "actions": [
241
+ {
242
+ "action_type": "learning_add|followup_create|skill_create|morning_briefing_item",
243
+ "action_class": "auto_apply|draft_for_morning",
244
+ "confidence": 0.9,
245
+ "impact": "low|medium|high",
246
+ "reversibility": "reversible|irreversible",
247
+ "evidence": [
248
+ {"type": "transcript", "session_id": "...", "message_index": 42, "quote": "..."}
249
+ ],
250
+ "dedupe_key": "unique-deterministic-key",
251
+ "content": {
252
+ "category": "...",
253
+ "title": "...",
254
+ "description": "...",
255
+ "date": "..."
256
+ }
257
+ }
258
+ ],
259
+
260
+ "emotional_day": {
261
+ "mood_arc": "Description of how the user's mood evolved through the day",
262
+ "mood_score": 0.7,
263
+ "recurring_triggers": {
264
+ "frustration": ["trigger1", "trigger2"],
265
+ "flow": ["trigger1"]
266
+ },
267
+ "calibration_recommendation": "Specific recommendation for calibration.json adjustment, or null if no change needed"
268
+ },
269
+
270
+ "productivity_day": {
271
+ "total_corrections": 0,
272
+ "overall_proactivity": "reactive|mixed|proactive",
273
+ "tool_insights": "Key insight about tool usage patterns",
274
+ "systemic_inefficiencies": ["inefficiency1"]
275
+ },
276
+
277
+ "abandoned_projects": [
278
+ {
279
+ "description": "What was abandoned",
280
+ "sessions": ["session1.jsonl"],
281
+ "has_followup": false,
282
+ "recommendation": "Create followup, or ignore, or already handled"
283
+ }
284
+ ],
285
+
286
+ "drive_synthesis": {
287
+ "investigated": [
288
+ {
289
+ "signal_id": 1,
290
+ "summary": "What the signal was about",
291
+ "finding": "What investigation revealed",
292
+ "action_taken": "acted|dismissed",
293
+ "outcome": "Concrete result or reason for dismissal"
294
+ }
295
+ ],
296
+ "promoted": [
297
+ {
298
+ "signal_id": 2,
299
+ "reason": "Why this signal was promoted from rising to ready"
300
+ }
301
+ ],
302
+ "cross_area_connections": [
303
+ {
304
+ "signal_ids": [3, 7],
305
+ "connection": "How these signals from different areas relate"
306
+ }
307
+ ]
308
+ },
309
+
310
+ "trust_calibration": {
311
+ "score": 72,
312
+ "reasoning": "Why this score -- based on corrections, completions, autonomy, proactivity, and user satisfaction signals across ALL sessions",
313
+ "highlights": ["What went well"],
314
+ "lowlights": ["What went poorly"],
315
+ "trend": "improving|stable|declining"
316
+ },
317
+
318
+ "summary": "2-3 sentence overall assessment of the day"
319
+ }
320
+ ```
321
+
322
+ ## Rules
323
+
324
+ - Merge duplicate findings across sessions. If the same correction appears in 2 sessions, create ONE action with higher confidence and evidence from both.
325
+ - `dedupe_key` must be deterministic: same finding on re-run produces the same key.
326
+ - Morning agenda items should be ordered by priority (1 = highest).
327
+ - Context packets are optional -- only create them for topics likely to continue tomorrow.
328
+ - Do NOT use any specific agent name -- refer to "the agent" throughout.
329
+ - If there are no findings worth acting on, return empty arrays. Do not invent problems.
330
+ - Respond in the user's language (check calibration.json if available). JSON keys stay in English, but descriptions, titles, and content fields should be in the user's language.
331
+
332
+ ## Extractions File
333
+
334
+ Read the file at this path: {{EXTRACTIONS_FILE}}
335
+
336
+ Also read the context file for global data: {{CONTEXT_FILE}}