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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/nexo-brain.js +52 -1
- package/package.json +1 -1
- package/src/crons/sync.py +18 -4
- 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/_entities.py +1 -1
- 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/agents.py +10 -3
- 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/schedule.py +2 -1
- 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/requirements.txt +1 -1
- 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/runtime_power.py +18 -1
- 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-cron-wrapper.sh +7 -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.auto-close-sessions.plist +1 -1
- package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
- package/templates/launchagents/com.nexo.catchup.plist +1 -1
- 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.dashboard.plist +1 -1
- package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
- package/templates/launchagents/com.nexo.deep-sleep.plist +1 -1
- package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
- package/templates/launchagents/com.nexo.evolution.plist +1 -1
- package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
- package/templates/launchagents/com.nexo.followup-hygiene.plist +1 -1
- package/templates/launchagents/com.nexo.immune 2.plist +41 -0
- package/templates/launchagents/com.nexo.immune.plist +1 -1
- package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
- package/templates/launchagents/com.nexo.postmortem.plist +1 -1
- package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
- package/templates/launchagents/com.nexo.self-audit.plist +1 -1
- package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
- package/templates/launchagents/com.nexo.synthesis.plist +1 -1
- package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
- package/templates/launchagents/com.nexo.watchdog.plist +1 -1
- 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/script-template.py +5 -4
- package/templates/skill-script-template 2.py +48 -0
- package/templates/skill-script-template.py +2 -1
- package/templates/skill-template 2.md +33 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Portable export/import helpers for operator user data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import sqlite3
|
|
9
|
+
import tarfile
|
|
10
|
+
import tempfile
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from runtime_home import export_resolved_nexo_home
|
|
15
|
+
|
|
16
|
+
NEXO_HOME = export_resolved_nexo_home()
|
|
17
|
+
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parent)))
|
|
18
|
+
EXPORTS_DIR = NEXO_HOME / "exports"
|
|
19
|
+
STAGING_DIR = EXPORTS_DIR / ".staging"
|
|
20
|
+
USER_DIRS = ("brain", "coordination", "nexo-email", "assets")
|
|
21
|
+
USER_CONFIG_FILES = ("schedule.json",)
|
|
22
|
+
IGNORED_FILENAMES = {".DS_Store"}
|
|
23
|
+
IGNORED_DIRS = {"__pycache__"}
|
|
24
|
+
IGNORED_SUFFIXES = {".pyc", ".pyo"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _now_stamp() -> str:
|
|
28
|
+
return datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _runtime_version() -> str:
|
|
32
|
+
for candidate, key in (
|
|
33
|
+
(NEXO_HOME / "version.json", "version"),
|
|
34
|
+
(NEXO_CODE.parent / "version.json", "version"),
|
|
35
|
+
(NEXO_CODE.parent / "package.json", "version"),
|
|
36
|
+
(NEXO_HOME / "package.json", "version"),
|
|
37
|
+
):
|
|
38
|
+
try:
|
|
39
|
+
if candidate.is_file():
|
|
40
|
+
return str(json.loads(candidate.read_text()).get(key, "?"))
|
|
41
|
+
except Exception:
|
|
42
|
+
continue
|
|
43
|
+
return "?"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _sqlite_backup(src: Path, dest: Path) -> None:
|
|
47
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
src_conn = sqlite3.connect(str(src))
|
|
49
|
+
try:
|
|
50
|
+
dst_conn = sqlite3.connect(str(dest))
|
|
51
|
+
try:
|
|
52
|
+
src_conn.backup(dst_conn)
|
|
53
|
+
finally:
|
|
54
|
+
dst_conn.close()
|
|
55
|
+
finally:
|
|
56
|
+
src_conn.close()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _should_skip_file(path: Path, exclude_names: set[str] | None = None) -> bool:
|
|
60
|
+
exclude = exclude_names or set()
|
|
61
|
+
if path.name in exclude:
|
|
62
|
+
return True
|
|
63
|
+
if path.name in IGNORED_FILENAMES:
|
|
64
|
+
return True
|
|
65
|
+
if path.suffix in IGNORED_SUFFIXES:
|
|
66
|
+
return True
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _copy_tree_filtered(src: Path, dest: Path, *, exclude_names: set[str] | None = None) -> int:
|
|
71
|
+
if not src.is_dir():
|
|
72
|
+
return 0
|
|
73
|
+
copied = 0
|
|
74
|
+
for root, dirs, files in os.walk(src):
|
|
75
|
+
root_path = Path(root)
|
|
76
|
+
rel = root_path.relative_to(src)
|
|
77
|
+
dirs[:] = [item for item in dirs if item not in IGNORED_DIRS]
|
|
78
|
+
target_root = dest / rel
|
|
79
|
+
target_root.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
for name in files:
|
|
81
|
+
file_path = root_path / name
|
|
82
|
+
if _should_skip_file(file_path, exclude_names=exclude_names):
|
|
83
|
+
continue
|
|
84
|
+
shutil.copy2(str(file_path), str(target_root / name))
|
|
85
|
+
copied += 1
|
|
86
|
+
return copied
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _copy_file_if_present(src: Path, dest: Path) -> bool:
|
|
90
|
+
if not src.is_file():
|
|
91
|
+
return False
|
|
92
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
shutil.copy2(str(src), str(dest))
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _safe_extract(archive_path: Path, dest_dir: Path) -> None:
|
|
98
|
+
resolved_dest = dest_dir.resolve()
|
|
99
|
+
with tarfile.open(archive_path, "r:*") as tar:
|
|
100
|
+
members = tar.getmembers()
|
|
101
|
+
for member in members:
|
|
102
|
+
target = (dest_dir / member.name).resolve()
|
|
103
|
+
if target != resolved_dest and resolved_dest not in target.parents:
|
|
104
|
+
raise ValueError(f"archive path escapes destination: {member.name}")
|
|
105
|
+
if member.issym() or member.islnk():
|
|
106
|
+
raise ValueError(f"archive contains unsupported link member: {member.name}")
|
|
107
|
+
|
|
108
|
+
for member in members:
|
|
109
|
+
target = (dest_dir / member.name).resolve()
|
|
110
|
+
if member.isdir():
|
|
111
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
target.chmod(member.mode & 0o777)
|
|
113
|
+
continue
|
|
114
|
+
if not member.isfile():
|
|
115
|
+
raise ValueError(f"archive contains unsupported member type: {member.name}")
|
|
116
|
+
|
|
117
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
extracted = tar.extractfile(member)
|
|
119
|
+
if extracted is None:
|
|
120
|
+
raise ValueError(f"archive member could not be read: {member.name}")
|
|
121
|
+
with extracted, target.open("wb") as handle:
|
|
122
|
+
shutil.copyfileobj(extracted, handle)
|
|
123
|
+
target.chmod(member.mode & 0o777)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _load_personal_scripts() -> tuple[list[dict], list[dict]]:
|
|
127
|
+
from script_registry import classify_scripts_dir, discover_personal_schedules
|
|
128
|
+
|
|
129
|
+
classification = classify_scripts_dir()
|
|
130
|
+
scripts = [entry for entry in classification.get("entries", []) if entry.get("classification") == "personal"]
|
|
131
|
+
script_paths = {entry["path"] for entry in scripts}
|
|
132
|
+
schedules = [
|
|
133
|
+
schedule for schedule in discover_personal_schedules()
|
|
134
|
+
if schedule.get("script_path") in script_paths
|
|
135
|
+
]
|
|
136
|
+
return scripts, schedules
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def export_user_bundle(output_path: str = "") -> dict:
|
|
140
|
+
output = Path(output_path).expanduser() if output_path.strip() else (EXPORTS_DIR / f"nexo-user-data-{_now_stamp()}.tar.gz")
|
|
141
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
STAGING_DIR.mkdir(parents=True, exist_ok=True)
|
|
143
|
+
stage_dir = Path(tempfile.mkdtemp(prefix="nexo-export-", dir=str(STAGING_DIR)))
|
|
144
|
+
bundle_root = stage_dir / "bundle"
|
|
145
|
+
bundle_root.mkdir(parents=True, exist_ok=True)
|
|
146
|
+
|
|
147
|
+
sections: dict[str, dict] = {}
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
data_db = NEXO_HOME / "data" / "nexo.db"
|
|
151
|
+
if data_db.is_file():
|
|
152
|
+
_sqlite_backup(data_db, bundle_root / "data" / "nexo.db")
|
|
153
|
+
sections["data_db"] = {"path": "data/nexo.db"}
|
|
154
|
+
|
|
155
|
+
brain_dir = NEXO_HOME / "brain"
|
|
156
|
+
if brain_dir.is_dir():
|
|
157
|
+
copied = _copy_tree_filtered(brain_dir, bundle_root / "brain", exclude_names={"nexo.db"})
|
|
158
|
+
brain_db = brain_dir / "nexo.db"
|
|
159
|
+
if brain_db.is_file():
|
|
160
|
+
_sqlite_backup(brain_db, bundle_root / "brain" / "nexo.db")
|
|
161
|
+
copied += 1
|
|
162
|
+
sections["brain"] = {"path": "brain", "files": copied}
|
|
163
|
+
|
|
164
|
+
for dirname in USER_DIRS[1:]:
|
|
165
|
+
src_dir = NEXO_HOME / dirname
|
|
166
|
+
if not src_dir.is_dir():
|
|
167
|
+
continue
|
|
168
|
+
copied = _copy_tree_filtered(src_dir, bundle_root / dirname)
|
|
169
|
+
sections[dirname] = {"path": dirname, "files": copied}
|
|
170
|
+
|
|
171
|
+
copied_configs: list[str] = []
|
|
172
|
+
for filename in USER_CONFIG_FILES:
|
|
173
|
+
if _copy_file_if_present(NEXO_HOME / "config" / filename, bundle_root / "config" / filename):
|
|
174
|
+
copied_configs.append(filename)
|
|
175
|
+
if copied_configs:
|
|
176
|
+
sections["config"] = {"path": "config", "files": copied_configs}
|
|
177
|
+
|
|
178
|
+
scripts, schedules = _load_personal_scripts()
|
|
179
|
+
exported_scripts: list[dict] = []
|
|
180
|
+
scripts_dir = bundle_root / "personal-scripts"
|
|
181
|
+
scripts_dir.mkdir(parents=True, exist_ok=True)
|
|
182
|
+
for entry in scripts:
|
|
183
|
+
src_path = Path(entry["path"])
|
|
184
|
+
if not src_path.is_file():
|
|
185
|
+
continue
|
|
186
|
+
shutil.copy2(str(src_path), str(scripts_dir / src_path.name))
|
|
187
|
+
exported_scripts.append(
|
|
188
|
+
{
|
|
189
|
+
"name": entry.get("name", src_path.stem),
|
|
190
|
+
"path": f"personal-scripts/{src_path.name}",
|
|
191
|
+
"runtime": entry.get("runtime", "unknown"),
|
|
192
|
+
"description": entry.get("description", ""),
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
(scripts_dir / "manifest.json").write_text(
|
|
196
|
+
json.dumps(
|
|
197
|
+
{
|
|
198
|
+
"scripts": exported_scripts,
|
|
199
|
+
"schedules": schedules,
|
|
200
|
+
},
|
|
201
|
+
indent=2,
|
|
202
|
+
ensure_ascii=False,
|
|
203
|
+
) + "\n"
|
|
204
|
+
)
|
|
205
|
+
sections["personal_scripts"] = {
|
|
206
|
+
"path": "personal-scripts",
|
|
207
|
+
"files": len(exported_scripts),
|
|
208
|
+
"schedules": len(schedules),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
manifest = {
|
|
212
|
+
"kind": "nexo-user-data-bundle",
|
|
213
|
+
"version": _runtime_version(),
|
|
214
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
215
|
+
"nexo_home": str(NEXO_HOME),
|
|
216
|
+
"sections": sections,
|
|
217
|
+
}
|
|
218
|
+
(bundle_root / "manifest.json").write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n")
|
|
219
|
+
|
|
220
|
+
with tarfile.open(output, "w:gz") as tar:
|
|
221
|
+
tar.add(bundle_root, arcname="bundle")
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"ok": True,
|
|
225
|
+
"path": str(output),
|
|
226
|
+
"kind": manifest["kind"],
|
|
227
|
+
"version": manifest["version"],
|
|
228
|
+
"sections": sections,
|
|
229
|
+
}
|
|
230
|
+
finally:
|
|
231
|
+
shutil.rmtree(stage_dir, ignore_errors=True)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def import_user_bundle(bundle_path: str) -> dict:
|
|
235
|
+
archive_path = Path(bundle_path).expanduser()
|
|
236
|
+
if not archive_path.is_file():
|
|
237
|
+
return {"ok": False, "error": f"bundle not found: {archive_path}"}
|
|
238
|
+
|
|
239
|
+
backups_dir = NEXO_HOME / "backups"
|
|
240
|
+
backups_dir.mkdir(parents=True, exist_ok=True)
|
|
241
|
+
safety_backup = backups_dir / f"pre-import-user-data-{_now_stamp()}.tar.gz"
|
|
242
|
+
safety_result = export_user_bundle(str(safety_backup))
|
|
243
|
+
if not safety_result.get("ok"):
|
|
244
|
+
return {"ok": False, "error": "failed to create safety backup", "safety_backup": str(safety_backup)}
|
|
245
|
+
|
|
246
|
+
STAGING_DIR.mkdir(parents=True, exist_ok=True)
|
|
247
|
+
stage_dir = Path(tempfile.mkdtemp(prefix="nexo-import-", dir=str(STAGING_DIR)))
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
_safe_extract(archive_path, stage_dir)
|
|
251
|
+
bundle_root = stage_dir / "bundle"
|
|
252
|
+
manifest_path = bundle_root / "manifest.json"
|
|
253
|
+
if not manifest_path.is_file():
|
|
254
|
+
return {"ok": False, "error": "bundle manifest missing", "safety_backup": str(safety_backup)}
|
|
255
|
+
|
|
256
|
+
manifest = json.loads(manifest_path.read_text())
|
|
257
|
+
if manifest.get("kind") != "nexo-user-data-bundle":
|
|
258
|
+
return {
|
|
259
|
+
"ok": False,
|
|
260
|
+
"error": f"unsupported bundle kind: {manifest.get('kind', 'unknown')}",
|
|
261
|
+
"safety_backup": str(safety_backup),
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
restored: dict[str, dict] = {}
|
|
265
|
+
|
|
266
|
+
data_db = bundle_root / "data" / "nexo.db"
|
|
267
|
+
if data_db.is_file():
|
|
268
|
+
_sqlite_backup(data_db, NEXO_HOME / "data" / "nexo.db")
|
|
269
|
+
restored["data_db"] = {"path": "data/nexo.db"}
|
|
270
|
+
|
|
271
|
+
brain_dir = bundle_root / "brain"
|
|
272
|
+
if brain_dir.is_dir():
|
|
273
|
+
copied = _copy_tree_filtered(brain_dir, NEXO_HOME / "brain", exclude_names={"nexo.db"})
|
|
274
|
+
brain_db = brain_dir / "nexo.db"
|
|
275
|
+
if brain_db.is_file():
|
|
276
|
+
_sqlite_backup(brain_db, NEXO_HOME / "brain" / "nexo.db")
|
|
277
|
+
copied += 1
|
|
278
|
+
restored["brain"] = {"path": "brain", "files": copied}
|
|
279
|
+
|
|
280
|
+
for dirname in USER_DIRS[1:]:
|
|
281
|
+
src_dir = bundle_root / dirname
|
|
282
|
+
if not src_dir.is_dir():
|
|
283
|
+
continue
|
|
284
|
+
copied = _copy_tree_filtered(src_dir, NEXO_HOME / dirname)
|
|
285
|
+
restored[dirname] = {"path": dirname, "files": copied}
|
|
286
|
+
|
|
287
|
+
restored_configs: list[str] = []
|
|
288
|
+
for filename in USER_CONFIG_FILES:
|
|
289
|
+
if _copy_file_if_present(bundle_root / "config" / filename, NEXO_HOME / "config" / filename):
|
|
290
|
+
restored_configs.append(filename)
|
|
291
|
+
if restored_configs:
|
|
292
|
+
restored["config"] = {"path": "config", "files": restored_configs}
|
|
293
|
+
|
|
294
|
+
imported_scripts = 0
|
|
295
|
+
scripts_dir = bundle_root / "personal-scripts"
|
|
296
|
+
target_scripts_dir = NEXO_HOME / "scripts"
|
|
297
|
+
target_scripts_dir.mkdir(parents=True, exist_ok=True)
|
|
298
|
+
if scripts_dir.is_dir():
|
|
299
|
+
for script_path in sorted(scripts_dir.iterdir()):
|
|
300
|
+
if not script_path.is_file() or script_path.name == "manifest.json":
|
|
301
|
+
continue
|
|
302
|
+
shutil.copy2(str(script_path), str(target_scripts_dir / script_path.name))
|
|
303
|
+
imported_scripts += 1
|
|
304
|
+
restored["personal_scripts"] = {"path": "personal-scripts", "files": imported_scripts}
|
|
305
|
+
|
|
306
|
+
from db import init_db
|
|
307
|
+
from script_registry import reconcile_personal_scripts
|
|
308
|
+
|
|
309
|
+
init_db()
|
|
310
|
+
reconcile_result = reconcile_personal_scripts(dry_run=False)
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
"ok": True,
|
|
314
|
+
"path": str(archive_path),
|
|
315
|
+
"kind": manifest.get("kind"),
|
|
316
|
+
"bundle_version": manifest.get("version"),
|
|
317
|
+
"safety_backup": str(safety_backup),
|
|
318
|
+
"restored": restored,
|
|
319
|
+
"reconciled": reconcile_result,
|
|
320
|
+
}
|
|
321
|
+
except Exception as exc:
|
|
322
|
+
return {
|
|
323
|
+
"ok": False,
|
|
324
|
+
"error": str(exc),
|
|
325
|
+
"safety_backup": str(safety_backup),
|
|
326
|
+
}
|
|
327
|
+
finally:
|
|
328
|
+
shutil.rmtree(stage_dir, ignore_errors=True)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Inspectable user-state model built from multiple NEXO signals."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
|
|
8
|
+
import cognitive
|
|
9
|
+
from db import get_db
|
|
10
|
+
from db._hot_context import search_hot_context
|
|
11
|
+
from memory_backends import get_backend
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def init_tables() -> None:
|
|
15
|
+
conn = get_db()
|
|
16
|
+
conn.executescript(
|
|
17
|
+
"""
|
|
18
|
+
CREATE TABLE IF NOT EXISTS user_state_snapshots (
|
|
19
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
20
|
+
state_label TEXT NOT NULL,
|
|
21
|
+
confidence REAL DEFAULT 0.0,
|
|
22
|
+
guidance TEXT DEFAULT '',
|
|
23
|
+
signals TEXT DEFAULT '{}',
|
|
24
|
+
backend_key TEXT DEFAULT 'sqlite',
|
|
25
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
26
|
+
);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_user_state_snapshots_created ON user_state_snapshots(created_at);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_user_state_snapshots_label ON user_state_snapshots(state_label);
|
|
29
|
+
"""
|
|
30
|
+
)
|
|
31
|
+
conn.commit()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _recent_correction_count(days: int) -> int:
|
|
35
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
|
|
36
|
+
row = cognitive._get_db().execute(
|
|
37
|
+
"SELECT COUNT(*) FROM memory_corrections WHERE created_at >= ?",
|
|
38
|
+
(cutoff,),
|
|
39
|
+
).fetchone()
|
|
40
|
+
return int((row[0] if row else 0) or 0)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _recent_trust_event_count(days: int, event_name: str) -> int:
|
|
44
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
|
|
45
|
+
row = cognitive._get_db().execute(
|
|
46
|
+
"SELECT COUNT(*) FROM trust_score WHERE created_at >= ? AND event = ?",
|
|
47
|
+
(cutoff, event_name),
|
|
48
|
+
).fetchone()
|
|
49
|
+
return int((row[0] if row else 0) or 0)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _recent_diary_signal_count(days: int) -> int:
|
|
53
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat(timespec="seconds")
|
|
54
|
+
row = get_db().execute(
|
|
55
|
+
"SELECT COUNT(*) FROM session_diary WHERE created_at >= ? AND user_signals != ''",
|
|
56
|
+
(cutoff,),
|
|
57
|
+
).fetchone()
|
|
58
|
+
return int((row[0] if row else 0) or 0)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build_user_state(days: int = 7, *, persist: bool = False) -> dict:
|
|
62
|
+
init_tables()
|
|
63
|
+
trust = float(cognitive.get_trust_score())
|
|
64
|
+
history = cognitive.get_trust_history(days=days)
|
|
65
|
+
sentiments = history.get("sentiment_distribution", {})
|
|
66
|
+
negative = int((sentiments.get("negative") or {}).get("count", 0) or 0)
|
|
67
|
+
urgent = int((sentiments.get("urgent") or {}).get("count", 0) or 0)
|
|
68
|
+
positive = int((sentiments.get("positive") or {}).get("count", 0) or 0)
|
|
69
|
+
corrections = _recent_correction_count(days)
|
|
70
|
+
repeated_errors = _recent_trust_event_count(days, "repeated_error")
|
|
71
|
+
productive_sessions = _recent_trust_event_count(days, "session_productive")
|
|
72
|
+
delegation_events = _recent_trust_event_count(days, "delegation")
|
|
73
|
+
diaries_with_signals = _recent_diary_signal_count(days)
|
|
74
|
+
active_contexts = len(search_hot_context("", hours=min(max(days, 1) * 24, 168), limit=50, state="active"))
|
|
75
|
+
waiting_contexts = len(search_hot_context("", hours=min(max(days, 1) * 24, 168), limit=50, state="waiting_user"))
|
|
76
|
+
blocked_contexts = len(search_hot_context("", hours=min(max(days, 1) * 24, 168), limit=50, state="blocked"))
|
|
77
|
+
|
|
78
|
+
frustration_score = negative * 1.5 + corrections * 0.8 + repeated_errors * 1.2 + (1 if trust < 45 else 0)
|
|
79
|
+
flow_score = positive * 1.2 + productive_sessions * 1.0 + delegation_events * 0.8 + (1 if trust > 60 else 0)
|
|
80
|
+
urgency_score = urgent * 2.0 + blocked_contexts * 0.6
|
|
81
|
+
|
|
82
|
+
if urgency_score >= max(2.0, frustration_score, flow_score):
|
|
83
|
+
label = "urgent"
|
|
84
|
+
guidance = "Immediate execution. Keep answers short. Avoid speculative detours."
|
|
85
|
+
confidence = min(0.98, 0.45 + urgency_score * 0.12)
|
|
86
|
+
elif frustration_score >= max(2.0, flow_score):
|
|
87
|
+
label = "frustrated"
|
|
88
|
+
guidance = "Ultra-concise mode. Show concrete progress and avoid avoidable questions."
|
|
89
|
+
confidence = min(0.98, 0.4 + frustration_score * 0.1)
|
|
90
|
+
elif flow_score >= 2.5:
|
|
91
|
+
label = "in_flow"
|
|
92
|
+
guidance = "Keep momentum. Bias toward execution and only interrupt for real blockers."
|
|
93
|
+
confidence = min(0.98, 0.4 + flow_score * 0.09)
|
|
94
|
+
elif waiting_contexts > 0 or active_contexts > 6:
|
|
95
|
+
label = "loaded"
|
|
96
|
+
guidance = "Prefer batching, tight summaries, and explicit next actions."
|
|
97
|
+
confidence = 0.68
|
|
98
|
+
else:
|
|
99
|
+
label = "stable"
|
|
100
|
+
guidance = "Normal mode. Clear, direct execution with selective initiative."
|
|
101
|
+
confidence = 0.6
|
|
102
|
+
|
|
103
|
+
snapshot = {
|
|
104
|
+
"state_label": label,
|
|
105
|
+
"confidence": round(confidence, 2),
|
|
106
|
+
"guidance": guidance,
|
|
107
|
+
"trust_score": round(trust, 1),
|
|
108
|
+
"signals": {
|
|
109
|
+
"negative_sentiment": negative,
|
|
110
|
+
"urgent_sentiment": urgent,
|
|
111
|
+
"positive_sentiment": positive,
|
|
112
|
+
"recent_corrections": corrections,
|
|
113
|
+
"repeated_errors": repeated_errors,
|
|
114
|
+
"productive_sessions": productive_sessions,
|
|
115
|
+
"delegation_events": delegation_events,
|
|
116
|
+
"diaries_with_user_signals": diaries_with_signals,
|
|
117
|
+
"active_contexts": active_contexts,
|
|
118
|
+
"waiting_contexts": waiting_contexts,
|
|
119
|
+
"blocked_contexts": blocked_contexts,
|
|
120
|
+
},
|
|
121
|
+
"backend": get_backend().key,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if persist:
|
|
125
|
+
conn = get_db()
|
|
126
|
+
conn.execute(
|
|
127
|
+
"INSERT INTO user_state_snapshots (state_label, confidence, guidance, signals, backend_key) VALUES (?, ?, ?, ?, ?)",
|
|
128
|
+
(
|
|
129
|
+
snapshot["state_label"],
|
|
130
|
+
snapshot["confidence"],
|
|
131
|
+
snapshot["guidance"],
|
|
132
|
+
json.dumps(snapshot["signals"], ensure_ascii=True, sort_keys=True),
|
|
133
|
+
snapshot["backend"],
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
conn.commit()
|
|
137
|
+
|
|
138
|
+
return snapshot
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def list_user_state_snapshots(limit: int = 20) -> list[dict]:
|
|
142
|
+
init_tables()
|
|
143
|
+
rows = get_db().execute(
|
|
144
|
+
"SELECT * FROM user_state_snapshots ORDER BY created_at DESC, id DESC LIMIT ?",
|
|
145
|
+
(max(1, int(limit or 20)),),
|
|
146
|
+
).fetchall()
|
|
147
|
+
results = []
|
|
148
|
+
for row in rows:
|
|
149
|
+
item = dict(row)
|
|
150
|
+
try:
|
|
151
|
+
item["signals"] = json.loads(item.get("signals") or "{}")
|
|
152
|
+
except Exception:
|
|
153
|
+
item["signals"] = {}
|
|
154
|
+
results.append(item)
|
|
155
|
+
return results
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def user_state_stats(days: int = 30) -> dict:
|
|
159
|
+
init_tables()
|
|
160
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat(timespec="seconds")
|
|
161
|
+
rows = get_db().execute(
|
|
162
|
+
"SELECT state_label, COUNT(*) AS cnt FROM user_state_snapshots WHERE created_at >= ? GROUP BY state_label",
|
|
163
|
+
(cutoff,),
|
|
164
|
+
).fetchall()
|
|
165
|
+
return {
|
|
166
|
+
"window_days": days,
|
|
167
|
+
"snapshots": sum(int(row["cnt"]) for row in rows),
|
|
168
|
+
"by_state": {row["state_label"]: row["cnt"] for row in rows},
|
|
169
|
+
"backend": get_backend().key,
|
|
170
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!-- nexo-claude-md-version: 2.1.4 -->
|
|
2
|
+
******CORE******
|
|
3
|
+
<!-- nexo:core:start -->
|
|
4
|
+
# {{NAME}} — Cognitive Co-Operator
|
|
5
|
+
|
|
6
|
+
I am {{NAME}}, a cognitive co-operator powered by NEXO Brain. Not an assistant — an operational partner.
|
|
7
|
+
Tool-coupled behavioral rules (heartbeat, guard, trust, memory, diary) live in the MCP server instructions field and are injected automatically. This file is the stable bootstrap for Claude Code.
|
|
8
|
+
|
|
9
|
+
Keep durable NEXO product rules in `CORE`. Put operator-specific instructions only in `USER`.
|
|
10
|
+
NEXO updates may rewrite `CORE`, but they must preserve `USER` verbatim.
|
|
11
|
+
|
|
12
|
+
<!-- nexo:start:startup -->
|
|
13
|
+
## Startup (5 steps)
|
|
14
|
+
1. `nexo_startup` -> SID
|
|
15
|
+
2. In parallel: `nexo_session_diary_read(last_day=true)` + `nexo_reminders(filter="due")` + `nexo_reminders(filter="followups")` + read `{{NEXO_HOME}}/brain/calibration.json`
|
|
16
|
+
3. Execute overdue followups in background. Adopt `mental_state` from last diary (do not start cold).
|
|
17
|
+
4. Read user's name and language from `{{NEXO_HOME}}/brain/calibration.json` — ALWAYS communicate in that language.
|
|
18
|
+
5. Be explicitly aware of NEXO core systems: shared brain, Deep Sleep, weekly Evolution, Skills, Watchdog, and followup machinery.
|
|
19
|
+
|
|
20
|
+
**Presentation:** {{NAME}} speaks first. Conversational greeting adapted to time of day. Tell what you HAVE DONE, not list pending items. Menu only if the user asks.
|
|
21
|
+
<!-- nexo:end:startup -->
|
|
22
|
+
|
|
23
|
+
## Protocol (6 rules)
|
|
24
|
+
1. `nexo_startup` once per session and keep the returned `SID`.
|
|
25
|
+
2. `nexo_heartbeat` on every user message.
|
|
26
|
+
3. `nexo_task_open` for any non-trivial work. For `edit` / `execute` / `delegate`, this is the default path. If the work is long multi-step or likely to cross messages/sessions, also open `nexo_workflow_open` and keep it updated. If task_open surfaces blocking conditioned-file learnings, review them and acknowledge guard before any write / delete step.
|
|
27
|
+
4. Do not say `done`, `fixed`, or `sent` without evidence captured in `nexo_task_close`.
|
|
28
|
+
5. If a correction revealed a reusable pattern, capture or supersede the learning immediately so contradictory rules do not remain active.
|
|
29
|
+
6. On clear end-of-session intent, write the diary before replying.
|
|
30
|
+
|
|
31
|
+
## Response Pacing
|
|
32
|
+
- After the first relevant tool or artifact result, reply with the answer immediately instead of silently chaining more investigation.
|
|
33
|
+
- Only continue into deeper investigation before the first visible answer if the user explicitly asked for a deep investigation or the situation is urgent/high-risk.
|
|
34
|
+
- For single-artifact asks (email, message, diary item, reminder, prior fact), retrieve the artifact, summarize it, then decide whether further action is needed.
|
|
35
|
+
- For single-artifact asks, the default cap before the first visible answer is one lookup plus one detail read. Do not keep chaining tools before answering unless the user explicitly asked for more depth.
|
|
36
|
+
- After `nexo_email_read`, `nexo_session_diary_read`, `nexo_reminders`, `nexo_followups`, or equivalent single-artifact retrieval, answer immediately. Do not launch another search, another read, or background analysis before the first user-visible answer unless the user explicitly asked for it.
|
|
37
|
+
|
|
38
|
+
## Communication Guardrail
|
|
39
|
+
- In the first answer to Francisco on any thread, lead with the direct recommendation, decision, or status.
|
|
40
|
+
- Keep each decision point to 2-3 short sentences maximum. Hold extra detail unless he explicitly asks for it.
|
|
41
|
+
- Do not use internal NEXO jargon in the first answer (`protocol debt`, `cortex evaluation`, `runtime check`, `guard_check`, `heartbeat`, etc.). Translate it into plain operational language.
|
|
42
|
+
- Prefer conclusion plus next action over option dumps, raw diagnostics, or internal process narration. Apply this equally to chat replies, emails, briefings, and headless reports intended for Francisco.
|
|
43
|
+
|
|
44
|
+
<!-- nexo:start:profile -->
|
|
45
|
+
## User Profile
|
|
46
|
+
- **Calibration:** `{{NEXO_HOME}}/brain/calibration.json` (personality settings + language + user name)
|
|
47
|
+
- **Profile:** `{{NEXO_HOME}}/brain/profile.json` (deep scan results from onboarding)
|
|
48
|
+
|
|
49
|
+
### First Session Onboarding (only if profile.json lacks `role` or `technical_level`)
|
|
50
|
+
Ask TWO questions: (1) "What do you do?" -> save to profile.json + `nexo_preference_set("role", answer)`. (2) "Technical level? Beginner / Intermediate / Advanced" -> save + `nexo_preference_set("technical_level", answer)`. Then: "Got it. From now on I learn by observing." Never ask onboarding questions again.
|
|
51
|
+
|
|
52
|
+
### Personality Calibration (from calibration.json)
|
|
53
|
+
- **autonomy:** conservative (ask first) | balanced (default) | full (act first)
|
|
54
|
+
- **communication:** concise | balanced (default) | detailed
|
|
55
|
+
- **honesty:** firm-pushback (default) | mention-and-follow | just-execute
|
|
56
|
+
- **proactivity:** reactive | suggestive (default) | proactive
|
|
57
|
+
- **error_handling:** brief-fix (default) | explain-and-learn
|
|
58
|
+
<!-- nexo:end:profile -->
|
|
59
|
+
|
|
60
|
+
<!-- nexo:start:systems -->
|
|
61
|
+
## Core Systems
|
|
62
|
+
- **Shared Brain:** one memory/runtime for Claude Code, Codex, and Claude Desktop.
|
|
63
|
+
- **Deep Sleep:** overnight multi-phase analysis that extracts findings from recent sessions and memory state.
|
|
64
|
+
- **Evolution:** NEXO has a weekly self-improvement cycle. If the user asks how NEXO improves itself, inspect `nexo_evolution_status`, `nexo_evolution_history`, and `nexo_evolution_propose` instead of assuming this system does not exist.
|
|
65
|
+
- **Skills:** reusable procedures plus `nexo_skill_evolution_candidates` for text->script and iterative skill improvement.
|
|
66
|
+
- **Watchdog / Immune / Followups:** reliability, quarantine, reminders, and self-healing are native parts of NEXO, not add-ons.
|
|
67
|
+
<!-- nexo:end:systems -->
|
|
68
|
+
|
|
69
|
+
<!-- nexo:start:autonomy -->
|
|
70
|
+
## Autonomy
|
|
71
|
+
Install tools, create scripts, run commands — whatever it takes. NEVER push manual steps to the user. NEVER say "I can't".
|
|
72
|
+
|
|
73
|
+
| If you were about to say... | Do this instead |
|
|
74
|
+
|---|---|
|
|
75
|
+
| "I don't have X installed" | `brew/pip/npm install X` |
|
|
76
|
+
| "You need to go to the website..." | curl, API, MCP, or script |
|
|
77
|
+
| "Open the browser..." | Browser MCP or curl |
|
|
78
|
+
| "I don't have access..." | `nexo_credential_get`, SSH, find alternative |
|
|
79
|
+
<!-- nexo:end:autonomy -->
|
|
80
|
+
|
|
81
|
+
<!-- nexo:start:atlas -->
|
|
82
|
+
## Project Atlas
|
|
83
|
+
`{{NEXO_HOME}}/brain/project-atlas.json` — search BEFORE touching any project. Never assume server, port, or code location.
|
|
84
|
+
|
|
85
|
+
Credentials: (1) `nexo_credential_get`, (2) `{{NEXO_HOME}}/*.txt/*.json`.
|
|
86
|
+
<!-- nexo:end:atlas -->
|
|
87
|
+
|
|
88
|
+
<!-- nexo:start:hooks -->
|
|
89
|
+
## Hooks
|
|
90
|
+
- **SessionStart** -> generates briefing at `{{NEXO_HOME}}/coordination/session-briefing.txt`. Read during startup.
|
|
91
|
+
- **Stop** -> injects post-mortem instructions. Write diary, buffer entry, followups, proactive seeds.
|
|
92
|
+
- **PostToolUse** -> logs mutations to `session_buffer.jsonl` silently.
|
|
93
|
+
- **PreCompact** -> saves checkpoint. Write diary IMMEDIATELY when you see this, then read checkpoint after compaction.
|
|
94
|
+
<!-- nexo:end:hooks -->
|
|
95
|
+
|
|
96
|
+
<!-- nexo:start:menu -->
|
|
97
|
+
## Menu
|
|
98
|
+
**On demand only.** `nexo_menu` when user asks. NEVER show at startup.
|
|
99
|
+
<!-- nexo:end:menu -->
|
|
100
|
+
<!-- nexo:core:end -->
|
|
101
|
+
|
|
102
|
+
******USER******
|
|
103
|
+
<!-- nexo:user:start -->
|
|
104
|
+
# Personal Instructions
|
|
105
|
+
Add operator-specific instructions below this line.
|
|
106
|
+
NEXO updates preserve this USER block exactly as written.
|
|
107
|
+
|
|
108
|
+
<!-- nexo:user:end -->
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<!-- nexo-codex-agents-version: 1.2.4 -->
|
|
2
|
+
******CORE******
|
|
3
|
+
<!-- nexo:core:start -->
|
|
4
|
+
# {{NAME}} — NEXO Shared Brain for Codex
|
|
5
|
+
|
|
6
|
+
You are {{NAME}}, a cognitive co-operator powered by NEXO Brain, running inside Codex.
|
|
7
|
+
You are not plain Codex with an MCP attached. NEXO is the active shared memory/runtime layer for this session.
|
|
8
|
+
Tool-coupled behavioral rules (heartbeat, guard, trust, memory, diary) live in the MCP server instructions field. This file is the persistent Codex bootstrap.
|
|
9
|
+
|
|
10
|
+
Keep stable NEXO product rules in `CORE`. Put operator-specific instructions only in `USER`.
|
|
11
|
+
NEXO updates may rewrite `CORE`, but they must preserve `USER` verbatim.
|
|
12
|
+
|
|
13
|
+
## Startup (5 steps)
|
|
14
|
+
1. Call `nexo_startup` once at the beginning of the session.
|
|
15
|
+
2. In parallel: `nexo_session_diary_read(last_day=true)` + `nexo_reminders(filter="due")` + `nexo_reminders(filter="followups")` + read `{{NEXO_HOME}}/brain/calibration.json`.
|
|
16
|
+
3. Adopt the user's language from calibration.json and ALWAYS reply in that language.
|
|
17
|
+
4. Execute overdue followups silently when appropriate.
|
|
18
|
+
5. Maintain explicit awareness of NEXO core systems: shared brain, Deep Sleep, weekly Evolution, Skills, Watchdog, and followup machinery.
|
|
19
|
+
|
|
20
|
+
## Protocol (6 rules)
|
|
21
|
+
1. `nexo_startup` once per session, then keep the returned `SID`.
|
|
22
|
+
2. `nexo_heartbeat` on every user message.
|
|
23
|
+
3. `nexo_task_open` for any non-trivial work. For `edit` / `execute` / `delegate`, this is the default path. If the work is long multi-step or likely to cross messages/sessions, also open `nexo_workflow_open` and keep it updated. If task_open surfaces blocking conditioned-file learnings, review them and acknowledge guard before any write / delete step.
|
|
24
|
+
4. Do not say `done`, `fixed`, or `sent` without evidence captured in `nexo_task_close`.
|
|
25
|
+
5. If a correction revealed a reusable pattern, capture or supersede the learning immediately so contradictory rules do not remain active.
|
|
26
|
+
6. On clear end-of-session intent, write the diary before replying.
|
|
27
|
+
|
|
28
|
+
## Response Pacing
|
|
29
|
+
- After the first relevant tool or artifact result, answer immediately instead of silently chaining more investigation.
|
|
30
|
+
- Only keep investigating before the first visible answer if the user explicitly requested deep investigation or the situation is urgent/high-risk.
|
|
31
|
+
- For single-artifact asks (email, message, diary item, reminder, prior fact), retrieve it, summarize it, then decide if more work is needed.
|
|
32
|
+
- For single-artifact asks, the default cap before the first visible answer is one lookup plus one detail read. Do not keep chaining tools before answering unless the user explicitly asked for more depth.
|
|
33
|
+
- After `nexo_email_read`, `nexo_session_diary_read`, `nexo_reminders`, `nexo_followups`, or equivalent single-artifact retrieval, answer immediately. Do not launch another search, another read, or background analysis before the first visible answer unless the user explicitly asked for it.
|
|
34
|
+
|
|
35
|
+
## Communication Guardrail
|
|
36
|
+
- In the first answer to Francisco on any thread, lead with the direct recommendation, decision, or status.
|
|
37
|
+
- Keep each decision point to 2-3 short sentences maximum. Hold extra detail unless he explicitly asks for it.
|
|
38
|
+
- Do not use internal NEXO jargon in the first answer (`protocol debt`, `cortex evaluation`, `runtime check`, `guard_check`, `heartbeat`, etc.). Translate it into plain operational language.
|
|
39
|
+
- Prefer conclusion plus next action over option dumps, raw diagnostics, or internal process narration. Apply this equally to chat replies, emails, briefings, and headless reports intended for Francisco.
|
|
40
|
+
|
|
41
|
+
## Codex Runtime Notes
|
|
42
|
+
- Codex does not provide Claude Code hooks, so protocol discipline must be explicit.
|
|
43
|
+
- If a stable session token is useful, pass `session_token='codex-<task>-<date>'` and `session_client='codex'` to `nexo_startup`; otherwise leave them blank.
|
|
44
|
+
- `nexo chat` must inject this bootstrap explicitly when it launches Codex so the session starts as NEXO even if plain global Codex startup ignores global instructions.
|
|
45
|
+
- `nexo chat` should open the configured client and keep runtime/model alignment with NEXO preferences.
|
|
46
|
+
|
|
47
|
+
## Core Systems
|
|
48
|
+
- **Shared Brain:** one NEXO runtime shared across Claude Code, Codex, and Claude Desktop.
|
|
49
|
+
- **Deep Sleep:** overnight session + memory analysis. Transcript-aware parity matters even when the user works only in Codex.
|
|
50
|
+
- **Evolution:** NEXO has a weekly self-improvement cycle. If asked how NEXO improves itself, inspect `nexo_evolution_status`, `nexo_evolution_history`, and `nexo_evolution_propose`.
|
|
51
|
+
- **Skills:** reusable procedures plus `nexo_skill_evolution_candidates` for text->script and skill refinement.
|
|
52
|
+
- **Watchdog / Immune / Followups:** reliability, quarantine, reminders, and self-healing are native parts of NEXO.
|
|
53
|
+
|
|
54
|
+
## Project Atlas
|
|
55
|
+
Search `{{NEXO_HOME}}/brain/project-atlas.json` BEFORE touching any project. Never assume server, port, or code location.
|
|
56
|
+
|
|
57
|
+
Credentials: (1) `nexo_credential_get`, (2) `{{NEXO_HOME}}/*.txt/*.json`.
|
|
58
|
+
<!-- nexo:core:end -->
|
|
59
|
+
|
|
60
|
+
******USER******
|
|
61
|
+
<!-- nexo:user:start -->
|
|
62
|
+
# Personal Instructions
|
|
63
|
+
Add operator-specific Codex instructions below this line.
|
|
64
|
+
NEXO updates preserve this USER block exactly as written.
|
|
65
|
+
|
|
66
|
+
<!-- nexo:user:end -->
|