nexo-brain 2.1.0 → 2.3.0
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/README.md +7 -7
- package/bin/nexo-brain.js +53 -26
- package/package.json +1 -1
- package/scripts/migrate-to-unified 2.sh +813 -0
- package/scripts/migrate-v1.5-to-v1.6 2.py +778 -0
- package/scripts/migrate-v1.7-to-v1.8 2.py +214 -0
- package/scripts/migrate-v1.7-to-v1.8.py +2 -2
- package/scripts/nexo-preflight.sh +236 -0
- package/scripts/pre-commit-check 2.sh +55 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
- package/src/auto_close_sessions 2.py +159 -0
- package/src/auto_update 2.py +634 -0
- package/src/auto_update.py +25 -0
- package/src/claim_graph 2.py +323 -0
- package/src/cognitive/__init__ 2.py +62 -0
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/cognitive/_core 2.py +567 -0
- package/src/cognitive/_decay 2.py +382 -0
- package/src/cognitive/_ingest 2.py +892 -0
- package/src/cognitive/_memory 2.py +912 -0
- package/src/cognitive/_search 2.py +949 -0
- package/src/cognitive/_trust 2.py +464 -0
- package/src/cognitive/_trust.py +10 -36
- package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
- package/src/crons/manifest 2.json +106 -0
- package/src/crons/manifest.json +6 -13
- package/src/crons/sync 2.py +217 -0
- package/src/crons/sync.py +151 -6
- package/src/dashboard/__init__ 2.py +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/app 2.py +789 -0
- package/src/db/__init__ 2.py +89 -0
- package/src/db/__init__.py +13 -0
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
- package/src/db/_core 2.py +417 -0
- package/src/db/_credentials 2.py +124 -0
- package/src/db/_cron_runs.py +74 -0
- package/src/db/_entities 2.py +178 -0
- package/src/db/_episodic 2.py +738 -0
- package/src/db/_episodic.py +40 -6
- package/src/db/_evolution 2.py +54 -0
- package/src/db/_fts 2.py +406 -0
- package/src/db/_learnings 2.py +168 -0
- package/src/db/_reminders 2.py +338 -0
- package/src/db/_schema 2.py +364 -0
- package/src/db/_schema.py +64 -0
- package/src/db/_sessions 2.py +300 -0
- package/src/db/_skills.py +514 -0
- package/src/db/_tasks 2.py +91 -0
- package/src/evolution_cycle 2.py +266 -0
- package/src/hnsw_index 2.py +254 -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-session.sh +2 -0
- package/src/hooks/capture-tool-logs 2.sh +127 -0
- package/src/hooks/capture-tool-logs.sh +3 -2
- package/src/hooks/daily-briefing-check 2.sh +33 -0
- package/src/hooks/inbox-hook 2.sh +76 -0
- package/src/hooks/inbox-hook.sh +3 -2
- package/src/hooks/post-compact 2.sh +148 -0
- package/src/hooks/post-compact.sh +1 -1
- package/src/hooks/pre-compact 2.sh +151 -0
- package/src/hooks/pre-compact.sh +1 -1
- package/src/hooks/session-start 2.sh +268 -0
- package/src/hooks/session-start.sh +6 -3
- package/src/hooks/session-stop 2.sh +140 -0
- package/src/hooks/session-stop.sh +14 -102
- package/src/kg_populate 2.py +290 -0
- package/src/knowledge_graph 2.py +257 -0
- package/src/maintenance 2.py +59 -0
- package/src/migrate_embeddings 2.py +122 -0
- package/src/plugin_loader 2.py +202 -0
- package/src/plugins/__init__ 2.py +0 -0
- package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/adaptive_mode 2.py +805 -0
- package/src/plugins/agents 2.py +52 -0
- package/src/plugins/artifact_registry 2.py +450 -0
- package/src/plugins/backup 2.py +104 -0
- package/src/plugins/cognitive_memory 2.py +564 -0
- package/src/plugins/core_rules 2.py +252 -0
- package/src/plugins/cortex 2.py +299 -0
- package/src/plugins/entities 2.py +67 -0
- package/src/plugins/episodic_memory 2.py +533 -0
- package/src/plugins/episodic_memory.py +5 -3
- package/src/plugins/evolution 2.py +115 -0
- package/src/plugins/guard 2.py +746 -0
- package/src/plugins/knowledge_graph_tools 2.py +105 -0
- package/src/plugins/preferences 2.py +47 -0
- package/src/plugins/schedule.py +212 -0
- package/src/plugins/skills.py +264 -0
- package/src/plugins/update 2.py +256 -0
- package/src/requirements 2.txt +12 -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/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/check-context 2.py +264 -0
- package/src/scripts/deep-sleep/apply_findings.py +168 -8
- package/src/scripts/deep-sleep/collect.py +33 -11
- package/src/scripts/deep-sleep/extract-prompt.md +38 -0
- package/src/scripts/deep-sleep/extract.py +80 -8
- package/src/scripts/deep-sleep/synthesize-prompt.md +59 -2
- package/src/scripts/deep-sleep/synthesize.py +3 -1
- 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 +242 -0
- package/src/scripts/nexo-catchup.py +65 -29
- package/src/scripts/nexo-cognitive-decay 2.py +182 -0
- package/src/scripts/nexo-cron-wrapper.sh +53 -0
- package/src/scripts/nexo-daily-self-audit 2.py +552 -0
- package/src/scripts/nexo-daily-self-audit.py +4 -2
- package/src/scripts/nexo-deep-sleep 2.sh +97 -0
- package/src/scripts/nexo-deep-sleep.sh +66 -77
- package/src/scripts/nexo-evolution-run 2.py +597 -0
- package/src/scripts/nexo-evolution-run.py +13 -0
- package/src/scripts/nexo-followup-hygiene 2.py +112 -0
- package/src/scripts/nexo-immune 2.py +927 -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 +245 -0
- package/src/scripts/nexo-learning-housekeep.py +156 -1
- package/src/scripts/nexo-learning-validator 2.py +207 -0
- package/src/scripts/nexo-learning-validator.py +19 -0
- package/src/scripts/nexo-migrate 2.py +232 -0
- package/src/scripts/nexo-postmortem-consolidator 2.py +421 -0
- package/src/scripts/nexo-postmortem-consolidator.py +3 -2
- package/src/scripts/nexo-pre-commit 2.py +120 -0
- package/src/scripts/nexo-prevent-sleep 2.sh +29 -0
- package/src/scripts/nexo-proactive-dashboard 2.py +345 -0
- package/src/scripts/nexo-reflection 2.py +253 -0
- package/src/scripts/nexo-runtime-preflight 2.py +274 -0
- package/src/scripts/nexo-send-email 2.py +25 -0
- package/src/scripts/nexo-send-reply 2.py +178 -0
- package/src/scripts/nexo-sleep 2.py +592 -0
- package/src/scripts/nexo-sleep.py +16 -11
- package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
- package/src/scripts/nexo-synthesis 2.py +253 -0
- package/src/scripts/nexo-synthesis.py +46 -3
- package/src/scripts/nexo-tcc-approve 2.sh +79 -0
- package/src/scripts/nexo-update 2.sh +161 -0
- package/src/scripts/nexo-watchdog 2.sh +878 -0
- package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
- package/src/scripts/nexo-watchdog.sh +72 -19
- package/src/server 2.py +733 -0
- package/src/server.py +11 -2
- package/src/storage_router 2.py +32 -0
- package/src/tools_coordination 2.py +102 -0
- package/src/tools_credentials 2.py +68 -0
- package/src/tools_learnings 2.py +220 -0
- package/src/tools_menu 2.py +227 -0
- package/src/tools_reminders 2.py +86 -0
- package/src/tools_reminders_crud 2.py +159 -0
- package/src/tools_reminders_crud.py +7 -0
- package/src/tools_sessions 2.py +476 -0
- package/src/tools_task_history 2.py +57 -0
- package/templates/CLAUDE.md 2.template +63 -0
- package/templates/openclaw 2.json +13 -0
- package/tests/__init__ 2.py +0 -0
- package/tests/conftest 2.py +71 -0
- package/tests/test_cognitive 2.py +205 -0
- package/tests/test_knowledge_graph 2.py +140 -0
- package/tests/test_migrations 2.py +137 -0
- package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
- /package/src/scripts/{nexo-github-monitor.py → nexo-github-monitor 2.py} +0 -0
|
@@ -135,21 +135,147 @@ def update_calibration_mood(synthesis: dict) -> dict:
|
|
|
135
135
|
# Keep last 30 days
|
|
136
136
|
cal["mood_history"] = cal["mood_history"][-30:]
|
|
137
137
|
|
|
138
|
-
# Apply calibration recommendation
|
|
138
|
+
# Apply calibration recommendation automatically
|
|
139
139
|
rec = emotional_day.get("calibration_recommendation")
|
|
140
140
|
if rec and rec != "null":
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
applied_changes = []
|
|
142
|
+
|
|
143
|
+
# Parse and apply known calibration adjustments
|
|
144
|
+
rec_lower = rec.lower()
|
|
145
|
+
personality = cal.get("personality", {})
|
|
146
|
+
|
|
147
|
+
# Autonomy adjustments
|
|
148
|
+
if "autonomy" in rec_lower or "autonomía" in rec_lower:
|
|
149
|
+
if any(w in rec_lower for w in ["full", "más autonomía", "subir", "increase"]):
|
|
150
|
+
personality["autonomy"] = "full"
|
|
151
|
+
applied_changes.append("autonomy → full")
|
|
152
|
+
elif any(w in rec_lower for w in ["conservative", "reducir", "bajar"]):
|
|
153
|
+
personality["autonomy"] = "conservative"
|
|
154
|
+
applied_changes.append("autonomy → conservative")
|
|
155
|
+
|
|
156
|
+
# Communication adjustments
|
|
157
|
+
if any(w in rec_lower for w in ["concis", "breve", "shorter", "telegráf"]):
|
|
158
|
+
personality["communication"] = "concise"
|
|
159
|
+
applied_changes.append("communication → concise")
|
|
160
|
+
elif any(w in rec_lower for w in ["detail", "explicar más", "más contexto"]):
|
|
161
|
+
personality["communication"] = "detailed"
|
|
162
|
+
applied_changes.append("communication → detailed")
|
|
163
|
+
|
|
164
|
+
# Proactivity adjustments
|
|
165
|
+
if any(w in rec_lower for w in ["más proactiv", "proactive", "anticipar"]):
|
|
166
|
+
personality["proactivity"] = "proactive"
|
|
167
|
+
applied_changes.append("proactivity → proactive")
|
|
168
|
+
|
|
169
|
+
cal["personality"] = personality
|
|
170
|
+
|
|
171
|
+
# Log the recommendation and what was applied
|
|
172
|
+
if "calibration_log" not in cal:
|
|
173
|
+
cal["calibration_log"] = []
|
|
174
|
+
cal["calibration_log"].append({
|
|
144
175
|
"date": synthesis.get("date", ""),
|
|
145
176
|
"recommendation": rec,
|
|
146
|
-
"applied":
|
|
177
|
+
"applied": applied_changes if applied_changes else ["noted, no auto-applicable changes"],
|
|
147
178
|
})
|
|
148
|
-
|
|
149
|
-
cal["calibration_notes"] = cal["calibration_notes"][-10:]
|
|
179
|
+
cal["calibration_log"] = cal["calibration_log"][-20:]
|
|
150
180
|
|
|
151
181
|
calibration_file.write_text(json.dumps(cal, indent=2, ensure_ascii=False))
|
|
152
|
-
|
|
182
|
+
changes_str = ", ".join(applied_changes) if rec and applied_changes else "none"
|
|
183
|
+
return {"success": True, "mood_score": emotional_day.get("mood_score"), "calibration_applied": changes_str}
|
|
184
|
+
except Exception as e:
|
|
185
|
+
return {"success": False, "error": str(e)}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def calibrate_trust_score(synthesis: dict, target_date: str) -> dict:
|
|
189
|
+
"""Set the daily trust score from Deep Sleep analysis.
|
|
190
|
+
|
|
191
|
+
This is the authoritative score for the day — replaces incremental
|
|
192
|
+
adjustments with a holistic evaluation of the entire day.
|
|
193
|
+
"""
|
|
194
|
+
trust_cal = synthesis.get("trust_calibration")
|
|
195
|
+
if not trust_cal or "score" not in trust_cal:
|
|
196
|
+
return {"success": False, "error": "no trust_calibration in synthesis"}
|
|
197
|
+
|
|
198
|
+
score = max(0, min(100, trust_cal["score"]))
|
|
199
|
+
reasoning = trust_cal.get("reasoning", "Deep Sleep calibration")
|
|
200
|
+
trend = trust_cal.get("trend", "stable")
|
|
201
|
+
highlights = trust_cal.get("highlights", [])
|
|
202
|
+
lowlights = trust_cal.get("lowlights", [])
|
|
203
|
+
|
|
204
|
+
context = (
|
|
205
|
+
f"Deep Sleep {target_date} | trend: {trend} | "
|
|
206
|
+
f"highlights: {', '.join(highlights[:3])} | "
|
|
207
|
+
f"lowlights: {', '.join(lowlights[:3])}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
# Get current score for delta calculation
|
|
212
|
+
db = sqlite3.connect(str(COGNITIVE_DB))
|
|
213
|
+
row = db.execute(
|
|
214
|
+
"SELECT score FROM trust_score ORDER BY id DESC LIMIT 1"
|
|
215
|
+
).fetchone()
|
|
216
|
+
old_score = row[0] if row else 50.0
|
|
217
|
+
delta = score - old_score
|
|
218
|
+
|
|
219
|
+
db.execute(
|
|
220
|
+
"INSERT INTO trust_score (score, event, delta, context) VALUES (?, ?, ?, ?)",
|
|
221
|
+
(score, f"deep_sleep_calibration: {reasoning[:200]}", delta, context[:500])
|
|
222
|
+
)
|
|
223
|
+
db.commit()
|
|
224
|
+
db.close()
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
"success": True,
|
|
228
|
+
"old_score": old_score,
|
|
229
|
+
"new_score": score,
|
|
230
|
+
"delta": delta,
|
|
231
|
+
"trend": trend,
|
|
232
|
+
}
|
|
233
|
+
except Exception as e:
|
|
234
|
+
return {"success": False, "error": str(e)}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def create_skill(skill_data: dict) -> dict:
|
|
238
|
+
"""Create a skill in nexo.db from Deep Sleep extraction."""
|
|
239
|
+
if not NEXO_DB.exists():
|
|
240
|
+
return {"success": False, "error": "nexo.db not found"}
|
|
241
|
+
try:
|
|
242
|
+
import hashlib
|
|
243
|
+
skill_id = skill_data.get("id", "")
|
|
244
|
+
if not skill_id:
|
|
245
|
+
skill_id = "SK-DS-" + hashlib.md5(
|
|
246
|
+
skill_data.get("name", "").encode()
|
|
247
|
+
).hexdigest()[:8].upper()
|
|
248
|
+
|
|
249
|
+
name = skill_data.get("name", "")
|
|
250
|
+
description = skill_data.get("description", "")
|
|
251
|
+
tags = json.dumps(skill_data.get("tags", []))
|
|
252
|
+
trigger_patterns = json.dumps(skill_data.get("trigger_patterns", []))
|
|
253
|
+
source_sessions = json.dumps(skill_data.get("source_sessions", []))
|
|
254
|
+
steps = skill_data.get("steps", [])
|
|
255
|
+
gotchas = skill_data.get("gotchas", [])
|
|
256
|
+
|
|
257
|
+
# Build file content for the skill .md file
|
|
258
|
+
steps_md = "\n".join(f"{i+1}. {s}" for i, s in enumerate(steps))
|
|
259
|
+
gotchas_md = "\n".join(f"- {g}" for g in gotchas) if gotchas else "None"
|
|
260
|
+
|
|
261
|
+
conn = sqlite3.connect(str(NEXO_DB))
|
|
262
|
+
# Check if skill already exists
|
|
263
|
+
existing = conn.execute("SELECT id FROM skills WHERE id = ?", (skill_id,)).fetchone()
|
|
264
|
+
if existing:
|
|
265
|
+
conn.close()
|
|
266
|
+
return {"success": False, "error": f"Skill {skill_id} already exists", "id": skill_id}
|
|
267
|
+
|
|
268
|
+
now = datetime.now().isoformat(timespec='seconds')
|
|
269
|
+
conn.execute(
|
|
270
|
+
"""INSERT INTO skills
|
|
271
|
+
(id, name, description, level, trust_score, tags, trigger_patterns,
|
|
272
|
+
source_sessions, linked_learnings, created_at, updated_at)
|
|
273
|
+
VALUES (?, ?, ?, 'draft', 50, ?, ?, ?, '[]', ?, ?)""",
|
|
274
|
+
(skill_id, name, description, tags, trigger_patterns, source_sessions, now, now),
|
|
275
|
+
)
|
|
276
|
+
conn.commit()
|
|
277
|
+
conn.close()
|
|
278
|
+
return {"success": True, "id": skill_id, "name": name}
|
|
153
279
|
except Exception as e:
|
|
154
280
|
return {"success": False, "error": str(e)}
|
|
155
281
|
|
|
@@ -445,6 +571,11 @@ def apply_action(action: dict, run_id: str) -> dict:
|
|
|
445
571
|
log_entry["status"] = "applied" if result.get("success") else "error"
|
|
446
572
|
log_entry["details"] = result
|
|
447
573
|
|
|
574
|
+
elif action_type == "skill_create":
|
|
575
|
+
result = create_skill(content)
|
|
576
|
+
log_entry["status"] = "applied" if result.get("success") else "error"
|
|
577
|
+
log_entry["details"] = result
|
|
578
|
+
|
|
448
579
|
elif action_type == "morning_briefing_item":
|
|
449
580
|
# These are included in the briefing file, not applied separately
|
|
450
581
|
log_entry["status"] = "included_in_briefing"
|
|
@@ -527,6 +658,35 @@ def main():
|
|
|
527
658
|
else:
|
|
528
659
|
print(f" Mood skip: {mood_result.get('error', '?')}")
|
|
529
660
|
|
|
661
|
+
# Calibrate trust score (authoritative daily score from Deep Sleep)
|
|
662
|
+
print("[apply] Calibrating trust score...")
|
|
663
|
+
trust_result = calibrate_trust_score(synthesis, target_date)
|
|
664
|
+
if trust_result.get("success"):
|
|
665
|
+
stats["applied"] += 1
|
|
666
|
+
print(f" Trust: {trust_result['old_score']:.0f} → {trust_result['new_score']:.0f} (Δ{trust_result['delta']:+.0f}, {trust_result['trend']})")
|
|
667
|
+
else:
|
|
668
|
+
print(f" Trust skip: {trust_result.get('error', '?')}")
|
|
669
|
+
|
|
670
|
+
# Create skills from synthesis
|
|
671
|
+
skills_data = synthesis.get("skills", [])
|
|
672
|
+
if skills_data:
|
|
673
|
+
print(f"[apply] Creating {len(skills_data)} skill(s)...")
|
|
674
|
+
for skill_data in skills_data:
|
|
675
|
+
if skill_data.get("confidence", 0) < 0.7:
|
|
676
|
+
continue
|
|
677
|
+
if skill_data.get("merge_with"):
|
|
678
|
+
print(f" Skip {skill_data.get('id', '?')}: merge candidate (needs runtime merge)")
|
|
679
|
+
continue
|
|
680
|
+
result = create_skill(skill_data)
|
|
681
|
+
if result.get("success"):
|
|
682
|
+
stats["applied"] += 1
|
|
683
|
+
print(f" Skill created: {result['id']} — {result.get('name', '')[:50]}")
|
|
684
|
+
elif "already exists" in result.get("error", ""):
|
|
685
|
+
stats["skipped_dedupe"] += 1
|
|
686
|
+
else:
|
|
687
|
+
stats["errors"] += 1
|
|
688
|
+
print(f" Skill error: {result.get('error', 'unknown')}", file=sys.stderr)
|
|
689
|
+
|
|
530
690
|
# Create followups for abandoned projects
|
|
531
691
|
abandoned_results = create_abandoned_followups(synthesis)
|
|
532
692
|
for r in abandoned_results:
|
|
@@ -116,8 +116,15 @@ def extract_session(jsonl_path: Path) -> dict | None:
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def
|
|
120
|
-
"""Collect all sessions modified
|
|
119
|
+
def collect_transcripts_since(since_iso: str, until_iso: str = "") -> list[dict]:
|
|
120
|
+
"""Collect all sessions modified after `since_iso` (exclusive) up to `until_iso` (inclusive).
|
|
121
|
+
|
|
122
|
+
Uses a watermark approach: deep sleep tracks the last processed timestamp
|
|
123
|
+
so nothing is missed regardless of when sessions happen (day, night, etc.).
|
|
124
|
+
"""
|
|
125
|
+
since_dt = datetime.fromisoformat(since_iso)
|
|
126
|
+
until_dt = datetime.fromisoformat(until_iso) if until_iso else datetime.now()
|
|
127
|
+
|
|
121
128
|
sessions = []
|
|
122
129
|
for sdir in find_session_dirs():
|
|
123
130
|
for f in sdir.glob("*.jsonl"):
|
|
@@ -125,7 +132,7 @@ def collect_transcripts(target_date: str) -> list[dict]:
|
|
|
125
132
|
mtime = datetime.fromtimestamp(f.stat().st_mtime)
|
|
126
133
|
except OSError:
|
|
127
134
|
continue
|
|
128
|
-
if mtime
|
|
135
|
+
if since_dt < mtime <= until_dt:
|
|
129
136
|
session = extract_session(f)
|
|
130
137
|
if session:
|
|
131
138
|
session["modified"] = mtime.isoformat()
|
|
@@ -339,25 +346,40 @@ def format_transcripts(sessions: list[dict]) -> str:
|
|
|
339
346
|
|
|
340
347
|
|
|
341
348
|
def main():
|
|
342
|
-
|
|
349
|
+
# Watermark-based collection: since_iso and until_iso passed by the wrapper script
|
|
350
|
+
# argv[1] = run_id (date label for output files)
|
|
351
|
+
# argv[2] = since_iso (exclusive lower bound, e.g. "2026-04-01T04:30:00")
|
|
352
|
+
# argv[3] = until_iso (inclusive upper bound, e.g. "2026-04-02T04:30:00") — optional, defaults to now
|
|
353
|
+
run_id = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
|
|
354
|
+
since_iso = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
355
|
+
until_iso = sys.argv[3] if len(sys.argv) > 3 else ""
|
|
356
|
+
|
|
343
357
|
DEEP_SLEEP_DIR.mkdir(parents=True, exist_ok=True)
|
|
344
358
|
|
|
345
|
-
print(f"[collect] Phase 1: Collecting context
|
|
359
|
+
print(f"[collect] Phase 1: Collecting context (run_id={run_id})")
|
|
346
360
|
|
|
347
|
-
# 1. Transcripts
|
|
348
|
-
|
|
349
|
-
|
|
361
|
+
# 1. Transcripts — watermark-based
|
|
362
|
+
if since_iso:
|
|
363
|
+
print(f"[collect] Gathering transcripts since {since_iso}" + (f" until {until_iso}" if until_iso else ""))
|
|
364
|
+
sessions = collect_transcripts_since(since_iso, until_iso)
|
|
365
|
+
else:
|
|
366
|
+
# Fallback: collect everything from last 48h (safe catch-all)
|
|
367
|
+
fallback_since = (datetime.now() - timedelta(hours=48)).isoformat()
|
|
368
|
+
print(f"[collect] No watermark — collecting last 48h since {fallback_since}")
|
|
369
|
+
sessions = collect_transcripts_since(fallback_since)
|
|
350
370
|
print(f" Found {len(sessions)} sessions")
|
|
351
371
|
|
|
352
372
|
if not sessions:
|
|
353
|
-
print(f"[collect] No sessions found
|
|
354
|
-
output_file = DEEP_SLEEP_DIR / f"{
|
|
373
|
+
print(f"[collect] No new sessions found. Writing minimal context file.")
|
|
374
|
+
output_file = DEEP_SLEEP_DIR / f"{run_id}-context.txt"
|
|
355
375
|
output_file.write_text(
|
|
356
|
-
f"Deep Sleep Context for {
|
|
376
|
+
f"Deep Sleep Context for {run_id}\n\nNo sessions found.\n"
|
|
357
377
|
)
|
|
358
378
|
print(f"[collect] Output: {output_file}")
|
|
359
379
|
return
|
|
360
380
|
|
|
381
|
+
target_date = run_id # Keep variable name for downstream compat
|
|
382
|
+
|
|
361
383
|
# 2. Core DB data
|
|
362
384
|
print("[collect] Querying databases...")
|
|
363
385
|
followups = collect_followups()
|
|
@@ -58,6 +58,22 @@ Detect work that was started but not finished in this session:
|
|
|
58
58
|
- Investigations started but conclusions never reached
|
|
59
59
|
Only flag if the work was NOT captured in a followup or reminder.
|
|
60
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
|
+
|
|
74
|
+
Only flag if the procedure was SUCCESSFUL (the task was completed without major failures).
|
|
75
|
+
Do NOT flag trivial tasks (single-step actions, simple file edits, quick lookups).
|
|
76
|
+
|
|
61
77
|
### 8. Productivity Patterns
|
|
62
78
|
Analyze how the session went in terms of efficiency:
|
|
63
79
|
- How many times did the agent need correction before getting it right?
|
|
@@ -195,6 +211,28 @@ Return ONLY valid JSON. No markdown code fences. No explanation text before or a
|
|
|
195
211
|
}
|
|
196
212
|
],
|
|
197
213
|
|
|
214
|
+
"skill_candidates": [
|
|
215
|
+
{
|
|
216
|
+
"name": "Short name for the procedure (e.g., Deploy Chrome Extension)",
|
|
217
|
+
"description": "What this procedure accomplishes (1-2 sentences)",
|
|
218
|
+
"steps": [
|
|
219
|
+
"Step 1: What was done first",
|
|
220
|
+
"Step 2: What was done next",
|
|
221
|
+
"Step 3: etc."
|
|
222
|
+
],
|
|
223
|
+
"tags": ["domain1", "domain2"],
|
|
224
|
+
"trigger_phrases": ["phrase that would trigger this", "another trigger"],
|
|
225
|
+
"gotchas": ["Warning or caveat discovered during execution"],
|
|
226
|
+
"evidence": {
|
|
227
|
+
"type": "transcript",
|
|
228
|
+
"session_id": "filename.jsonl",
|
|
229
|
+
"message_index": 10,
|
|
230
|
+
"quote": "Start of the multi-step task"
|
|
231
|
+
},
|
|
232
|
+
"confidence": 0.85
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
|
|
198
236
|
"productivity_score": {
|
|
199
237
|
"corrections_needed": 0,
|
|
200
238
|
"proactivity": "reactive|mixed|proactive",
|
|
@@ -113,6 +113,14 @@ def analyze_session(session_id: str, date_dir: Path, shared_context_file: Path |
|
|
|
113
113
|
try:
|
|
114
114
|
env = os.environ.copy()
|
|
115
115
|
env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
|
|
116
|
+
env.pop("CLAUDECODE", None)
|
|
117
|
+
env.pop("CLAUDE_CODE", None)
|
|
118
|
+
|
|
119
|
+
JSON_SYSTEM_PROMPT = (
|
|
120
|
+
"You are a JSON-only analyst. Your ENTIRE response must be a single valid JSON object. "
|
|
121
|
+
"No text before it. No text after it. No markdown fences. No explanations. "
|
|
122
|
+
"If you want to summarize, put it inside the JSON fields. Start with { and end with }."
|
|
123
|
+
)
|
|
116
124
|
|
|
117
125
|
result = subprocess.run(
|
|
118
126
|
[
|
|
@@ -120,8 +128,9 @@ def analyze_session(session_id: str, date_dir: Path, shared_context_file: Path |
|
|
|
120
128
|
"-p", prompt,
|
|
121
129
|
"--model", "opus",
|
|
122
130
|
"--output-format", "text",
|
|
131
|
+
"--append-system-prompt", JSON_SYSTEM_PROMPT,
|
|
123
132
|
"--allowedTools",
|
|
124
|
-
"Read,Grep,Bash
|
|
133
|
+
"Read,Grep,Bash"
|
|
125
134
|
],
|
|
126
135
|
capture_output=True,
|
|
127
136
|
text=True,
|
|
@@ -139,6 +148,28 @@ def analyze_session(session_id: str, date_dir: Path, shared_context_file: Path |
|
|
|
139
148
|
if not line.strip().startswith("Post-mortem") and line.strip()
|
|
140
149
|
)
|
|
141
150
|
parsed = extract_json_from_response(output)
|
|
151
|
+
|
|
152
|
+
# Fallback: if Claude returned text instead of JSON, ask a short conversion call
|
|
153
|
+
if not parsed and len(output.strip()) > 50:
|
|
154
|
+
print(f" Got text instead of JSON ({len(output)} chars). Converting...")
|
|
155
|
+
convert_prompt = (
|
|
156
|
+
f"Convert the following analysis into the exact JSON schema required. "
|
|
157
|
+
f"Return ONLY the JSON object, nothing else.\n\n"
|
|
158
|
+
f"Analysis:\n{output[:8000]}\n\n"
|
|
159
|
+
f"Required schema: session_id, findings[], emotional_timeline[], "
|
|
160
|
+
f"abandoned_projects[], skill_candidates[], productivity_score, protocol_summary"
|
|
161
|
+
)
|
|
162
|
+
convert_result = subprocess.run(
|
|
163
|
+
[claude_bin, "-p", convert_prompt, "--model", "sonnet",
|
|
164
|
+
"--output-format", "text",
|
|
165
|
+
"--append-system-prompt", JSON_SYSTEM_PROMPT],
|
|
166
|
+
capture_output=True, text=True, timeout=120, env=env
|
|
167
|
+
)
|
|
168
|
+
if convert_result.returncode == 0:
|
|
169
|
+
parsed = extract_json_from_response(convert_result.stdout)
|
|
170
|
+
if parsed:
|
|
171
|
+
print(f" Conversion succeeded")
|
|
172
|
+
|
|
142
173
|
if not parsed:
|
|
143
174
|
# Save raw output for debugging
|
|
144
175
|
debug_file = DEEP_SLEEP_DIR / f"debug-extract-{session_id[:20]}.txt"
|
|
@@ -207,32 +238,70 @@ def main():
|
|
|
207
238
|
print(f"[extract] Phase 2: Analyzing {len(session_files)} sessions for {target_date}")
|
|
208
239
|
print(f"[extract] Claude CLI: {claude_bin}")
|
|
209
240
|
|
|
241
|
+
# Checkpoint directory: one JSON per session, survives crashes
|
|
242
|
+
checkpoint_dir = date_dir / "checkpoints"
|
|
243
|
+
checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
244
|
+
|
|
210
245
|
all_extractions = []
|
|
211
246
|
total_findings = 0
|
|
247
|
+
skipped = 0
|
|
248
|
+
MAX_RETRIES = 3
|
|
212
249
|
|
|
213
250
|
for i, session_id in enumerate(session_files):
|
|
251
|
+
sid_safe = session_id.replace(".jsonl", "")[:30]
|
|
252
|
+
checkpoint_file = checkpoint_dir / f"{sid_safe}.json"
|
|
253
|
+
|
|
254
|
+
# Resume: skip already-processed sessions
|
|
255
|
+
if checkpoint_file.exists():
|
|
256
|
+
try:
|
|
257
|
+
with open(checkpoint_file) as f:
|
|
258
|
+
cached = json.load(f)
|
|
259
|
+
findings_count = len(cached.get("findings", []))
|
|
260
|
+
total_findings += findings_count
|
|
261
|
+
all_extractions.append(cached)
|
|
262
|
+
skipped += 1
|
|
263
|
+
print(f"[extract] Session {i + 1}/{len(session_files)}: {session_id} (cached, {findings_count} findings)")
|
|
264
|
+
continue
|
|
265
|
+
except (json.JSONDecodeError, KeyError):
|
|
266
|
+
pass # Corrupted checkpoint, re-process
|
|
267
|
+
|
|
214
268
|
print(f"[extract] Session {i + 1}/{len(session_files)}: {session_id}")
|
|
215
269
|
|
|
216
|
-
|
|
270
|
+
# Retry loop
|
|
271
|
+
result = None
|
|
272
|
+
for attempt in range(1, MAX_RETRIES + 1):
|
|
273
|
+
result = analyze_session(session_id, date_dir, shared_context_file, claude_bin)
|
|
274
|
+
if result:
|
|
275
|
+
break
|
|
276
|
+
if attempt < MAX_RETRIES:
|
|
277
|
+
print(f" -> Attempt {attempt}/{MAX_RETRIES} failed, retrying...")
|
|
217
278
|
|
|
218
279
|
if result:
|
|
219
280
|
findings_count = len(result.get("findings", []))
|
|
220
281
|
total_findings += findings_count
|
|
221
282
|
all_extractions.append(result)
|
|
222
|
-
|
|
283
|
+
# Save checkpoint
|
|
284
|
+
with open(checkpoint_file, "w") as f:
|
|
285
|
+
json.dump(result, f, indent=2, ensure_ascii=False)
|
|
286
|
+
print(f" -> {findings_count} findings extracted (checkpointed)")
|
|
223
287
|
else:
|
|
224
|
-
print(f" ->
|
|
225
|
-
|
|
288
|
+
print(f" -> Failed after {MAX_RETRIES} attempts, marking as failed")
|
|
289
|
+
failed_entry = {
|
|
226
290
|
"session_id": session_id,
|
|
227
291
|
"findings": [],
|
|
228
|
-
"error": "Extraction failed"
|
|
229
|
-
}
|
|
292
|
+
"error": f"Extraction failed after {MAX_RETRIES} attempts"
|
|
293
|
+
}
|
|
294
|
+
all_extractions.append(failed_entry)
|
|
295
|
+
# Save failed checkpoint too (so we don't retry forever)
|
|
296
|
+
with open(checkpoint_file, "w") as f:
|
|
297
|
+
json.dump(failed_entry, f, indent=2, ensure_ascii=False)
|
|
230
298
|
|
|
231
299
|
# Merge into output
|
|
232
300
|
output = {
|
|
233
301
|
"date": target_date,
|
|
234
302
|
"sessions_analyzed": len(session_files),
|
|
235
303
|
"sessions_succeeded": len([e for e in all_extractions if "error" not in e]),
|
|
304
|
+
"sessions_cached": skipped,
|
|
236
305
|
"total_findings": total_findings,
|
|
237
306
|
"extractions": all_extractions
|
|
238
307
|
}
|
|
@@ -241,7 +310,10 @@ def main():
|
|
|
241
310
|
with open(output_file, "w") as f:
|
|
242
311
|
json.dump(output, f, indent=2, ensure_ascii=False)
|
|
243
312
|
|
|
244
|
-
|
|
313
|
+
if skipped:
|
|
314
|
+
print(f"\n[extract] Done. {total_findings} findings from {len(session_files)} sessions ({skipped} cached, {len(session_files) - skipped} new).")
|
|
315
|
+
else:
|
|
316
|
+
print(f"\n[extract] Done. {total_findings} findings from {len(session_files)} sessions.")
|
|
245
317
|
print(f"[extract] Output: {output_file}")
|
|
246
318
|
|
|
247
319
|
|
|
@@ -52,7 +52,41 @@ Consolidate `abandoned_projects` from all sessions:
|
|
|
52
52
|
- Cross-reference across sessions — was the abandoned work picked up later in another session?
|
|
53
53
|
- Only flag projects that are truly abandoned (no followup AND not resumed)
|
|
54
54
|
|
|
55
|
-
### 7.
|
|
55
|
+
### 7. Trust Calibration (CRITICAL)
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
Scoring guide:
|
|
59
|
+
- **90-100**: Flawless day. Zero corrections needed. Proactive. Anticipated user needs. Deployed code without issues.
|
|
60
|
+
- **70-89**: Good day. Minor corrections, quickly resolved. Mostly proactive. User satisfied.
|
|
61
|
+
- **50-69**: Average day. Some corrections, some reactive behavior. Mixed results.
|
|
62
|
+
- **30-49**: Below average. Multiple corrections. Repeated mistakes. User had to push.
|
|
63
|
+
- **0-29**: Bad day. Many corrections, repeated errors, user frustrated. Broke things.
|
|
64
|
+
|
|
65
|
+
Consider ALL of these:
|
|
66
|
+
- Number and severity of corrections (most important signal)
|
|
67
|
+
- Tasks completed successfully vs failed
|
|
68
|
+
- Did the agent act autonomously or wait to be told?
|
|
69
|
+
- Did the agent catch its own mistakes or did the user?
|
|
70
|
+
- Did the agent repeat known errors (worst offense)?
|
|
71
|
+
- User emotional signals throughout the day
|
|
72
|
+
- Code deployed: did it work first try?
|
|
73
|
+
|
|
74
|
+
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.
|
|
75
|
+
|
|
76
|
+
### 9. Skill Extraction
|
|
77
|
+
Consolidate `skill_candidates` from all session extractions into publishable skills:
|
|
78
|
+
- Merge similar procedures from different sessions into a single skill
|
|
79
|
+
- Generalize: replace session-specific IDs, paths, or names with placeholders or descriptions
|
|
80
|
+
- Only include skills with confidence >= 0.7
|
|
81
|
+
- Check if a similar skill already exists (use `nexo_skill_match` if available) — if so, note it for merging instead of creating new
|
|
82
|
+
|
|
83
|
+
For each skill, generate:
|
|
84
|
+
- A unique ID starting with `SK-` (e.g., `SK-DEPLOY-CHROME-EXT`)
|
|
85
|
+
- Name, description, tags, trigger_patterns
|
|
86
|
+
- The full step-by-step procedure as the skill content
|
|
87
|
+
- Source session IDs for traceability
|
|
88
|
+
|
|
89
|
+
### 8. Consolidated Actions
|
|
56
90
|
Merge and deduplicate all findings into a final action list. Each action should have:
|
|
57
91
|
- `action_type`: `learning_add`, `followup_create`, `morning_briefing_item`
|
|
58
92
|
- `action_class`: `auto_apply` (confidence >= 0.8, reversible) or `draft_for_morning` (confidence < 0.8 or high impact)
|
|
@@ -101,9 +135,24 @@ Return ONLY valid JSON. No markdown code fences. No explanation text.
|
|
|
101
135
|
}
|
|
102
136
|
],
|
|
103
137
|
|
|
138
|
+
"skills": [
|
|
139
|
+
{
|
|
140
|
+
"id": "SK-SHORT-ID",
|
|
141
|
+
"name": "Human readable name",
|
|
142
|
+
"description": "What this procedure does (1-2 sentences)",
|
|
143
|
+
"steps": ["Step 1", "Step 2", "Step 3"],
|
|
144
|
+
"tags": ["tag1", "tag2"],
|
|
145
|
+
"trigger_patterns": ["trigger phrase 1", "trigger phrase 2"],
|
|
146
|
+
"gotchas": ["Warning or caveat"],
|
|
147
|
+
"source_sessions": ["session1.jsonl"],
|
|
148
|
+
"confidence": 0.85,
|
|
149
|
+
"merge_with": null
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
|
|
104
153
|
"actions": [
|
|
105
154
|
{
|
|
106
|
-
"action_type": "learning_add|followup_create|morning_briefing_item",
|
|
155
|
+
"action_type": "learning_add|followup_create|skill_create|morning_briefing_item",
|
|
107
156
|
"action_class": "auto_apply|draft_for_morning",
|
|
108
157
|
"confidence": 0.9,
|
|
109
158
|
"impact": "low|medium|high",
|
|
@@ -147,6 +196,14 @@ Return ONLY valid JSON. No markdown code fences. No explanation text.
|
|
|
147
196
|
}
|
|
148
197
|
],
|
|
149
198
|
|
|
199
|
+
"trust_calibration": {
|
|
200
|
+
"score": 72,
|
|
201
|
+
"reasoning": "Why this score -- based on corrections, completions, autonomy, proactivity, and user satisfaction signals across ALL sessions",
|
|
202
|
+
"highlights": ["What went well"],
|
|
203
|
+
"lowlights": ["What went poorly"],
|
|
204
|
+
"trend": "improving|stable|declining"
|
|
205
|
+
},
|
|
206
|
+
|
|
150
207
|
"summary": "2-3 sentence overall assessment of the day"
|
|
151
208
|
}
|
|
152
209
|
```
|
|
@@ -115,6 +115,8 @@ def main():
|
|
|
115
115
|
try:
|
|
116
116
|
env = os.environ.copy()
|
|
117
117
|
env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
|
|
118
|
+
env.pop("CLAUDECODE", None)
|
|
119
|
+
env.pop("CLAUDE_CODE", None)
|
|
118
120
|
|
|
119
121
|
result = subprocess.run(
|
|
120
122
|
[
|
|
@@ -123,7 +125,7 @@ def main():
|
|
|
123
125
|
"--model", "opus",
|
|
124
126
|
"--output-format", "text",
|
|
125
127
|
"--allowedTools",
|
|
126
|
-
"Read,
|
|
128
|
+
"Read,Grep,Bash"
|
|
127
129
|
],
|
|
128
130
|
capture_output=True,
|
|
129
131
|
text=True,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# NEXO DB hourly backup — crontab: 0 * * * * $NEXO_HOME/scripts/nexo-backup.sh
|
|
3
|
+
NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
|
|
4
|
+
NEXO_DIR="$NEXO_HOME"
|
|
5
|
+
BACKUP_DIR="$NEXO_HOME/backups"
|
|
6
|
+
WEEKLY_DIR="$BACKUP_DIR/weekly"
|
|
7
|
+
DB="$NEXO_HOME/data/nexo.db"
|
|
8
|
+
RETENTION_HOURS=48
|
|
9
|
+
|
|
10
|
+
mkdir -p "$BACKUP_DIR" "$WEEKLY_DIR"
|
|
11
|
+
|
|
12
|
+
# Hourly backup
|
|
13
|
+
TIMESTAMP=$(date +%Y-%m-%d-%H%M)
|
|
14
|
+
sqlite3 "$DB" ".backup '$BACKUP_DIR/nexo-$TIMESTAMP.db'"
|
|
15
|
+
|
|
16
|
+
# Weekly backup — save one per week (Sundays)
|
|
17
|
+
WEEK=$(date +%Y-W%V)
|
|
18
|
+
WEEKLY_FILE="$WEEKLY_DIR/weekly-$WEEK.db"
|
|
19
|
+
if [ ! -f "$WEEKLY_FILE" ] && [ "$(date +%u)" = "7" ]; then
|
|
20
|
+
cp "$BACKUP_DIR/nexo-$TIMESTAMP.db" "$WEEKLY_FILE"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Cleanup: hourly >48h, weekly >90 days
|
|
24
|
+
find "$BACKUP_DIR" -maxdepth 1 -name "nexo-*.db" -mmin +$((RETENTION_HOURS * 60)) -delete
|
|
25
|
+
find "$WEEKLY_DIR" -name "weekly-*.db" -mtime +90 -delete
|