nexo-brain 1.0.0 → 1.1.1

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
 
@@ -565,6 +602,35 @@ If NEXO Brain is useful to you, consider:
565
602
 
566
603
  [![Star History Chart](https://api.star-history.com/svg?repos=wazionapps/nexo&type=Date)](https://star-history.com/#wazionapps/nexo&Date)
567
604
 
605
+ ## Changelog
606
+
607
+ ### v1.1.0 — Context Continuity (2026-03-27)
608
+ - **Context Continuity**: PreCompact/PostCompact hooks preserve session state across compaction events
609
+ - New `session_checkpoints` SQLite table + migration #12
610
+ - New tools: `nexo_checkpoint_save`, `nexo_checkpoint_read`
611
+ - Heartbeat automatically maintains checkpoint every interaction
612
+ - Core Memory Block re-injected post-compaction with task, files, decisions, reasoning thread
613
+ - 115+ total tools, 20 categories, 6 hooks
614
+
615
+ ### v1.0.0 — Cognitive Cortex + Stable Release (2026-03-26)
616
+ - **Cognitive Cortex**: architectural inhibitory control (ASK/PROPOSE/ACT modes)
617
+ - 30 Core Rules as immutable DNA in SQLite
618
+ - Designed via 3-way AI debate (Claude Opus + GPT-5.4 + Gemini 3.1 Pro)
619
+ - Artifact Registry for operational facts
620
+ - Full benchmark suite (LoCoMo F1: 0.588)
621
+
622
+ ### v0.10.0 — Smart Context (2026-03-22)
623
+ - Smart Startup: pre-loads memories from pending followups + diary
624
+ - Context Packet: structured injection for subagents
625
+ - Auto-Prime: keyword-triggered area learnings in heartbeat
626
+ - Diary Archive: permanent subconscious memory (180d+ auto-archived)
627
+
628
+ ### v0.9.0 — Cognitive Memory (2026-03-15)
629
+ - Atkinson-Shiffrin memory model (STM → LTM promotion)
630
+ - Semantic RAG with fastembed (BAAI/bge-small-en-v1.5, 384 dims)
631
+ - Trust scoring, sentiment detection, adaptive personality modes
632
+ - Ebbinghaus decay, sister detection, quarantine system
633
+
568
634
  ## License
569
635
 
570
636
  MIT -- see [LICENSE](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
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,147 @@
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 checkpoint for the session that just compacted
17
+ # PreCompact writes the SID to /tmp/nexo-compacting-sid
18
+ TARGET_SID=""
19
+ if [ -f /tmp/nexo-compacting-sid ]; then
20
+ TARGET_SID=$(cat /tmp/nexo-compacting-sid 2>/dev/null || echo "")
21
+ rm -f /tmp/nexo-compacting-sid
22
+ fi
23
+
24
+ CHECKPOINT=""
25
+ if [ -f "$NEXO_DB" ]; then
26
+ if [ -n "$TARGET_SID" ]; then
27
+ # Read checkpoint for the specific session that compacted
28
+ CHECKPOINT=$(sqlite3 "$NEXO_DB" "
29
+ SELECT sid, task, task_status, active_files, current_goal,
30
+ decisions_summary, errors_found, reasoning_thread,
31
+ next_step, compaction_count
32
+ FROM session_checkpoints
33
+ WHERE sid = '$TARGET_SID'
34
+ " 2>/dev/null || echo "")
35
+ fi
36
+ # Fallback: if no target SID or no checkpoint found, use latest
37
+ if [ -z "$CHECKPOINT" ]; then
38
+ CHECKPOINT=$(sqlite3 "$NEXO_DB" "
39
+ SELECT sid, task, task_status, active_files, current_goal,
40
+ decisions_summary, errors_found, reasoning_thread,
41
+ next_step, compaction_count
42
+ FROM session_checkpoints
43
+ ORDER BY updated_at DESC LIMIT 1
44
+ " 2>/dev/null || echo "")
45
+ fi
46
+
47
+ if [ -n "$CHECKPOINT" ]; then
48
+ # Parse pipe-separated fields
49
+ SID=$(echo "$CHECKPOINT" | cut -d'|' -f1)
50
+ TASK=$(echo "$CHECKPOINT" | cut -d'|' -f2)
51
+ TASK_STATUS=$(echo "$CHECKPOINT" | cut -d'|' -f3)
52
+ ACTIVE_FILES=$(echo "$CHECKPOINT" | cut -d'|' -f4)
53
+ CURRENT_GOAL=$(echo "$CHECKPOINT" | cut -d'|' -f5)
54
+ DECISIONS=$(echo "$CHECKPOINT" | cut -d'|' -f6)
55
+ ERRORS=$(echo "$CHECKPOINT" | cut -d'|' -f7)
56
+ REASONING=$(echo "$CHECKPOINT" | cut -d'|' -f8)
57
+ NEXT_STEP=$(echo "$CHECKPOINT" | cut -d'|' -f9)
58
+ COMPACT_COUNT=$(echo "$CHECKPOINT" | cut -d'|' -f10)
59
+
60
+ # Increment compaction count
61
+ sqlite3 "$NEXO_DB" "
62
+ UPDATE session_checkpoints
63
+ SET compaction_count = compaction_count + 1, updated_at = datetime('now')
64
+ WHERE sid = '$SID'
65
+ " 2>/dev/null || true
66
+
67
+ # Read diary draft for extra context
68
+ DRAFT=$(sqlite3 "$NEXO_DB" "
69
+ SELECT tasks_seen, last_context_hint
70
+ FROM session_diary_draft
71
+ WHERE sid = '$SID'
72
+ " 2>/dev/null || echo "")
73
+
74
+ TASKS_SEEN=""
75
+ LAST_HINT=""
76
+ if [ -n "$DRAFT" ]; then
77
+ TASKS_SEEN=$(echo "$DRAFT" | cut -d'|' -f1)
78
+ LAST_HINT=$(echo "$DRAFT" | cut -d'|' -f2)
79
+ fi
80
+
81
+ # Build Core Memory Block
82
+ BLOCK="## SESSION CONTINUITY [auto-injected post-compaction #$((COMPACT_COUNT + 1))]"
83
+ BLOCK="$BLOCK\n**Session:** $SID"
84
+ BLOCK="$BLOCK\n**Task:** $TASK (status: $TASK_STATUS)"
85
+
86
+ if [ -n "$CURRENT_GOAL" ] && [ "$CURRENT_GOAL" != "$TASK" ]; then
87
+ BLOCK="$BLOCK\n**Goal:** $CURRENT_GOAL"
88
+ fi
89
+
90
+ if [ -n "$ACTIVE_FILES" ] && [ "$ACTIVE_FILES" != "[]" ]; then
91
+ BLOCK="$BLOCK\n**Files:** $ACTIVE_FILES"
92
+ fi
93
+
94
+ if [ -n "$DECISIONS" ]; then
95
+ BLOCK="$BLOCK\n**Decisions:** $DECISIONS"
96
+ fi
97
+
98
+ if [ -n "$ERRORS" ]; then
99
+ BLOCK="$BLOCK\n**Errors:** $ERRORS"
100
+ fi
101
+
102
+ if [ -n "$NEXT_STEP" ]; then
103
+ BLOCK="$BLOCK\n**Next:** $NEXT_STEP"
104
+ fi
105
+
106
+ if [ -n "$REASONING" ]; then
107
+ BLOCK="$BLOCK\n**Context:** $REASONING"
108
+ fi
109
+
110
+ if [ -n "$LAST_HINT" ]; then
111
+ BLOCK="$BLOCK\n**Last context:** $LAST_HINT"
112
+ fi
113
+
114
+ if [ -n "$TASKS_SEEN" ] && [ "$TASKS_SEEN" != "[]" ]; then
115
+ BLOCK="$BLOCK\n**Session tasks so far:** $TASKS_SEEN"
116
+ fi
117
+
118
+ BLOCK="$BLOCK\n**Tool logs:** ${NEXO_HOME}/operations/tool-logs/${TODAY}.jsonl ($LOG_LINES entries)"
119
+ BLOCK="$BLOCK\n\n**POST-COMPACTION INSTRUCTIONS:**"
120
+ BLOCK="$BLOCK\n1. Call nexo_heartbeat with the SID above to reconnect with the session"
121
+ BLOCK="$BLOCK\n2. If you need specific lost data, query tool logs with jq"
122
+ BLOCK="$BLOCK\n3. Continue the task from where it left off — do NOT start from zero"
123
+ BLOCK="$BLOCK\n4. MCP tools (nexo_*) have all persistent state"
124
+
125
+ # Escape for JSON
126
+ BLOCK_ESCAPED=$(echo -e "$BLOCK" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
127
+
128
+ cat << HOOKEOF
129
+ {
130
+ "systemMessage": $BLOCK_ESCAPED
131
+ }
132
+ HOOKEOF
133
+ else
134
+ # No checkpoint — fallback to basic message
135
+ cat << 'HOOKEOF'
136
+ {
137
+ "systemMessage": "Post-compaction: no prior checkpoint found. Call nexo_heartbeat to reconnect session state."
138
+ }
139
+ HOOKEOF
140
+ fi
141
+ else
142
+ cat << 'HOOKEOF'
143
+ {
144
+ "systemMessage": "Post-compaction: nexo.db not found. Reconnect via nexo_startup."
145
+ }
146
+ HOOKEOF
147
+ fi
@@ -1,65 +1,49 @@
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
+ # Write SID to temp file so PostCompact knows which session compacted
26
+ echo "$LATEST_SID" > /tmp/nexo-compacting-sid
27
+ # Pull diary draft data into checkpoint
28
+ sqlite3 "$NEXO_DB" "
29
+ INSERT INTO session_checkpoints (sid, task, current_goal, updated_at)
30
+ SELECT s.sid, s.task, COALESCE(d.last_context_hint, s.task), datetime('now')
31
+ FROM sessions s
32
+ LEFT JOIN session_diary_draft d ON d.sid = s.sid
33
+ WHERE s.sid = '$LATEST_SID'
34
+ ON CONFLICT(sid) DO UPDATE SET
35
+ task = excluded.task,
36
+ current_goal = CASE
37
+ WHEN excluded.current_goal != '' THEN excluded.current_goal
38
+ ELSE session_checkpoints.current_goal
39
+ END,
40
+ updated_at = datetime('now')
41
+ " 2>/dev/null || true
42
+ fi
43
+ fi
10
44
 
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
45
  cat << HOOKEOF
61
46
  {
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."
47
+ "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
48
  }
65
49
  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()