nexo-brain 5.3.13 → 5.3.15

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 (230) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/nexo-brain.js +52 -1
  3. package/package.json +1 -1
  4. package/src/crons/sync.py +18 -4
  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/_entities.py +1 -1
  43. package/src/db/_episodic 2.py +762 -0
  44. package/src/db/_evolution 2.py +54 -0
  45. package/src/db/_fts 2.py +406 -0
  46. package/src/db/_goal_profiles 2.py +376 -0
  47. package/src/db/_hot_context 2.py +660 -0
  48. package/src/db/_outcomes 2.py +800 -0
  49. package/src/db/_personal_scripts 2.py +582 -0
  50. package/src/db/_sessions 2.py +330 -0
  51. package/src/db/_tasks 2.py +91 -0
  52. package/src/db/_watchers 2.py +173 -0
  53. package/src/doctor/formatters 2.py +52 -0
  54. package/src/doctor/models 2.py +69 -0
  55. package/src/doctor/planes 2.py +87 -0
  56. package/src/doctor/providers/__init__ 2.py +1 -0
  57. package/src/doctor/providers/deep 2.py +367 -0
  58. package/src/evolution_cycle 2.py +519 -0
  59. package/src/hooks/auto_capture 2.py +208 -0
  60. package/src/hooks/caffeinate-guard 2.sh +8 -0
  61. package/src/hooks/capture-session 2.sh +21 -0
  62. package/src/hooks/capture-tool-logs 2.sh +158 -0
  63. package/src/hooks/daily-briefing-check 2.sh +33 -0
  64. package/src/hooks/heartbeat-enforcement 2.py +90 -0
  65. package/src/hooks/heartbeat-posttool 2.sh +18 -0
  66. package/src/hooks/inbox-hook 2.sh +76 -0
  67. package/src/hooks/post-compact 2.sh +152 -0
  68. package/src/hooks/pre-compact 2.sh +169 -0
  69. package/src/hooks/protocol-guardrail 2.sh +10 -0
  70. package/src/hooks/protocol-pretool-guardrail 2.sh +9 -0
  71. package/src/hooks/session-stop 2.sh +52 -0
  72. package/src/kg_populate 2.py +292 -0
  73. package/src/maintenance 2.py +53 -0
  74. package/src/memory_backends 2.py +71 -0
  75. package/src/migrate_embeddings 2.py +124 -0
  76. package/src/nexo_sdk 2.py +103 -0
  77. package/src/observability 2.py +199 -0
  78. package/src/plugin_loader 2.py +217 -0
  79. package/src/plugins/__init__ 2.py +0 -0
  80. package/src/plugins/agents.py +10 -3
  81. package/src/plugins/artifact_registry 2.py +450 -0
  82. package/src/plugins/backup 2.py +127 -0
  83. package/src/plugins/claims_tools 2.py +119 -0
  84. package/src/plugins/cognitive_memory 2.py +609 -0
  85. package/src/plugins/core_rules 2.py +252 -0
  86. package/src/plugins/cortex 2.py +1155 -0
  87. package/src/plugins/entities 2.py +67 -0
  88. package/src/plugins/episodic_memory 2.py +560 -0
  89. package/src/plugins/evolution 2.py +167 -0
  90. package/src/plugins/goal_engine 2.py +142 -0
  91. package/src/plugins/guard 2.py +862 -0
  92. package/src/plugins/impact 2.py +29 -0
  93. package/src/plugins/knowledge_graph_tools 2.py +137 -0
  94. package/src/plugins/media_memory_tools 2.py +98 -0
  95. package/src/plugins/memory_export 2.py +196 -0
  96. package/src/plugins/outcomes 2.py +130 -0
  97. package/src/plugins/personal_scripts 2.py +117 -0
  98. package/src/plugins/preferences 2.py +47 -0
  99. package/src/plugins/protocol 2.py +1449 -0
  100. package/src/plugins/schedule.py +2 -1
  101. package/src/plugins/simple_api 2.py +106 -0
  102. package/src/plugins/skills 2.py +341 -0
  103. package/src/plugins/state_watchers 2.py +79 -0
  104. package/src/plugins/update 2.py +986 -0
  105. package/src/plugins/user_state_tools 2.py +43 -0
  106. package/src/plugins/workflow 2.py +588 -0
  107. package/src/protocol_settings 2.py +59 -0
  108. package/src/public_contribution 2.py +466 -0
  109. package/src/public_evolution_queue 2.py +241 -0
  110. package/src/requirements 2.txt +14 -0
  111. package/src/requirements.txt +1 -1
  112. package/src/retroactive_learnings 2.py +373 -0
  113. package/src/rules/__init__ 2.py +0 -0
  114. package/src/rules/core-rules 2.json +331 -0
  115. package/src/rules/migrate 2.py +207 -0
  116. package/src/runtime_power 2.py +874 -0
  117. package/src/runtime_power.py +18 -1
  118. package/src/script_registry 2.py +1559 -0
  119. package/src/scripts/check-context 2.py +272 -0
  120. package/src/scripts/deep-sleep/apply_findings 2.py +2327 -0
  121. package/src/scripts/deep-sleep/collect 2.py +928 -0
  122. package/src/scripts/deep-sleep/extract 2.py +330 -0
  123. package/src/scripts/deep-sleep/extract-prompt 2.md +285 -0
  124. package/src/scripts/deep-sleep/synthesize 2.py +312 -0
  125. package/src/scripts/deep-sleep/synthesize-prompt 2.md +336 -0
  126. package/src/scripts/nexo-agent-run 2.py +75 -0
  127. package/src/scripts/nexo-auto-update 2.py +6 -0
  128. package/src/scripts/nexo-backup 2.sh +25 -0
  129. package/src/scripts/nexo-brain-activation 2.sh +140 -0
  130. package/src/scripts/nexo-catchup 2.py +300 -0
  131. package/src/scripts/nexo-cognitive-decay 2.py +257 -0
  132. package/src/scripts/nexo-cortex-cycle 2.py +293 -0
  133. package/src/scripts/nexo-cron-wrapper 2.sh +53 -0
  134. package/src/scripts/nexo-cron-wrapper.sh +7 -0
  135. package/src/scripts/nexo-daily-self-audit 2.py +2161 -0
  136. package/src/scripts/nexo-dashboard 2.sh +29 -0
  137. package/src/scripts/nexo-deep-sleep 2.sh +86 -0
  138. package/src/scripts/nexo-evolution-run 2.py +1664 -0
  139. package/src/scripts/nexo-followup-hygiene 2.py +139 -0
  140. package/src/scripts/nexo-hook-record 2.py +42 -0
  141. package/src/scripts/nexo-immune 2.py +936 -0
  142. package/src/scripts/nexo-impact-scorer 2.py +117 -0
  143. package/src/scripts/nexo-inbox-hook 2.sh +74 -0
  144. package/src/scripts/nexo-install 2.py +6 -0
  145. package/src/scripts/nexo-learning-housekeep 2.py +401 -0
  146. package/src/scripts/nexo-learning-validator 2.py +266 -0
  147. package/src/scripts/nexo-migrate 2.py +260 -0
  148. package/src/scripts/nexo-outcome-checker 2.py +127 -0
  149. package/src/scripts/nexo-postmortem-consolidator 2.py +456 -0
  150. package/src/scripts/nexo-pre-commit 2.py +120 -0
  151. package/src/scripts/nexo-prevent-sleep 2.sh +35 -0
  152. package/src/scripts/nexo-proactive-dashboard 2.py +354 -0
  153. package/src/scripts/nexo-reflection 2.py +256 -0
  154. package/src/scripts/nexo-runtime-preflight 2.py +274 -0
  155. package/src/scripts/nexo-sleep 2.py +631 -0
  156. package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
  157. package/src/scripts/nexo-sync-clients 2.py +16 -0
  158. package/src/scripts/nexo-synthesis 2.py +475 -0
  159. package/src/scripts/nexo-tcc-approve 2.sh +79 -0
  160. package/src/scripts/nexo-update 2.sh +306 -0
  161. package/src/scripts/nexo-watchdog 2.sh +1207 -0
  162. package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
  163. package/src/scripts/rehydrate_learnings_from_archive 2.py +245 -0
  164. package/src/server 2.py +1296 -0
  165. package/src/skills/run-nexo-audit-phase/guide 2.md +43 -0
  166. package/src/skills/run-nexo-audit-phase/skill 2.json +59 -0
  167. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +17 -0
  168. package/src/skills/run-nexo-core-fix-cycle/script 2.py +276 -0
  169. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +58 -0
  170. package/src/skills/run-release-final-audit/guide 2.md +16 -0
  171. package/src/skills/run-release-final-audit/script 2.py +259 -0
  172. package/src/skills/run-release-final-audit/skill 2.json +77 -0
  173. package/src/skills/run-runtime-doctor/guide 2.md +12 -0
  174. package/src/skills/run-runtime-doctor/script 2.py +21 -0
  175. package/src/skills/run-runtime-doctor/skill 2.json +25 -0
  176. package/src/skills_runtime 2.py +932 -0
  177. package/src/state_watchers_runtime 2.py +475 -0
  178. package/src/storage_router 2.py +32 -0
  179. package/src/system_catalog 2.py +786 -0
  180. package/src/tools_coordination 2.py +103 -0
  181. package/src/tools_credentials 2.py +68 -0
  182. package/src/tools_drive 2.py +487 -0
  183. package/src/tools_hot_context 2.py +163 -0
  184. package/src/tools_learnings 2.py +612 -0
  185. package/src/tools_menu 2.py +229 -0
  186. package/src/tools_reminders 2.py +88 -0
  187. package/src/tools_reminders_crud 2.py +363 -0
  188. package/src/tools_sessions 2.py +1054 -0
  189. package/src/tools_system_catalog 2.py +19 -0
  190. package/src/tools_task_history 2.py +57 -0
  191. package/src/tools_transcripts 2.py +98 -0
  192. package/src/transcript_utils 2.py +412 -0
  193. package/src/user_context 2.py +46 -0
  194. package/src/user_data_portability 2.py +328 -0
  195. package/src/user_state_model 2.py +170 -0
  196. package/templates/CLAUDE.md 2.template +108 -0
  197. package/templates/CODEX.AGENTS.md 2.template +66 -0
  198. package/templates/launchagents/README 2.md +132 -0
  199. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +39 -0
  200. package/templates/launchagents/com.nexo.auto-close-sessions.plist +1 -1
  201. package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
  202. package/templates/launchagents/com.nexo.catchup.plist +1 -1
  203. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +40 -0
  204. package/templates/launchagents/com.nexo.dashboard 2.plist +43 -0
  205. package/templates/launchagents/com.nexo.dashboard.plist +1 -1
  206. package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
  207. package/templates/launchagents/com.nexo.deep-sleep.plist +1 -1
  208. package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
  209. package/templates/launchagents/com.nexo.evolution.plist +1 -1
  210. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
  211. package/templates/launchagents/com.nexo.followup-hygiene.plist +1 -1
  212. package/templates/launchagents/com.nexo.immune 2.plist +41 -0
  213. package/templates/launchagents/com.nexo.immune.plist +1 -1
  214. package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
  215. package/templates/launchagents/com.nexo.postmortem.plist +1 -1
  216. package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
  217. package/templates/launchagents/com.nexo.self-audit.plist +1 -1
  218. package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
  219. package/templates/launchagents/com.nexo.synthesis.plist +1 -1
  220. package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
  221. package/templates/launchagents/com.nexo.watchdog.plist +1 -1
  222. package/templates/nexo_helper 2.py +301 -0
  223. package/templates/openclaw 2.json +13 -0
  224. package/templates/plugin-template 2.py +40 -0
  225. package/templates/script-template 2.py +59 -0
  226. package/templates/script-template 2.sh +13 -0
  227. package/templates/script-template.py +5 -4
  228. package/templates/skill-script-template 2.py +48 -0
  229. package/templates/skill-script-template.py +2 -1
  230. package/templates/skill-template 2.md +33 -0
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+ """
4
+ Deep Sleep v2 -- Phase 2: Extract findings from each session using the configured automation backend.
5
+
6
+ For each session in the context file, sends the extract-prompt.md to Claude
7
+ and collects structured findings. Merges all per-session results into
8
+ $DATE-extractions.json.
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
+ from datetime import datetime
18
+ from pathlib import Path
19
+
20
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
21
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parents[2])))
22
+ DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
23
+ PROMPT_FILE = Path(__file__).parent / "extract-prompt.md"
24
+
25
+ if str(NEXO_CODE) not in sys.path:
26
+ sys.path.insert(0, str(NEXO_CODE))
27
+
28
+ from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
29
+ try:
30
+ from client_preferences import resolve_user_model as _resolve_user_model
31
+ _USER_MODEL = _resolve_user_model()
32
+ except Exception:
33
+ _USER_MODEL = ""
34
+
35
+
36
+ # No timeout -- headless automation can take as long as needed
37
+ CLAUDE_TIMEOUT = 21600 # 3h safety net (prevents zombie processes)
38
+
39
+
40
+ def extract_json_from_response(text: str) -> dict | None:
41
+ """Parse JSON from Claude's response, handling markdown fences."""
42
+ text = text.strip()
43
+
44
+ # Strip markdown code fences if present
45
+ if text.startswith("```"):
46
+ lines = text.split("\n")
47
+ # Remove first line (```json or ```) and last line (```)
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
+ # Find the outermost JSON object
56
+ brace_start = text.find("{")
57
+ if brace_start < 0:
58
+ return None
59
+
60
+ # Find matching closing brace
61
+ depth = 0
62
+ for i in range(brace_start, len(text)):
63
+ if text[i] == "{":
64
+ depth += 1
65
+ elif text[i] == "}":
66
+ depth -= 1
67
+ if depth == 0:
68
+ try:
69
+ return json.loads(text[brace_start:i + 1])
70
+ except json.JSONDecodeError:
71
+ break
72
+ return None
73
+
74
+
75
+ def _safe_session_slug(session_id: str) -> str:
76
+ return (
77
+ session_id
78
+ .replace(".jsonl", "")
79
+ .replace(":", "-")
80
+ .replace("/", "-")
81
+ )
82
+
83
+
84
+ def find_session_file(session_id: str, date_dir: Path, session_txt_map: dict[str, str] | None = None) -> Path | None:
85
+ """Find the individual .txt file for a session."""
86
+ if session_txt_map:
87
+ mapped = session_txt_map.get(session_id)
88
+ if mapped:
89
+ candidate = date_dir / mapped
90
+ if candidate.exists():
91
+ return candidate
92
+ if date_dir and date_dir.exists():
93
+ sid_short = _safe_session_slug(session_id)[:20]
94
+ for f in sorted(date_dir.glob("session-*.txt")):
95
+ if sid_short in f.name:
96
+ return f
97
+ return None
98
+
99
+
100
+ def analyze_session(
101
+ session_id: str,
102
+ date_dir: Path,
103
+ shared_context_file: Path | None,
104
+ session_txt_map: dict[str, str] | None = None,
105
+ ) -> dict | None:
106
+ """Send a session to the automation backend for extraction analysis.
107
+
108
+ The backend reads the small per-session file + shared context file.
109
+ Prompt is short — the heavy lifting is in the Read tool calls.
110
+ """
111
+ session_file = find_session_file(session_id, date_dir, session_txt_map=session_txt_map)
112
+ if not session_file:
113
+ print(f" No session file found for {session_id}", file=sys.stderr)
114
+ return None
115
+
116
+ print(f" File: {session_file.name} ({session_file.stat().st_size / 1024:.0f} KB)")
117
+
118
+ # Build a short prompt — Claude reads the files itself
119
+ shared_ctx_instruction = ""
120
+ if shared_context_file and shared_context_file.exists():
121
+ shared_ctx_instruction = f"\n\nAlso read the shared context (followups, learnings, DB state) at: {shared_context_file}"
122
+
123
+ prompt_template = PROMPT_FILE.read_text()
124
+ prompt = prompt_template.replace("{{CONTEXT_FILE}}", str(session_file))
125
+ prompt = prompt.replace("{{SESSION_ID}}", session_id)
126
+ prompt += shared_ctx_instruction
127
+
128
+ try:
129
+ JSON_SYSTEM_PROMPT = (
130
+ "You are a JSON-only analyst. Your ENTIRE response must be a single valid JSON object. "
131
+ "No text before it. No text after it. No markdown fences. No explanations. "
132
+ "If you want to summarize, put it inside the JSON fields. Start with { and end with }."
133
+ )
134
+
135
+ result = run_automation_prompt(
136
+ prompt,
137
+ model=_USER_MODEL or "opus",
138
+ timeout=CLAUDE_TIMEOUT,
139
+ output_format="text",
140
+ append_system_prompt=JSON_SYSTEM_PROMPT,
141
+ allowed_tools="Read,Grep,Bash",
142
+ )
143
+
144
+ if result.returncode != 0:
145
+ print(f" Automation backend error (exit {result.returncode}): {result.stderr[:300]}", file=sys.stderr)
146
+ return None
147
+
148
+ # Filter out stop hook contamination (e.g. "Post-mortem completo.")
149
+ output = "\n".join(
150
+ line for line in result.stdout.splitlines()
151
+ if not line.strip().startswith("Post-mortem") and line.strip()
152
+ )
153
+ parsed = extract_json_from_response(output)
154
+
155
+ # Fallback: if Claude returned text instead of JSON, ask a short conversion call
156
+ if not parsed and len(output.strip()) > 50:
157
+ print(f" Got text instead of JSON ({len(output)} chars). Converting...")
158
+ convert_prompt = (
159
+ f"Convert the following analysis into the exact JSON schema required. "
160
+ f"Return ONLY the JSON object, nothing else.\n\n"
161
+ f"Analysis:\n{output[:8000]}\n\n"
162
+ f"Required schema: session_id, findings[], emotional_timeline[], "
163
+ f"abandoned_projects[], skill_candidates[], productivity_score, protocol_summary"
164
+ )
165
+ convert_result = run_automation_prompt(
166
+ convert_prompt,
167
+ model=_USER_MODEL or "sonnet",
168
+ timeout=120,
169
+ output_format="text",
170
+ append_system_prompt=JSON_SYSTEM_PROMPT,
171
+ )
172
+ if convert_result.returncode == 0:
173
+ parsed = extract_json_from_response(convert_result.stdout)
174
+ if parsed:
175
+ print(f" Conversion succeeded")
176
+
177
+ if not parsed:
178
+ # Save raw output for debugging
179
+ debug_file = DEEP_SLEEP_DIR / f"debug-extract-{session_id[:20]}.txt"
180
+ debug_file.write_text(result.stdout[:5000])
181
+ print(f" Failed to parse JSON. Raw output saved to {debug_file}", file=sys.stderr)
182
+ return None
183
+
184
+ return parsed
185
+
186
+ except AutomationBackendUnavailableError as exc:
187
+ print(f" Automation backend unavailable: {exc}", file=sys.stderr)
188
+ return None
189
+ except subprocess.TimeoutExpired:
190
+ print(f" Automation backend timeout ({CLAUDE_TIMEOUT}s)", file=sys.stderr)
191
+ return None
192
+
193
+
194
+ def main():
195
+ target_date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
196
+
197
+ context_file = DEEP_SLEEP_DIR / f"{target_date}-context.txt"
198
+ meta_file = DEEP_SLEEP_DIR / f"{target_date}-meta.json"
199
+ date_dir = DEEP_SLEEP_DIR / target_date
200
+
201
+ if not context_file.exists() and not date_dir.exists():
202
+ print(f"[extract] No context for {target_date}. Run collect.py first.")
203
+ sys.exit(1)
204
+
205
+ # Read metadata to get session list
206
+ if meta_file.exists():
207
+ with open(meta_file) as f:
208
+ meta = json.load(f)
209
+ session_files = meta.get("session_files", [])
210
+ session_txt_map = meta.get("session_txt_map", {})
211
+ else:
212
+ # Fallback: parse context file for session IDs
213
+ print("[extract] No meta file found, scanning context for sessions...")
214
+ session_files = []
215
+ session_txt_map = {}
216
+ if context_file.exists():
217
+ for line in context_file.read_text().splitlines():
218
+ if line.startswith("SESSION ") and ":" in line:
219
+ parts = line.split(":", 1)
220
+ if len(parts) == 2:
221
+ sid = parts[1].strip()
222
+ if sid.endswith(".jsonl"):
223
+ session_files.append(sid)
224
+
225
+ if not session_files:
226
+ print(f"[extract] No sessions to analyze for {target_date}.")
227
+ output = {"date": target_date, "sessions_analyzed": 0, "extractions": []}
228
+ output_file = DEEP_SLEEP_DIR / f"{target_date}-extractions.json"
229
+ with open(output_file, "w") as f:
230
+ json.dump(output, f, indent=2, ensure_ascii=False)
231
+ print(f"[extract] Output: {output_file}")
232
+ return
233
+
234
+ # Shared context file (followups, learnings, DB state)
235
+ shared_context_file = date_dir / "shared-context.txt" if date_dir.exists() else None
236
+ if shared_context_file and shared_context_file.exists():
237
+ print(f"[extract] Shared context: {shared_context_file} ({shared_context_file.stat().st_size / 1024:.0f} KB)")
238
+ else:
239
+ shared_context_file = None
240
+ print("[extract] No shared context file")
241
+
242
+ print(f"[extract] Phase 2: Analyzing {len(session_files)} sessions for {target_date}")
243
+ print("[extract] Automation backend: schedule-configured")
244
+
245
+ # Checkpoint directory: one JSON per session, survives crashes
246
+ checkpoint_dir = date_dir / "checkpoints"
247
+ checkpoint_dir.mkdir(parents=True, exist_ok=True)
248
+
249
+ all_extractions = []
250
+ total_findings = 0
251
+ skipped = 0
252
+ MAX_RETRIES = 3
253
+
254
+ for i, session_id in enumerate(session_files):
255
+ sid_safe = _safe_session_slug(session_id)[:40]
256
+ checkpoint_file = checkpoint_dir / f"{sid_safe}.json"
257
+
258
+ # Resume: skip already-processed sessions
259
+ if checkpoint_file.exists():
260
+ try:
261
+ with open(checkpoint_file) as f:
262
+ cached = json.load(f)
263
+ findings_count = len(cached.get("findings", []))
264
+ total_findings += findings_count
265
+ all_extractions.append(cached)
266
+ skipped += 1
267
+ print(f"[extract] Session {i + 1}/{len(session_files)}: {session_id} (cached, {findings_count} findings)")
268
+ continue
269
+ except (json.JSONDecodeError, KeyError):
270
+ pass # Corrupted checkpoint, re-process
271
+
272
+ print(f"[extract] Session {i + 1}/{len(session_files)}: {session_id}")
273
+
274
+ # Retry loop
275
+ result = None
276
+ for attempt in range(1, MAX_RETRIES + 1):
277
+ result = analyze_session(
278
+ session_id,
279
+ date_dir,
280
+ shared_context_file,
281
+ session_txt_map=session_txt_map,
282
+ )
283
+ if result:
284
+ break
285
+ if attempt < MAX_RETRIES:
286
+ print(f" -> Attempt {attempt}/{MAX_RETRIES} failed, retrying...")
287
+
288
+ if result:
289
+ findings_count = len(result.get("findings", []))
290
+ total_findings += findings_count
291
+ all_extractions.append(result)
292
+ # Save checkpoint
293
+ with open(checkpoint_file, "w") as f:
294
+ json.dump(result, f, indent=2, ensure_ascii=False)
295
+ print(f" -> {findings_count} findings extracted (checkpointed)")
296
+ else:
297
+ print(f" -> Failed after {MAX_RETRIES} attempts, marking as failed")
298
+ failed_entry = {
299
+ "session_id": session_id,
300
+ "findings": [],
301
+ "error": f"Extraction failed after {MAX_RETRIES} attempts"
302
+ }
303
+ all_extractions.append(failed_entry)
304
+ # Save failed checkpoint too (so we don't retry forever)
305
+ with open(checkpoint_file, "w") as f:
306
+ json.dump(failed_entry, f, indent=2, ensure_ascii=False)
307
+
308
+ # Merge into output
309
+ output = {
310
+ "date": target_date,
311
+ "sessions_analyzed": len(session_files),
312
+ "sessions_succeeded": len([e for e in all_extractions if "error" not in e]),
313
+ "sessions_cached": skipped,
314
+ "total_findings": total_findings,
315
+ "extractions": all_extractions
316
+ }
317
+
318
+ output_file = DEEP_SLEEP_DIR / f"{target_date}-extractions.json"
319
+ with open(output_file, "w") as f:
320
+ json.dump(output, f, indent=2, ensure_ascii=False)
321
+
322
+ if skipped:
323
+ print(f"\n[extract] Done. {total_findings} findings from {len(session_files)} sessions ({skipped} cached, {len(session_files) - skipped} new).")
324
+ else:
325
+ print(f"\n[extract] Done. {total_findings} findings from {len(session_files)} sessions.")
326
+ print(f"[extract] Output: {output_file}")
327
+
328
+
329
+ if __name__ == "__main__":
330
+ main()
@@ -0,0 +1,285 @@
1
+ # Deep Sleep v2 -- Phase 2: Session Extraction
2
+
3
+ You are an overnight analyst for an AI agent's cognitive memory system. You have access to the complete transcript of a session between a user and their AI agent.
4
+
5
+ ## Setup
6
+
7
+ FIRST: Call `nexo_startup` with `task='deep-sleep extraction'` to initialize the system.
8
+
9
+ ## Your Task
10
+
11
+ Read the context file provided below. It contains:
12
+ - The full session transcript (user messages, agent responses, tool usage)
13
+ - Active followups, learnings, trust history, and other system state
14
+
15
+ For the given session, extract the following categories of findings.
16
+
17
+ ### 1. Uncaptured Corrections
18
+ The user corrected the agent but no learning was saved.
19
+ Signals: frustration tone, repeating instructions 2+ times, explicit corrections ("no", "wrong", "that's not it"), the user having to explain something twice.
20
+ For each, note whether it was already captured as a learning (check if `nexo_learning_add` was called after the correction).
21
+
22
+ ### 2. Self-Corrected Errors
23
+ The agent searched in the wrong place, used the wrong approach, then found the right answer.
24
+ These represent knowledge gaps that should become learnings so the agent goes to the right place next time.
25
+ Example: agent looked for config in `/etc/` but it was in `~/.config/`.
26
+
27
+ ### 3. Unformalised Ideas
28
+ The user mentioned an idea, plan, or intention that was never formalized into a followup or reminder.
29
+ Signals: "we should...", "it would be nice...", "we could...", "I want to...", future-tense plans without deadlines.
30
+
31
+ ### 4. Missed Commitments
32
+ The user or agent said "tomorrow", "next week", "when I have time", "I'll look at it later" -- but no followup was created.
33
+ Check the tool usage log: was `nexo_followup_create` or `nexo_reminder_create` called after the commitment?
34
+
35
+ ### 5. Protocol Compliance
36
+ Check whether the agent followed standard protocols:
37
+ - `guard_check` called before editing production/shared files?
38
+ - `heartbeat` called with meaningful context?
39
+ - `change_log` called after production code changes?
40
+ - `learning_add` called after resolving errors?
41
+ - `followup_complete` called when user confirmed a task was done?
42
+ - `feedback` captured after corrections?
43
+
44
+ ### 6. Emotional Signals
45
+ Detect the user's emotional state throughout the session. Look for:
46
+ - Frustration: short replies, cursing, "no", "otra vez", repeated corrections, tone shifts
47
+ - Flow state: long productive stretches, "perfecto", "sí", rapid-fire instructions
48
+ - Satisfaction: praise, "bien", "genial", accepting work without pushback
49
+ - Disengagement: shorter messages over time, "ok", "vale", stopping mid-task
50
+ - Stress: urgency words, deadlines, multiple tasks at once
51
+ For each signal, note the approximate point in the session (early/mid/late) and what triggered it.
52
+
53
+ ### 7. Abandoned Projects
54
+ Detect work that was started but not finished in this session:
55
+ - Tasks the user mentioned wanting to do but never got to
56
+ - Work that was interrupted by something else and never resumed
57
+ - Features partially implemented then dropped
58
+ - Investigations started but conclusions never reached
59
+ Only flag if the work was NOT captured in a followup or reminder.
60
+
61
+ ### 9. Skill Candidates (Reusable Procedures)
62
+ Detect multi-step tasks that were completed successfully and could be reused:
63
+ - Tasks that required 3+ distinct steps to complete
64
+ - Tasks where the agent followed a clear sequence of actions
65
+ - Procedures that are likely to be repeated in the future
66
+ - Examples: deploying code, configuring a service, running an audit, setting up infrastructure
67
+
68
+ For each candidate, extract:
69
+ - The full step-by-step procedure (what was actually done, in order)
70
+ - Tags describing the domain (e.g., "shopify", "chrome", "deploy")
71
+ - Trigger phrases that would indicate this procedure is needed (e.g., "deploy extension", "push theme")
72
+ - Any gotchas or warnings discovered during execution
73
+ - Whether the procedure looks scriptable (`scriptable: true|false`)
74
+ - Automation scope: `read-only|local|remote`
75
+ - Candidate params if the procedure clearly has repeated inputs (e.g. store, version, environment)
76
+ - Which steps are automatable vs still manual
77
+
78
+ Only flag if the procedure was SUCCESSFUL (the task was completed without major failures).
79
+ Do NOT flag trivial tasks (single-step actions, simple file edits, quick lookups).
80
+
81
+ ### 8. Productivity Patterns
82
+ Analyze how the session went in terms of efficiency:
83
+ - How many times did the agent need correction before getting it right?
84
+ - Did the agent anticipate needs or always wait for instructions?
85
+ - Were there unnecessary back-and-forths that a better approach would have avoided?
86
+ - Did the agent propose solutions or just ask questions?
87
+ - Tool usage: which tools were used most? Any tools used unnecessarily or not used when they should have been?
88
+
89
+ ## Output Format
90
+
91
+ Return ONLY valid JSON. No markdown code fences. No explanation text before or after.
92
+
93
+ ```json
94
+ {
95
+ "session_id": "filename.jsonl",
96
+ "findings": [
97
+ {
98
+ "type": "uncaptured_correction",
99
+ "confidence": 0.9,
100
+ "impact": "high",
101
+ "reversibility": "reversible",
102
+ "evidence": {
103
+ "type": "transcript",
104
+ "session_id": "filename.jsonl",
105
+ "message_index": 42,
106
+ "quote": "Exact user words (max 150 chars)"
107
+ },
108
+ "dedupe_key": "correction-<short-hash-of-content>",
109
+ "action_class": "auto_apply",
110
+ "description": "What the agent should have learned",
111
+ "suggested_action": "learning_add",
112
+ "suggested_content": {
113
+ "category": "process|code|ui|communication",
114
+ "title": "Short title for the learning",
115
+ "content": "Full learning content"
116
+ }
117
+ },
118
+ {
119
+ "type": "self_corrected_error",
120
+ "confidence": 0.85,
121
+ "impact": "medium",
122
+ "reversibility": "reversible",
123
+ "evidence": {
124
+ "type": "transcript",
125
+ "session_id": "filename.jsonl",
126
+ "message_index": 15,
127
+ "quote": "What the agent did wrong and how it found the right answer"
128
+ },
129
+ "dedupe_key": "selfcorrect-<short-hash>",
130
+ "action_class": "auto_apply",
131
+ "description": "Knowledge gap identified",
132
+ "suggested_action": "learning_add",
133
+ "suggested_content": {
134
+ "category": "code",
135
+ "title": "Short title",
136
+ "content": "Full learning content"
137
+ }
138
+ },
139
+ {
140
+ "type": "unformalised_idea",
141
+ "confidence": 0.7,
142
+ "impact": "medium",
143
+ "reversibility": "reversible",
144
+ "evidence": {
145
+ "type": "transcript",
146
+ "session_id": "filename.jsonl",
147
+ "message_index": 88,
148
+ "quote": "User's exact words"
149
+ },
150
+ "dedupe_key": "idea-<short-hash>",
151
+ "action_class": "draft_for_morning",
152
+ "description": "What the idea is about",
153
+ "suggested_action": "followup_create",
154
+ "suggested_content": {
155
+ "description": "Followup description",
156
+ "date": "YYYY-MM-DD or empty"
157
+ }
158
+ },
159
+ {
160
+ "type": "missed_commitment",
161
+ "confidence": 0.8,
162
+ "impact": "medium",
163
+ "reversibility": "reversible",
164
+ "evidence": {
165
+ "type": "transcript",
166
+ "session_id": "filename.jsonl",
167
+ "message_index": 102,
168
+ "quote": "User's exact words"
169
+ },
170
+ "dedupe_key": "commitment-<short-hash>",
171
+ "action_class": "auto_apply",
172
+ "description": "What was promised",
173
+ "suggested_action": "followup_create",
174
+ "suggested_content": {
175
+ "description": "Followup description",
176
+ "date": "YYYY-MM-DD"
177
+ }
178
+ },
179
+ {
180
+ "type": "protocol_violation",
181
+ "confidence": 0.95,
182
+ "impact": "low",
183
+ "reversibility": "reversible",
184
+ "evidence": {
185
+ "type": "transcript",
186
+ "session_id": "filename.jsonl",
187
+ "message_index": 50,
188
+ "quote": "What happened"
189
+ },
190
+ "dedupe_key": "protocol-<protocol-name>-<short-hash>",
191
+ "action_class": "auto_apply",
192
+ "description": "Which protocol was violated and how",
193
+ "suggested_action": "learning_add",
194
+ "suggested_content": {
195
+ "category": "process",
196
+ "title": "Protocol compliance: <protocol name>",
197
+ "content": "Details of what should have been done"
198
+ }
199
+ }
200
+ ],
201
+ "emotional_timeline": [
202
+ {
203
+ "phase": "early|mid|late",
204
+ "emotion": "frustrated|flow|satisfied|disengaged|stressed|neutral",
205
+ "trigger": "What caused this emotional state (max 100 chars)",
206
+ "intensity": 0.8
207
+ }
208
+ ],
209
+
210
+ "abandoned_projects": [
211
+ {
212
+ "description": "What was started but not finished",
213
+ "reason": "interrupted|dropped|forgotten|deferred",
214
+ "has_followup": false
215
+ }
216
+ ],
217
+
218
+ "skill_candidates": [
219
+ {
220
+ "name": "Short name for the procedure (e.g., Deploy Chrome Extension)",
221
+ "description": "What this procedure accomplishes (1-2 sentences)",
222
+ "steps": [
223
+ "Step 1: What was done first",
224
+ "Step 2: What was done next",
225
+ "Step 3: etc."
226
+ ],
227
+ "tags": ["domain1", "domain2"],
228
+ "trigger_phrases": ["phrase that would trigger this", "another trigger"],
229
+ "gotchas": ["Warning or caveat discovered during execution"],
230
+ "scriptable": false,
231
+ "automation_scope": "read-only|local|remote",
232
+ "candidate_params": {
233
+ "param_name": {
234
+ "type": "string|integer|number|boolean",
235
+ "required": true
236
+ }
237
+ },
238
+ "automatable_steps": ["Step that could be automated"],
239
+ "manual_steps": ["Step that still needs human judgment"],
240
+ "evidence": {
241
+ "type": "transcript",
242
+ "session_id": "filename.jsonl",
243
+ "message_index": 10,
244
+ "quote": "Start of the multi-step task"
245
+ },
246
+ "confidence": 0.85
247
+ }
248
+ ],
249
+
250
+ "productivity_score": {
251
+ "corrections_needed": 0,
252
+ "proactivity": "reactive|mixed|proactive",
253
+ "unnecessary_roundtrips": 0,
254
+ "tool_efficiency": "efficient|mixed|wasteful",
255
+ "notable_tools": ["tool1", "tool2"],
256
+ "summary": "One sentence assessment of session productivity"
257
+ },
258
+
259
+ "protocol_summary": {
260
+ "guard_check": {"required": 0, "executed": 0},
261
+ "heartbeat": {"total": 0, "with_context": 0},
262
+ "change_log": {"edits": 0, "logged": 0},
263
+ "learning_capture": {"errors": 0, "captured": 0},
264
+ "followup_complete": {"confirmed_done": 0, "marked": 0}
265
+ }
266
+ }
267
+ ```
268
+
269
+ ## Rules
270
+
271
+ - Be SPECIFIC. Quote the user's exact words in evidence.
272
+ - Only flag REAL issues. If the agent did capture something correctly, do not flag it.
273
+ - `confidence` is 0.0-1.0. Be honest -- if you're unsure, use a lower value.
274
+ - `action_class`: use `auto_apply` when confidence >= 0.8 and the action is reversible. Use `draft_for_morning` when confidence < 0.8 or when the impact is high.
275
+ - `dedupe_key`: create a short, deterministic key from the finding type + core content so duplicate findings across runs are suppressed.
276
+ - Do NOT use any specific agent name -- refer to "the agent" throughout.
277
+ - If no issues found, return `{"session_id": "...", "findings": [], "protocol_summary": {...}}`.
278
+ - Do NOT invent problems. Empty findings are perfectly fine.
279
+ - Respond in the user's language (check calibration.json if available). The JSON keys stay in English, but `description`, `title`, `content` fields should be in the user's language.
280
+
281
+ ## Context
282
+
283
+ Read the session transcript at this path: {{CONTEXT_FILE}}
284
+
285
+ Analyze session: {{SESSION_ID}}