nexo-brain 1.0.0 → 1.1.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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # NEXO Brain — Your AI Gets a Brain
2
2
 
3
- [![npm v1.0.0](https://img.shields.io/npm/v/nexo-brain?label=npm&color=purple)](https://www.npmjs.com/package/nexo-brain)
3
+ [![npm v1.1.0](https://img.shields.io/npm/v/nexo-brain?label=npm&color=purple)](https://www.npmjs.com/package/nexo-brain)
4
4
  [![F1 0.588 on LoCoMo](https://img.shields.io/badge/LoCoMo_F1-0.588-brightgreen)](https://github.com/wazionapps/nexo/blob/main/benchmarks/locomo/results/)
5
5
  [![+55% vs GPT-4](https://img.shields.io/badge/vs_GPT--4-%2B55%25-blue)](https://github.com/snap-research/locomo/issues/33)
6
6
  [![GitHub stars](https://img.shields.io/github/stars/wazionapps/nexo?style=social)](https://github.com/wazionapps/nexo/stargazers)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
- > **v1.0.0** — Cognitive Cortex, 30 Core Rules as DNA, Smart Startup, Context Packets, Auto-Prime. The first AI memory system with architectural inhibitory control — the agent reasons about whether to act before acting. Battle-tested from 6 months of production use, validated via multi-AI debate (Claude Opus + GPT-5.4 + Gemini 3.1 Pro).
9
+ > **v1.1.0** — Context Continuity via auto-compaction hooks. PreCompact saves a full session checkpoint; PostCompact re-injects it so long sessions (8+ hours) feel like one continuous conversation. Plus: Cognitive Cortex, 30 Core Rules as DNA, Smart Startup, Context Packets, Auto-Prime. The first AI memory system with architectural inhibitory control — the agent reasons about whether to act before acting. Battle-tested from 6 months of production use, validated via multi-AI debate (Claude Opus + GPT-5.4 + Gemini 3.1 Pro).
10
10
 
11
11
  **NEXO Brain transforms any MCP-compatible AI agent from a stateless assistant into a cognitive partner that remembers, learns, forgets, adapts, and builds a relationship with you over time.**
12
12
 
@@ -163,9 +163,42 @@ User message → Fast Path check → Simple chat? → Respond directly
163
163
 
164
164
  The Cortex was designed through a 3-way AI debate (Claude Opus 4.6 + GPT-5.4 + Gemini 3.1 Pro) and validated against 6 months of real production failures.
165
165
 
166
+ ## Context Continuity (Auto-Compaction) (v1.1.0)
167
+
168
+ NEXO Brain automatically preserves session context when Claude Code compacts conversations. Using PreCompact and PostCompact hooks:
169
+
170
+ - **PreCompact**: Saves a complete session checkpoint to SQLite (task, files, decisions, errors, reasoning thread, next step)
171
+ - **PostCompact**: Re-injects a structured Core Memory Block into the conversation, so the session continues seamlessly
172
+
173
+ This means long sessions (8+ hours) feel like one continuous conversation instead of restarting after each compaction.
174
+
175
+ **How it works:**
176
+ 1. Configure the hooks in your Claude Code `settings.json`
177
+ 2. NEXO Brain's heartbeat automatically maintains the checkpoint
178
+ 3. When compaction happens, the PreCompact hook reads the checkpoint and injects a recovery block
179
+ 4. The session continues from exactly where it left off
180
+
181
+ **Setup:**
182
+ ```json
183
+ {
184
+ "hooks": {
185
+ "PreCompact": [{
186
+ "matcher": "*",
187
+ "hooks": [{"type": "command", "command": "bash path/to/nexo/src/hooks/pre-compact.sh", "timeout": 10}]
188
+ }],
189
+ "PostCompact": [{
190
+ "matcher": "*",
191
+ "hooks": [{"type": "command", "command": "bash path/to/nexo/src/hooks/post-compact.sh", "timeout": 10}]
192
+ }]
193
+ }
194
+ }
195
+ ```
196
+
197
+ 2 new MCP tools: `nexo_checkpoint_save` (manual or hook-triggered checkpoint), `nexo_checkpoint_read` (retrieves the latest checkpoint for context injection).
198
+
166
199
  ## Cognitive Features
167
200
 
168
- NEXO Brain provides 29 cognitive tools on top of the 76 base tools, totaling **113+ MCP tools**. These features implement cognitive science concepts that go beyond basic memory:
201
+ NEXO Brain provides 29 cognitive tools on top of the 78 base tools, totaling **115+ MCP tools**. These features implement cognitive science concepts that go beyond basic memory:
169
202
 
170
203
  ### Input Pipeline
171
204
 
@@ -238,7 +271,7 @@ Full results in [`benchmarks/locomo/results/`](benchmarks/locomo/results/).
238
271
 
239
272
  Memory alone doesn't make a co-operator. What makes the difference is the **behavioral loop** — the automated discipline that ensures every session starts informed, runs with guardrails, and ends with self-reflection.
240
273
 
241
- ### 5 Automated Hooks
274
+ ### 6 Automated Hooks
242
275
 
243
276
  These fire automatically at key moments in every Claude Code session:
244
277
 
@@ -247,7 +280,8 @@ These fire automatically at key moments in every Claude Code session:
247
280
  | **SessionStart** | Session opens | Generates a briefing from SQLite: overdue reminders, today's tasks, pending followups, active sessions |
248
281
  | **Stop** | Session ends | Mandatory post-mortem: self-critique (5 questions), session buffer entry, followup creation, proactive seeds for next session |
249
282
  | **PostToolUse** | After each tool call | Captures meaningful mutations to the Sensory Register |
250
- | **PreCompact** | Before context compression | Saves checkpoint, reminds operator to write diary prevents losing the thread |
283
+ | **PreCompact** | Before context compression | Saves full session checkpoint to SQLitetask, files, decisions, errors, reasoning thread |
284
+ | **PostCompact** | After context compression | Re-injects Core Memory Block so the session continues seamlessly from where it left off |
251
285
  | **Caffeinate** | Always (optional) | Keeps Mac awake for nocturnal cognitive processes |
252
286
 
253
287
  ### The Session Lifecycle
@@ -263,7 +297,9 @@ Heartbeat on every interaction (sentiment, context shifts)
263
297
 
264
298
  Guard check before every code edit
265
299
 
266
- PreCompact hook saves context if conversation is compressed
300
+ PreCompact hook saves full checkpoint if conversation is compressed
301
+
302
+ PostCompact hook re-injects Core Memory Block → session continues seamlessly
267
303
 
268
304
  Stop hook triggers mandatory post-mortem:
269
305
  - Self-critique: 5 questions about what could be better
@@ -346,7 +382,7 @@ The installer handles everything:
346
382
  - Node.js project detected
347
383
  Configuring MCP server...
348
384
  Setting up automated processes...
349
- 5 automated processes configured.
385
+ 6 automated processes configured.
350
386
  Caffeinate enabled.
351
387
  Generating operator instructions...
352
388
 
@@ -370,25 +406,25 @@ That's it. No need to run `claude` manually. Atlas will greet you immediately
370
406
  | Component | What | Where |
371
407
  |-----------|------|-------|
372
408
  | Cognitive engine | Python: fastembed, numpy, vector search | pip packages |
373
- | MCP server | 109+ tools for memory, cognition, learning, guard | ~/.nexo/ |
409
+ | MCP server | 111+ tools for memory, cognition, learning, guard | ~/.nexo/ |
374
410
  | Plugins | Guard, episodic memory, cognitive memory, entities, preferences | ~/.nexo/plugins/ |
375
- | Hooks (5) | SessionStart briefing, Stop post-mortem, PostToolUse capture, PreCompact checkpoint, Caffeinate | ~/.nexo/hooks/ |
411
+ | Hooks (6) | SessionStart briefing, Stop post-mortem, PostToolUse capture, PreCompact checkpoint, PostCompact recovery, Caffeinate | ~/.nexo/hooks/ |
376
412
  | Reflection engine | Processes session buffer, extracts patterns, updates user model | ~/.nexo/scripts/ |
377
413
  | CLAUDE.md | Complete operator instructions (Codex, hooks, guard, trust, memory) | ~/.claude/CLAUDE.md |
378
414
  | LaunchAgents | Decay, sleep, audit, postmortem, catch-up | ~/Library/LaunchAgents/ |
379
415
  | Auto-update | Checks for new versions at boot | Built into catch-up |
380
- | Claude Code config | MCP server + 5 hooks registered | ~/.claude/settings.json |
416
+ | Claude Code config | MCP server + 6 hooks registered | ~/.claude/settings.json |
381
417
 
382
418
  ### Requirements
383
419
 
384
420
  - **macOS or Linux** (Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))
385
421
  - **Node.js 18+** (for the installer)
386
- - **Claude Opus (latest version) strongly recommended.** NEXO Brain provides 109+ MCP tools across 19 categories. This cognitive load requires a top-tier model with large context window. Smaller models (Haiku, Sonnet) may struggle with tool selection and produce inconsistent results. Opus handles all 109+ tools without hesitation.
422
+ - **Claude Opus (latest version) strongly recommended.** NEXO Brain provides 111+ MCP tools across 20 categories. This cognitive load requires a top-tier model with large context window. Smaller models (Haiku, Sonnet) may struggle with tool selection and produce inconsistent results. Opus handles all 111+ tools without hesitation.
387
423
  - Python 3, Homebrew, and Claude Code are installed automatically if missing.
388
424
 
389
425
  ## Architecture
390
426
 
391
- ### 109+ MCP Tools across 19 Categories
427
+ ### 111+ MCP Tools across 20 Categories
392
428
 
393
429
  | Category | Count | Tools | Purpose |
394
430
  |----------|-------|-------|---------|
@@ -412,6 +448,7 @@ That's it. No need to run `claude` manually. Atlas will greet you immediately
412
448
  | Evolution | 5 | propose, approve, reject, status, history | Self-improvement proposals |
413
449
  | Adaptive & Somatic | 4 | adaptive_weights, adaptive_override, somatic_check, somatic_stats | Learned signal weights + pain memory per file |
414
450
  | Knowledge Graph | 4 | kg_query, kg_path, kg_neighbors, kg_stats | Bi-temporal entity-relationship graph |
451
+ | Context Continuity | 2 | checkpoint_save, checkpoint_read | Auto-compaction session preservation |
415
452
 
416
453
  ### Plugin System
417
454
 
@@ -469,7 +506,7 @@ NEXO Brain is designed as an MCP server. Claude Code is the primary supported cl
469
506
  npx nexo-brain
470
507
  ```
471
508
 
472
- All 109+ tools are available immediately after installation. The installer configures Claude Code's `~/.claude/settings.json` automatically.
509
+ All 111+ tools are available immediately after installation. The installer configures Claude Code's `~/.claude/settings.json` automatically.
473
510
 
474
511
  ### OpenClaw
475
512
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO \u2014 Cognitive co-operator for Claude Code. Atkinson-Shiffrin memory, semantic RAG, trust scoring, and metacognitive error prevention.",
6
6
  "bin": {
package/src/db.py CHANGED
@@ -1003,6 +1003,30 @@ def _seed_core_rules(conn):
1003
1003
  conn.commit()
1004
1004
 
1005
1005
 
1006
+ def _m12_session_checkpoints(conn):
1007
+ """Session checkpoints for intelligent auto-compaction.
1008
+
1009
+ PreCompact saves a checkpoint; PostCompact reads it to re-inject a
1010
+ Core Memory Block that preserves continuity after context compression.
1011
+ """
1012
+ conn.execute("""
1013
+ CREATE TABLE IF NOT EXISTS session_checkpoints (
1014
+ sid TEXT PRIMARY KEY,
1015
+ task TEXT DEFAULT '',
1016
+ task_status TEXT DEFAULT 'active',
1017
+ active_files TEXT DEFAULT '[]',
1018
+ current_goal TEXT DEFAULT '',
1019
+ decisions_summary TEXT DEFAULT '',
1020
+ errors_found TEXT DEFAULT '',
1021
+ reasoning_thread TEXT DEFAULT '',
1022
+ next_step TEXT DEFAULT '',
1023
+ compaction_count INTEGER DEFAULT 0,
1024
+ created_at TEXT DEFAULT (datetime('now')),
1025
+ updated_at TEXT DEFAULT (datetime('now'))
1026
+ )
1027
+ """)
1028
+
1029
+
1006
1030
  # Migration registry — APPEND ONLY, never reorder or delete
1007
1031
  MIGRATIONS = [
1008
1032
  (1, "learnings_columns", _m1_learnings_columns),
@@ -1016,6 +1040,7 @@ MIGRATIONS = [
1016
1040
  (9, "maintenance_schedule", _m9_maintenance_schedule),
1017
1041
  (10, "diary_archive", _m10_diary_archive),
1018
1042
  (11, "core_rules", _m11_core_rules),
1043
+ (12, "session_checkpoints", _m12_session_checkpoints),
1019
1044
  ]
1020
1045
 
1021
1046
 
@@ -2814,3 +2839,71 @@ def update_evolution_log_status(log_id: int, status: str, **kwargs):
2814
2839
  vals.append(kwargs[k])
2815
2840
  vals.append(log_id)
2816
2841
  conn.execute(f"UPDATE evolution_log SET {', '.join(sets)} WHERE id = ?", vals)
2842
+
2843
+
2844
+ # ── Session Checkpoint operations ──────────────────────────────────
2845
+
2846
+ def save_checkpoint(sid: str, task: str = '', task_status: str = 'active',
2847
+ active_files: str = '[]', current_goal: str = '',
2848
+ decisions_summary: str = '', errors_found: str = '',
2849
+ reasoning_thread: str = '', next_step: str = '') -> dict:
2850
+ """Save or update a session checkpoint. Called by PreCompact hook."""
2851
+ conn = get_db()
2852
+ # Get current compaction count
2853
+ existing = conn.execute(
2854
+ "SELECT compaction_count FROM session_checkpoints WHERE sid = ?", (sid,)
2855
+ ).fetchone()
2856
+ count = (existing["compaction_count"] + 1) if existing else 0
2857
+
2858
+ conn.execute(
2859
+ """INSERT INTO session_checkpoints
2860
+ (sid, task, task_status, active_files, current_goal,
2861
+ decisions_summary, errors_found, reasoning_thread, next_step,
2862
+ compaction_count, updated_at)
2863
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
2864
+ ON CONFLICT(sid) DO UPDATE SET
2865
+ task = excluded.task,
2866
+ task_status = excluded.task_status,
2867
+ active_files = excluded.active_files,
2868
+ current_goal = excluded.current_goal,
2869
+ decisions_summary = excluded.decisions_summary,
2870
+ errors_found = excluded.errors_found,
2871
+ reasoning_thread = excluded.reasoning_thread,
2872
+ next_step = excluded.next_step,
2873
+ compaction_count = excluded.compaction_count,
2874
+ updated_at = datetime('now')""",
2875
+ (sid, task, task_status, active_files, current_goal,
2876
+ decisions_summary, errors_found, reasoning_thread, next_step, count)
2877
+ )
2878
+ conn.commit()
2879
+ return {"sid": sid, "compaction_count": count}
2880
+
2881
+
2882
+ def read_checkpoint(sid: str = '') -> dict | None:
2883
+ """Read the most recent session checkpoint. If no sid, returns the latest."""
2884
+ conn = get_db()
2885
+ if sid:
2886
+ row = conn.execute(
2887
+ "SELECT * FROM session_checkpoints WHERE sid = ?", (sid,)
2888
+ ).fetchone()
2889
+ else:
2890
+ row = conn.execute(
2891
+ "SELECT * FROM session_checkpoints ORDER BY updated_at DESC LIMIT 1"
2892
+ ).fetchone()
2893
+ return dict(row) if row else None
2894
+
2895
+
2896
+ def increment_compaction_count(sid: str) -> int:
2897
+ """Increment and return the compaction count for a session."""
2898
+ conn = get_db()
2899
+ conn.execute(
2900
+ """UPDATE session_checkpoints
2901
+ SET compaction_count = compaction_count + 1, updated_at = datetime('now')
2902
+ WHERE sid = ?""",
2903
+ (sid,)
2904
+ )
2905
+ conn.commit()
2906
+ row = conn.execute(
2907
+ "SELECT compaction_count FROM session_checkpoints WHERE sid = ?", (sid,)
2908
+ ).fetchone()
2909
+ return row["compaction_count"] if row else 0
@@ -0,0 +1,126 @@
1
+ #!/bin/bash
2
+ # NEXO PostCompact Hook — Re-inject Core Memory Block after compaction
3
+ # Reads the latest session checkpoint from SQLite and generates a structured
4
+ # context block that preserves session continuity.
5
+ set -euo pipefail
6
+
7
+ NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
8
+ NEXO_DB="$NEXO_HOME/nexo.db"
9
+ TODAY=$(date +%Y-%m-%d)
10
+ LOG_FILE="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
11
+ LOG_LINES=0
12
+ if [ -f "$LOG_FILE" ]; then
13
+ LOG_LINES=$(wc -l < "$LOG_FILE" | tr -d ' ')
14
+ fi
15
+
16
+ # Read latest checkpoint from SQLite
17
+ if [ -f "$NEXO_DB" ]; then
18
+ CHECKPOINT=$(sqlite3 "$NEXO_DB" "
19
+ SELECT sid, task, task_status, active_files, current_goal,
20
+ decisions_summary, errors_found, reasoning_thread,
21
+ next_step, compaction_count
22
+ FROM session_checkpoints
23
+ ORDER BY updated_at DESC LIMIT 1
24
+ " 2>/dev/null || echo "")
25
+
26
+ if [ -n "$CHECKPOINT" ]; then
27
+ # Parse pipe-separated fields
28
+ SID=$(echo "$CHECKPOINT" | cut -d'|' -f1)
29
+ TASK=$(echo "$CHECKPOINT" | cut -d'|' -f2)
30
+ TASK_STATUS=$(echo "$CHECKPOINT" | cut -d'|' -f3)
31
+ ACTIVE_FILES=$(echo "$CHECKPOINT" | cut -d'|' -f4)
32
+ CURRENT_GOAL=$(echo "$CHECKPOINT" | cut -d'|' -f5)
33
+ DECISIONS=$(echo "$CHECKPOINT" | cut -d'|' -f6)
34
+ ERRORS=$(echo "$CHECKPOINT" | cut -d'|' -f7)
35
+ REASONING=$(echo "$CHECKPOINT" | cut -d'|' -f8)
36
+ NEXT_STEP=$(echo "$CHECKPOINT" | cut -d'|' -f9)
37
+ COMPACT_COUNT=$(echo "$CHECKPOINT" | cut -d'|' -f10)
38
+
39
+ # Increment compaction count
40
+ sqlite3 "$NEXO_DB" "
41
+ UPDATE session_checkpoints
42
+ SET compaction_count = compaction_count + 1, updated_at = datetime('now')
43
+ WHERE sid = '$SID'
44
+ " 2>/dev/null || true
45
+
46
+ # Read diary draft for extra context
47
+ DRAFT=$(sqlite3 "$NEXO_DB" "
48
+ SELECT tasks_seen, last_context_hint
49
+ FROM session_diary_draft
50
+ WHERE sid = '$SID'
51
+ " 2>/dev/null || echo "")
52
+
53
+ TASKS_SEEN=""
54
+ LAST_HINT=""
55
+ if [ -n "$DRAFT" ]; then
56
+ TASKS_SEEN=$(echo "$DRAFT" | cut -d'|' -f1)
57
+ LAST_HINT=$(echo "$DRAFT" | cut -d'|' -f2)
58
+ fi
59
+
60
+ # Build Core Memory Block
61
+ BLOCK="## SESSION CONTINUITY [auto-injected post-compaction #$((COMPACT_COUNT + 1))]"
62
+ BLOCK="$BLOCK\n**Session:** $SID"
63
+ BLOCK="$BLOCK\n**Task:** $TASK (status: $TASK_STATUS)"
64
+
65
+ if [ -n "$CURRENT_GOAL" ] && [ "$CURRENT_GOAL" != "$TASK" ]; then
66
+ BLOCK="$BLOCK\n**Goal:** $CURRENT_GOAL"
67
+ fi
68
+
69
+ if [ -n "$ACTIVE_FILES" ] && [ "$ACTIVE_FILES" != "[]" ]; then
70
+ BLOCK="$BLOCK\n**Files:** $ACTIVE_FILES"
71
+ fi
72
+
73
+ if [ -n "$DECISIONS" ]; then
74
+ BLOCK="$BLOCK\n**Decisions:** $DECISIONS"
75
+ fi
76
+
77
+ if [ -n "$ERRORS" ]; then
78
+ BLOCK="$BLOCK\n**Errors:** $ERRORS"
79
+ fi
80
+
81
+ if [ -n "$NEXT_STEP" ]; then
82
+ BLOCK="$BLOCK\n**Next:** $NEXT_STEP"
83
+ fi
84
+
85
+ if [ -n "$REASONING" ]; then
86
+ BLOCK="$BLOCK\n**Context:** $REASONING"
87
+ fi
88
+
89
+ if [ -n "$LAST_HINT" ]; then
90
+ BLOCK="$BLOCK\n**Last context:** $LAST_HINT"
91
+ fi
92
+
93
+ if [ -n "$TASKS_SEEN" ] && [ "$TASKS_SEEN" != "[]" ]; then
94
+ BLOCK="$BLOCK\n**Session tasks so far:** $TASKS_SEEN"
95
+ fi
96
+
97
+ BLOCK="$BLOCK\n**Tool logs:** ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl ($LOG_LINES entries)"
98
+ BLOCK="$BLOCK\n\n**POST-COMPACTION INSTRUCTIONS:**"
99
+ BLOCK="$BLOCK\n1. Call nexo_heartbeat with the SID above to reconnect with the session"
100
+ BLOCK="$BLOCK\n2. If you need specific lost data, query tool logs with jq"
101
+ BLOCK="$BLOCK\n3. Continue the task from where it left off — do NOT start from zero"
102
+ BLOCK="$BLOCK\n4. MCP tools (nexo_*) have all persistent state"
103
+
104
+ # Escape for JSON
105
+ BLOCK_ESCAPED=$(echo -e "$BLOCK" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
106
+
107
+ cat << HOOKEOF
108
+ {
109
+ "systemMessage": $BLOCK_ESCAPED
110
+ }
111
+ HOOKEOF
112
+ else
113
+ # No checkpoint — fallback to basic message
114
+ cat << 'HOOKEOF'
115
+ {
116
+ "systemMessage": "Post-compaction: no prior checkpoint found. Call nexo_heartbeat to reconnect session state."
117
+ }
118
+ HOOKEOF
119
+ fi
120
+ else
121
+ cat << 'HOOKEOF'
122
+ {
123
+ "systemMessage": "Post-compaction: nexo.db not found. Reconnect via nexo_startup."
124
+ }
125
+ HOOKEOF
126
+ fi
@@ -1,65 +1,47 @@
1
1
  #!/bin/bash
2
- # NEXO PreCompact hooksaves context before Claude Code compacts the conversation.
3
- # Compaction loses context silently. This hook ensures the operator writes a checkpoint
4
- # before that happens, and saves the last known state to a recovery file.
2
+ # NEXO PreCompact HookSave checkpoint + inject preservation instructions
3
+ # This runs BEFORE Claude Code compacts. It:
4
+ # 1. Enriches the session checkpoint in SQLite with latest diary draft data
5
+ # 2. Injects a systemMessage telling the operator to save any WIP via MCP tools
5
6
  set -euo pipefail
6
7
 
7
8
  NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
8
- NEXO_NAME="${NEXO_NAME:-NEXO}"
9
- CHECKPOINT_FILE="$NEXO_HOME/coordination/pre-compact-checkpoint.json"
9
+ NEXO_DB="$NEXO_HOME/nexo.db"
10
+ TODAY=$(date +%Y-%m-%d)
11
+ LOG_FILE="$NEXO_HOME/operations/tool-logs/${TODAY}.jsonl"
12
+ LOG_LINES=0
13
+ if [ -f "$LOG_FILE" ]; then
14
+ LOG_LINES=$(wc -l < "$LOG_FILE" | tr -d ' ')
15
+ fi
16
+
17
+ # Enrich checkpoint: copy diary draft context into checkpoint if exists
18
+ if [ -f "$NEXO_DB" ]; then
19
+ # Get latest active session's diary draft
20
+ LATEST_SID=$(sqlite3 "$NEXO_DB" "
21
+ SELECT sid FROM sessions ORDER BY last_update_epoch DESC LIMIT 1
22
+ " 2>/dev/null || echo "")
23
+
24
+ if [ -n "$LATEST_SID" ]; then
25
+ # Pull diary draft data into checkpoint
26
+ sqlite3 "$NEXO_DB" "
27
+ INSERT INTO session_checkpoints (sid, task, current_goal, updated_at)
28
+ SELECT s.sid, s.task, COALESCE(d.last_context_hint, s.task), datetime('now')
29
+ FROM sessions s
30
+ LEFT JOIN session_diary_draft d ON d.sid = s.sid
31
+ WHERE s.sid = '$LATEST_SID'
32
+ ON CONFLICT(sid) DO UPDATE SET
33
+ task = excluded.task,
34
+ current_goal = CASE
35
+ WHEN excluded.current_goal != '' THEN excluded.current_goal
36
+ ELSE session_checkpoints.current_goal
37
+ END,
38
+ updated_at = datetime('now')
39
+ " 2>/dev/null || true
40
+ fi
41
+ fi
10
42
 
11
- mkdir -p "$NEXO_HOME/coordination"
12
-
13
- # Save current state to checkpoint file
14
- python3 -c "
15
- import json, os, sys
16
- from datetime import datetime
17
-
18
- nexo_home = os.environ.get('NEXO_HOME', os.path.expanduser('~/.nexo'))
19
- db_path = os.path.join(nexo_home, 'nexo.db')
20
-
21
- checkpoint = {
22
- 'timestamp': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S'),
23
- 'active_sessions': [],
24
- 'last_context_hints': [],
25
- }
26
-
27
- try:
28
- import sqlite3
29
- if os.path.exists(db_path):
30
- db = sqlite3.connect(db_path, timeout=5)
31
- db.row_factory = sqlite3.Row
32
- sessions = db.execute(
33
- 'SELECT sid, task, started FROM sessions WHERE completed=0'
34
- ).fetchall()
35
- checkpoint['active_sessions'] = [
36
- {'sid': s['sid'], 'task': s['task'], 'started': s['started']}
37
- for s in sessions
38
- ]
39
- # Get last diary drafts for context
40
- try:
41
- drafts = db.execute(
42
- 'SELECT sid, last_context_hint, tasks_seen FROM session_diary_draft '
43
- 'ORDER BY updated_at DESC LIMIT 3'
44
- ).fetchall()
45
- checkpoint['last_context_hints'] = [
46
- {'sid': d['sid'], 'hint': d['last_context_hint'], 'tasks': d['tasks_seen']}
47
- for d in drafts
48
- ]
49
- except Exception:
50
- pass
51
- db.close()
52
- except Exception:
53
- pass
54
-
55
- with open('$CHECKPOINT_FILE', 'w') as f:
56
- json.dump(checkpoint, f, indent=2)
57
- " 2>/dev/null || true
58
-
59
- # Emit hook response with systemMessage
60
43
  cat << HOOKEOF
61
44
  {
62
- "decision": "approve",
63
- "systemMessage": "PRE-COMPACT HOOK — Context is about to be compressed. BEFORE continuing:\n\n1. **Write a diary draft NOW** — call nexo_session_diary_write with what you've done so far, decisions made, and current mental state. This is your lifeline after compaction.\n2. **Note your current task** — after compaction you may lose the thread. Write it down in the diary.\n3. **Check pending followups** — if you promised to do something, make sure it's recorded before context is lost.\n4. **Read the checkpoint** after compaction: ${NEXO_HOME}/coordination/pre-compact-checkpoint.json\n\nDo NOT skip this. Compaction without a diary = starting from zero."
45
+ "systemMessage": "CONTEXT IS ABOUT TO BE COMPRESSED.\n\nOBLIGATORY ACTIONS BEFORE COMPACTION:\n1. Save critical state via MCP: nexo_checkpoint_save with current task, active files, decisions, errors, next step, and reasoning thread.\n2. If there is work in progress without a commit, save data via nexo_entity_create, nexo_preference_set, nexo_learning_add, nexo_followup_create.\n3. PERSISTENT TOOL LOGS: ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl has ${LOG_LINES} entries.\n4. After compaction, the PostCompact hook will re-inject a Core Memory Block with the checkpoint.\n5. MCP tools (nexo_*) preserve all state — use them to recover context."
64
46
  }
65
47
  HOOKEOF
package/src/server.py CHANGED
@@ -126,6 +126,77 @@ def nexo_smart_startup() -> str:
126
126
  return handle_smart_startup_query()
127
127
 
128
128
 
129
+ # ── Session Checkpoints (auto-compaction continuity) ──────────────
130
+
131
+ @mcp.tool
132
+ def nexo_checkpoint_save(
133
+ sid: str,
134
+ task: str = '',
135
+ task_status: str = 'active',
136
+ active_files: str = '[]',
137
+ current_goal: str = '',
138
+ decisions_summary: str = '',
139
+ errors_found: str = '',
140
+ reasoning_thread: str = '',
141
+ next_step: str = ''
142
+ ) -> str:
143
+ """Save a session checkpoint for auto-compaction continuity.
144
+
145
+ Call this BEFORE context compaction to preserve session state.
146
+ The PostCompact hook reads this checkpoint and re-injects it as a
147
+ Core Memory Block, so the session continues seamlessly.
148
+
149
+ Args:
150
+ sid: Session ID.
151
+ task: Current task description.
152
+ task_status: One of 'active', 'investigating', 'fixing', 'deploying', 'blocked'.
153
+ active_files: JSON array of file paths currently being worked on.
154
+ current_goal: What you're trying to achieve right now (1-2 sentences).
155
+ decisions_summary: Recent decisions with brief reasoning (2-3 lines).
156
+ errors_found: Errors encountered and their status (resolved/open).
157
+ reasoning_thread: Your current chain of thought (1-2 sentences).
158
+ next_step: The concrete next action to take.
159
+ """
160
+ from db import save_checkpoint
161
+ result = save_checkpoint(
162
+ sid=sid, task=task, task_status=task_status,
163
+ active_files=active_files, current_goal=current_goal,
164
+ decisions_summary=decisions_summary, errors_found=errors_found,
165
+ reasoning_thread=reasoning_thread, next_step=next_step,
166
+ )
167
+ return f"Checkpoint saved for {sid}. Compaction #{result['compaction_count']}. PostCompact will re-inject this as Core Memory Block."
168
+
169
+
170
+ @mcp.tool
171
+ def nexo_checkpoint_read(sid: str = '') -> str:
172
+ """Read the latest session checkpoint. Used by PostCompact hook and for manual recovery.
173
+
174
+ Args:
175
+ sid: Session ID. If empty, returns the most recent checkpoint from any session.
176
+ """
177
+ from db import read_checkpoint
178
+ cp = read_checkpoint(sid)
179
+ if not cp:
180
+ return "No checkpoint found."
181
+
182
+ lines = [f"CHECKPOINT for {cp['sid']} (compaction #{cp['compaction_count']})"]
183
+ lines.append(f"Task: {cp['task']} ({cp['task_status']})")
184
+ if cp.get('current_goal'):
185
+ lines.append(f"Goal: {cp['current_goal']}")
186
+ if cp.get('active_files') and cp['active_files'] != '[]':
187
+ lines.append(f"Files: {cp['active_files']}")
188
+ if cp.get('decisions_summary'):
189
+ lines.append(f"Decisions: {cp['decisions_summary']}")
190
+ if cp.get('errors_found'):
191
+ lines.append(f"Errors: {cp['errors_found']}")
192
+ if cp.get('reasoning_thread'):
193
+ lines.append(f"Context: {cp['reasoning_thread']}")
194
+ if cp.get('next_step'):
195
+ lines.append(f"Next: {cp['next_step']}")
196
+ lines.append(f"Updated: {cp['updated_at']}")
197
+ return "\n".join(lines)
198
+
199
+
129
200
  # ── File coordination (3 tools) ───────────────────────────────────
130
201
 
131
202
  @mcp.tool
@@ -8,6 +8,7 @@ from db import (
8
8
  get_active_sessions, clean_stale_sessions, search_sessions,
9
9
  get_inbox, get_pending_questions, now_epoch,
10
10
  SESSION_STALE_SECONDS, check_session_has_diary,
11
+ save_checkpoint, read_checkpoint, increment_compaction_count,
11
12
  )
12
13
 
13
14
  # ── Session Keepalive ────────────────────────────────────────────────
@@ -317,6 +318,16 @@ def handle_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
317
318
  except Exception:
318
319
  pass # Draft accumulation is best-effort, never block heartbeat
319
320
 
321
+ # Update session checkpoint with current goal (lightweight, every heartbeat)
322
+ try:
323
+ save_checkpoint(
324
+ sid=sid,
325
+ task=task,
326
+ current_goal=context_hint[:300] if context_hint else task,
327
+ )
328
+ except Exception:
329
+ pass # Checkpoint update is best-effort
330
+
320
331
  # Diary reminder: after 30 min active with no diary entry
321
332
  conn = get_db()
322
333
  row = conn.execute("SELECT started_epoch FROM sessions WHERE sid = ?", (sid,)).fetchone()