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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/nexo-brain.js +52 -10
- package/package.json +1 -1
- package/src/auto_update.py +11 -8
- package/src/dashboard/static/favicon 2.svg +32 -0
- package/src/dashboard/static/nexo-logo 2.png +0 -0
- package/src/dashboard/static/nexo-logo 2.svg +40 -0
- package/src/dashboard/static/style 2.css +2458 -0
- package/src/dashboard/templates/adaptive 2.html +118 -0
- package/src/dashboard/templates/artifacts 2.html +133 -0
- package/src/dashboard/templates/backups 2.html +136 -0
- package/src/dashboard/templates/base 2.html +417 -0
- package/src/dashboard/templates/calendar 2.html +591 -0
- package/src/dashboard/templates/chat 2.html +356 -0
- package/src/dashboard/templates/claims 2.html +259 -0
- package/src/dashboard/templates/cortex 2.html +321 -0
- package/src/dashboard/templates/credentials 2.html +128 -0
- package/src/dashboard/templates/crons 2.html +370 -0
- package/src/dashboard/templates/dashboard 2.html +494 -0
- package/src/dashboard/templates/dreams 2.html +252 -0
- package/src/dashboard/templates/email 2.html +160 -0
- package/src/dashboard/templates/evolution 2.html +189 -0
- package/src/dashboard/templates/feed 2.html +249 -0
- package/src/dashboard/templates/followup_health 2.html +170 -0
- package/src/dashboard/templates/graph 2.html +201 -0
- package/src/dashboard/templates/guard 2.html +259 -0
- package/src/dashboard/templates/inbox 2.html +251 -0
- package/src/dashboard/templates/memory 2.html +420 -0
- package/src/dashboard/templates/operations 2.html +608 -0
- package/src/dashboard/templates/plugins 2.html +185 -0
- package/src/dashboard/templates/protocol 2.html +199 -0
- package/src/dashboard/templates/rules 2.html +246 -0
- package/src/dashboard/templates/sentiment 2.html +247 -0
- package/src/dashboard/templates/sessions 2.html +218 -0
- package/src/dashboard/templates/skills 2.html +329 -0
- package/src/dashboard/templates/somatic 2.html +73 -0
- package/src/dashboard/templates/triggers 2.html +133 -0
- package/src/dashboard/templates/trust 2.html +360 -0
- package/src/db/__init__ 2.py +259 -0
- package/src/db/_core 2.py +437 -0
- package/src/db/_credentials 2.py +124 -0
- package/src/db/_episodic 2.py +762 -0
- package/src/db/_evolution 2.py +54 -0
- package/src/db/_fts 2.py +406 -0
- package/src/db/_goal_profiles 2.py +376 -0
- package/src/db/_hot_context 2.py +660 -0
- package/src/db/_outcomes 2.py +800 -0
- package/src/db/_personal_scripts 2.py +582 -0
- package/src/db/_sessions 2.py +330 -0
- package/src/db/_tasks 2.py +91 -0
- package/src/db/_watchers 2.py +173 -0
- package/src/doctor/formatters 2.py +52 -0
- package/src/doctor/models 2.py +69 -0
- package/src/doctor/planes 2.py +87 -0
- package/src/doctor/providers/__init__ 2.py +1 -0
- package/src/doctor/providers/deep 2.py +367 -0
- package/src/evolution_cycle 2.py +519 -0
- package/src/hooks/auto_capture 2.py +208 -0
- package/src/hooks/caffeinate-guard 2.sh +8 -0
- package/src/hooks/capture-session 2.sh +21 -0
- package/src/hooks/capture-tool-logs 2.sh +158 -0
- package/src/hooks/daily-briefing-check 2.sh +33 -0
- package/src/hooks/heartbeat-enforcement 2.py +90 -0
- package/src/hooks/heartbeat-posttool 2.sh +18 -0
- package/src/hooks/inbox-hook 2.sh +76 -0
- package/src/hooks/post-compact 2.sh +152 -0
- package/src/hooks/pre-compact 2.sh +169 -0
- package/src/hooks/protocol-guardrail 2.sh +10 -0
- package/src/hooks/protocol-pretool-guardrail 2.sh +9 -0
- package/src/hooks/session-stop 2.sh +52 -0
- package/src/kg_populate 2.py +292 -0
- package/src/maintenance 2.py +53 -0
- package/src/memory_backends 2.py +71 -0
- package/src/migrate_embeddings 2.py +124 -0
- package/src/nexo_sdk 2.py +103 -0
- package/src/observability 2.py +199 -0
- package/src/plugin_loader 2.py +217 -0
- package/src/plugins/__init__ 2.py +0 -0
- package/src/plugins/artifact_registry 2.py +450 -0
- package/src/plugins/backup 2.py +127 -0
- package/src/plugins/claims_tools 2.py +119 -0
- package/src/plugins/cognitive_memory 2.py +609 -0
- package/src/plugins/core_rules 2.py +252 -0
- package/src/plugins/cortex 2.py +1155 -0
- package/src/plugins/entities 2.py +67 -0
- package/src/plugins/episodic_memory 2.py +560 -0
- package/src/plugins/evolution 2.py +167 -0
- package/src/plugins/goal_engine 2.py +142 -0
- package/src/plugins/guard 2.py +862 -0
- package/src/plugins/impact 2.py +29 -0
- package/src/plugins/knowledge_graph_tools 2.py +137 -0
- package/src/plugins/media_memory_tools 2.py +98 -0
- package/src/plugins/memory_export 2.py +196 -0
- package/src/plugins/outcomes 2.py +130 -0
- package/src/plugins/personal_scripts 2.py +117 -0
- package/src/plugins/preferences 2.py +47 -0
- package/src/plugins/protocol 2.py +1449 -0
- package/src/plugins/simple_api 2.py +106 -0
- package/src/plugins/skills 2.py +341 -0
- package/src/plugins/state_watchers 2.py +79 -0
- package/src/plugins/update 2.py +986 -0
- package/src/plugins/user_state_tools 2.py +43 -0
- package/src/plugins/workflow 2.py +588 -0
- package/src/protocol_settings 2.py +59 -0
- package/src/public_contribution 2.py +466 -0
- package/src/public_evolution_queue 2.py +241 -0
- package/src/requirements 2.txt +14 -0
- package/src/retroactive_learnings 2.py +373 -0
- package/src/rules/__init__ 2.py +0 -0
- package/src/rules/core-rules 2.json +331 -0
- package/src/rules/migrate 2.py +207 -0
- package/src/runtime_power 2.py +874 -0
- package/src/script_registry 2.py +1559 -0
- package/src/scripts/check-context 2.py +272 -0
- package/src/scripts/deep-sleep/apply_findings 2.py +2327 -0
- package/src/scripts/deep-sleep/collect 2.py +928 -0
- package/src/scripts/deep-sleep/extract 2.py +330 -0
- package/src/scripts/deep-sleep/extract-prompt 2.md +285 -0
- package/src/scripts/deep-sleep/synthesize 2.py +312 -0
- package/src/scripts/deep-sleep/synthesize-prompt 2.md +336 -0
- package/src/scripts/nexo-agent-run 2.py +75 -0
- package/src/scripts/nexo-auto-update 2.py +6 -0
- package/src/scripts/nexo-backup 2.sh +25 -0
- package/src/scripts/nexo-brain-activation 2.sh +140 -0
- package/src/scripts/nexo-catchup 2.py +300 -0
- package/src/scripts/nexo-cognitive-decay 2.py +257 -0
- package/src/scripts/nexo-cortex-cycle 2.py +293 -0
- package/src/scripts/nexo-cron-wrapper 2.sh +53 -0
- package/src/scripts/nexo-daily-self-audit 2.py +2161 -0
- package/src/scripts/nexo-dashboard 2.sh +29 -0
- package/src/scripts/nexo-deep-sleep 2.sh +86 -0
- package/src/scripts/nexo-evolution-run 2.py +1664 -0
- package/src/scripts/nexo-followup-hygiene 2.py +139 -0
- package/src/scripts/nexo-hook-record 2.py +42 -0
- package/src/scripts/nexo-immune 2.py +936 -0
- package/src/scripts/nexo-impact-scorer 2.py +117 -0
- package/src/scripts/nexo-inbox-hook 2.sh +74 -0
- package/src/scripts/nexo-install 2.py +6 -0
- package/src/scripts/nexo-learning-housekeep 2.py +401 -0
- package/src/scripts/nexo-learning-validator 2.py +266 -0
- package/src/scripts/nexo-migrate 2.py +260 -0
- package/src/scripts/nexo-outcome-checker 2.py +127 -0
- package/src/scripts/nexo-postmortem-consolidator 2.py +456 -0
- package/src/scripts/nexo-pre-commit 2.py +120 -0
- package/src/scripts/nexo-prevent-sleep 2.sh +35 -0
- package/src/scripts/nexo-proactive-dashboard 2.py +354 -0
- package/src/scripts/nexo-reflection 2.py +256 -0
- package/src/scripts/nexo-runtime-preflight 2.py +274 -0
- package/src/scripts/nexo-sleep 2.py +631 -0
- package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
- package/src/scripts/nexo-sync-clients 2.py +16 -0
- package/src/scripts/nexo-synthesis 2.py +475 -0
- package/src/scripts/nexo-tcc-approve 2.sh +79 -0
- package/src/scripts/nexo-update 2.sh +306 -0
- package/src/scripts/nexo-watchdog 2.sh +1207 -0
- package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
- package/src/scripts/rehydrate_learnings_from_archive 2.py +245 -0
- package/src/server 2.py +1296 -0
- package/src/skills/run-nexo-audit-phase/guide 2.md +43 -0
- package/src/skills/run-nexo-audit-phase/skill 2.json +59 -0
- package/src/skills/run-nexo-core-fix-cycle/guide 2.md +17 -0
- package/src/skills/run-nexo-core-fix-cycle/script 2.py +276 -0
- package/src/skills/run-nexo-core-fix-cycle/skill 2.json +58 -0
- package/src/skills/run-release-final-audit/guide 2.md +16 -0
- package/src/skills/run-release-final-audit/script 2.py +259 -0
- package/src/skills/run-release-final-audit/skill 2.json +77 -0
- package/src/skills/run-runtime-doctor/guide 2.md +12 -0
- package/src/skills/run-runtime-doctor/script 2.py +21 -0
- package/src/skills/run-runtime-doctor/skill 2.json +25 -0
- package/src/skills_runtime 2.py +932 -0
- package/src/state_watchers_runtime 2.py +475 -0
- package/src/storage_router 2.py +32 -0
- package/src/system_catalog 2.py +786 -0
- package/src/tools_coordination 2.py +103 -0
- package/src/tools_credentials 2.py +68 -0
- package/src/tools_drive 2.py +487 -0
- package/src/tools_hot_context 2.py +163 -0
- package/src/tools_learnings 2.py +612 -0
- package/src/tools_menu 2.py +229 -0
- package/src/tools_reminders 2.py +88 -0
- package/src/tools_reminders_crud 2.py +363 -0
- package/src/tools_sessions 2.py +1054 -0
- package/src/tools_system_catalog 2.py +19 -0
- package/src/tools_task_history 2.py +57 -0
- package/src/tools_transcripts 2.py +98 -0
- package/src/transcript_utils 2.py +412 -0
- package/src/user_context 2.py +46 -0
- package/src/user_data_portability 2.py +328 -0
- package/src/user_state_model 2.py +170 -0
- package/templates/CLAUDE.md 2.template +108 -0
- package/templates/CODEX.AGENTS.md 2.template +66 -0
- package/templates/launchagents/README 2.md +132 -0
- package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +39 -0
- package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
- package/templates/launchagents/com.nexo.cognitive-decay 2.plist +40 -0
- package/templates/launchagents/com.nexo.dashboard 2.plist +43 -0
- package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
- package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
- package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
- package/templates/launchagents/com.nexo.immune 2.plist +41 -0
- package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
- package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
- package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
- package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
- package/templates/nexo_helper 2.py +301 -0
- package/templates/openclaw 2.json +13 -0
- package/templates/plugin-template 2.py +40 -0
- package/templates/script-template 2.py +59 -0
- package/templates/script-template 2.sh +13 -0
- package/templates/skill-script-template 2.py +48 -0
- 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}}
|