nexo-brain 0.10.0-beta.8 → 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 +77 -13
- package/package.json +3 -3
- package/src/db.py +93 -0
- package/src/hooks/post-compact.sh +126 -0
- package/src/hooks/pre-compact.sh +38 -56
- package/src/plugins/cortex.py +299 -0
- package/src/rules/core-rules.json +3 -1
- package/src/server.py +71 -0
- package/src/tools_sessions.py +11 -0
- package/templates/CLAUDE.md.template +22 -0
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# NEXO Brain — Your AI Gets a Brain
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/nexo-brain)
|
|
4
4
|
[](https://github.com/wazionapps/nexo/blob/main/benchmarks/locomo/results/)
|
|
5
5
|
[](https://github.com/snap-research/locomo/issues/33)
|
|
6
6
|
[](https://github.com/wazionapps/nexo/stargazers)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
-
> **
|
|
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
|
|
|
@@ -136,9 +136,69 @@ Like a human brain, NEXO Brain has automated processes that run while you're not
|
|
|
136
136
|
|
|
137
137
|
If your Mac was asleep during any scheduled process, NEXO Brain catches up in order when it wakes.
|
|
138
138
|
|
|
139
|
+
## Cognitive Cortex (v1.0.0)
|
|
140
|
+
|
|
141
|
+
The Cortex is a middleware cognitive layer that makes the agent **think before acting**. It implements architectural inhibitory control — the agent cannot bypass reasoning.
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
User message → Fast Path check → Simple chat? → Respond directly
|
|
145
|
+
→ Action needed? → Cortex activates
|
|
146
|
+
↓
|
|
147
|
+
Generate cognitive state
|
|
148
|
+
(goal, plan, unknowns, evidence)
|
|
149
|
+
↓
|
|
150
|
+
Middleware validates
|
|
151
|
+
├─ Unknowns? → ASK mode (tools blocked)
|
|
152
|
+
├─ No plan? → PROPOSE mode (read-only)
|
|
153
|
+
└─ Plan + evidence → ACT mode (full access)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
| Feature | What It Does |
|
|
157
|
+
|---------|-------------|
|
|
158
|
+
| **Inhibitory Control** | Physically restricts tools based on reasoning quality. Unknowns → can only ask. No plan → can only propose. Evidence + verification → can act. |
|
|
159
|
+
| **Event-Driven Activation** | Only activates on tool intent, ambiguity, destructive actions, or retries. Simple chat has zero overhead. |
|
|
160
|
+
| **Trust-Gated Escalation** | Low trust score → requires more evidence before allowing "act" mode. Trust builds through successful execution. |
|
|
161
|
+
| **Core Rules Injection** | Automatically surfaces relevant behavioral rules based on task type. |
|
|
162
|
+
| **Activation Metrics** | Tracks modes, inhibition rates, and task types for continuous improvement. |
|
|
163
|
+
|
|
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
|
+
|
|
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
|
+
|
|
139
199
|
## Cognitive Features
|
|
140
200
|
|
|
141
|
-
NEXO Brain provides
|
|
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:
|
|
142
202
|
|
|
143
203
|
### Input Pipeline
|
|
144
204
|
|
|
@@ -211,7 +271,7 @@ Full results in [`benchmarks/locomo/results/`](benchmarks/locomo/results/).
|
|
|
211
271
|
|
|
212
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.
|
|
213
273
|
|
|
214
|
-
###
|
|
274
|
+
### 6 Automated Hooks
|
|
215
275
|
|
|
216
276
|
These fire automatically at key moments in every Claude Code session:
|
|
217
277
|
|
|
@@ -220,7 +280,8 @@ These fire automatically at key moments in every Claude Code session:
|
|
|
220
280
|
| **SessionStart** | Session opens | Generates a briefing from SQLite: overdue reminders, today's tasks, pending followups, active sessions |
|
|
221
281
|
| **Stop** | Session ends | Mandatory post-mortem: self-critique (5 questions), session buffer entry, followup creation, proactive seeds for next session |
|
|
222
282
|
| **PostToolUse** | After each tool call | Captures meaningful mutations to the Sensory Register |
|
|
223
|
-
| **PreCompact** | Before context compression | Saves
|
|
283
|
+
| **PreCompact** | Before context compression | Saves full session checkpoint to SQLite — task, 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 |
|
|
224
285
|
| **Caffeinate** | Always (optional) | Keeps Mac awake for nocturnal cognitive processes |
|
|
225
286
|
|
|
226
287
|
### The Session Lifecycle
|
|
@@ -236,7 +297,9 @@ Heartbeat on every interaction (sentiment, context shifts)
|
|
|
236
297
|
↓
|
|
237
298
|
Guard check before every code edit
|
|
238
299
|
↓
|
|
239
|
-
PreCompact hook saves
|
|
300
|
+
PreCompact hook saves full checkpoint if conversation is compressed
|
|
301
|
+
↓
|
|
302
|
+
PostCompact hook re-injects Core Memory Block → session continues seamlessly
|
|
240
303
|
↓
|
|
241
304
|
Stop hook triggers mandatory post-mortem:
|
|
242
305
|
- Self-critique: 5 questions about what could be better
|
|
@@ -319,7 +382,7 @@ The installer handles everything:
|
|
|
319
382
|
- Node.js project detected
|
|
320
383
|
Configuring MCP server...
|
|
321
384
|
Setting up automated processes...
|
|
322
|
-
|
|
385
|
+
6 automated processes configured.
|
|
323
386
|
Caffeinate enabled.
|
|
324
387
|
Generating operator instructions...
|
|
325
388
|
|
|
@@ -343,25 +406,25 @@ That's it. No need to run `claude` manually. Atlas will greet you immediately
|
|
|
343
406
|
| Component | What | Where |
|
|
344
407
|
|-----------|------|-------|
|
|
345
408
|
| Cognitive engine | Python: fastembed, numpy, vector search | pip packages |
|
|
346
|
-
| MCP server |
|
|
409
|
+
| MCP server | 111+ tools for memory, cognition, learning, guard | ~/.nexo/ |
|
|
347
410
|
| Plugins | Guard, episodic memory, cognitive memory, entities, preferences | ~/.nexo/plugins/ |
|
|
348
|
-
| Hooks (
|
|
411
|
+
| Hooks (6) | SessionStart briefing, Stop post-mortem, PostToolUse capture, PreCompact checkpoint, PostCompact recovery, Caffeinate | ~/.nexo/hooks/ |
|
|
349
412
|
| Reflection engine | Processes session buffer, extracts patterns, updates user model | ~/.nexo/scripts/ |
|
|
350
413
|
| CLAUDE.md | Complete operator instructions (Codex, hooks, guard, trust, memory) | ~/.claude/CLAUDE.md |
|
|
351
414
|
| LaunchAgents | Decay, sleep, audit, postmortem, catch-up | ~/Library/LaunchAgents/ |
|
|
352
415
|
| Auto-update | Checks for new versions at boot | Built into catch-up |
|
|
353
|
-
| Claude Code config | MCP server +
|
|
416
|
+
| Claude Code config | MCP server + 6 hooks registered | ~/.claude/settings.json |
|
|
354
417
|
|
|
355
418
|
### Requirements
|
|
356
419
|
|
|
357
420
|
- **macOS or Linux** (Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))
|
|
358
421
|
- **Node.js 18+** (for the installer)
|
|
359
|
-
- **Claude Opus (latest version) strongly recommended.** NEXO Brain provides
|
|
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.
|
|
360
423
|
- Python 3, Homebrew, and Claude Code are installed automatically if missing.
|
|
361
424
|
|
|
362
425
|
## Architecture
|
|
363
426
|
|
|
364
|
-
###
|
|
427
|
+
### 111+ MCP Tools across 20 Categories
|
|
365
428
|
|
|
366
429
|
| Category | Count | Tools | Purpose |
|
|
367
430
|
|----------|-------|-------|---------|
|
|
@@ -385,6 +448,7 @@ That's it. No need to run `claude` manually. Atlas will greet you immediately
|
|
|
385
448
|
| Evolution | 5 | propose, approve, reject, status, history | Self-improvement proposals |
|
|
386
449
|
| Adaptive & Somatic | 4 | adaptive_weights, adaptive_override, somatic_check, somatic_stats | Learned signal weights + pain memory per file |
|
|
387
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 |
|
|
388
452
|
|
|
389
453
|
### Plugin System
|
|
390
454
|
|
|
@@ -442,7 +506,7 @@ NEXO Brain is designed as an MCP server. Claude Code is the primary supported cl
|
|
|
442
506
|
npx nexo-brain
|
|
443
507
|
```
|
|
444
508
|
|
|
445
|
-
All
|
|
509
|
+
All 111+ tools are available immediately after installation. The installer configures Claude Code's `~/.claude/settings.json` automatically.
|
|
446
510
|
|
|
447
511
|
### OpenClaw
|
|
448
512
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
|
-
"description": "NEXO
|
|
5
|
+
"description": "NEXO \u2014 Cognitive co-operator for Claude Code. Atkinson-Shiffrin memory, semantic RAG, trust scoring, and metacognitive error prevention.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nexo-brain": "./bin/nexo-brain.js"
|
|
8
8
|
},
|
|
@@ -32,4 +32,4 @@
|
|
|
32
32
|
"templates/",
|
|
33
33
|
"scripts/"
|
|
34
34
|
]
|
|
35
|
-
}
|
|
35
|
+
}
|
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
|
package/src/hooks/pre-compact.sh
CHANGED
|
@@ -1,65 +1,47 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# NEXO PreCompact
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
# NEXO PreCompact Hook — Save 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
|
-
|
|
9
|
-
|
|
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
|
-
"
|
|
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
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Cognitive Cortex plugin — middleware cognitive layer for NEXO Brain.
|
|
2
|
+
|
|
3
|
+
Provides structured pre-action reasoning with architectural inhibitory control.
|
|
4
|
+
The Cortex does NOT generate answers — it gates, plans, and validates actions.
|
|
5
|
+
|
|
6
|
+
Activation: event-driven, not on every turn. Only on:
|
|
7
|
+
- Tool intent (edit, execute, delegate)
|
|
8
|
+
- Ambiguity in user request
|
|
9
|
+
- Destructive actions
|
|
10
|
+
- Multi-step tasks
|
|
11
|
+
- Retry after failure
|
|
12
|
+
- Contradictions with known facts
|
|
13
|
+
|
|
14
|
+
v0.1: Single MCP tool + middleware validation.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_db():
|
|
22
|
+
from db import get_db
|
|
23
|
+
return get_db()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_core_rules_for_task(task_type: str) -> list[str]:
|
|
27
|
+
"""Get relevant Core Rules for the given task type."""
|
|
28
|
+
conn = _get_db()
|
|
29
|
+
try:
|
|
30
|
+
# Map task type to rule categories
|
|
31
|
+
category_map = {
|
|
32
|
+
"edit": ["integrity", "execution"],
|
|
33
|
+
"execute": ["integrity", "execution", "delegation"],
|
|
34
|
+
"delegate": ["delegation"],
|
|
35
|
+
"analyze": ["execution", "memory"],
|
|
36
|
+
"answer": ["communication"],
|
|
37
|
+
}
|
|
38
|
+
categories = category_map.get(task_type, ["integrity", "execution"])
|
|
39
|
+
placeholders = ",".join("?" * len(categories))
|
|
40
|
+
|
|
41
|
+
rows = conn.execute(
|
|
42
|
+
f"SELECT id, rule FROM core_rules WHERE category IN ({placeholders}) AND is_active = 1 AND type = 'blocking' ORDER BY importance DESC LIMIT 5",
|
|
43
|
+
categories
|
|
44
|
+
).fetchall()
|
|
45
|
+
return [f"{r['id']}: {r['rule']}" for r in rows]
|
|
46
|
+
except Exception:
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_trust_score() -> float:
|
|
51
|
+
"""Get current trust score from cognitive.db."""
|
|
52
|
+
try:
|
|
53
|
+
import cognitive
|
|
54
|
+
return cognitive.get_trust_score()
|
|
55
|
+
except Exception:
|
|
56
|
+
return 50.0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _validate_state(state: dict) -> dict:
|
|
60
|
+
"""Validate cognitive state and determine action mode.
|
|
61
|
+
|
|
62
|
+
Returns dict with: mode, warnings, injected_rules, blocked_reason
|
|
63
|
+
"""
|
|
64
|
+
warnings = []
|
|
65
|
+
mode = "act" # default: allow action
|
|
66
|
+
blocked_reason = None
|
|
67
|
+
|
|
68
|
+
task_type = state.get("task_type", "answer")
|
|
69
|
+
plan = state.get("plan", [])
|
|
70
|
+
unknowns = state.get("unknowns", [])
|
|
71
|
+
evidence = state.get("evidence_refs", [])
|
|
72
|
+
verification = state.get("verification_step", "")
|
|
73
|
+
constraints = state.get("constraints", [])
|
|
74
|
+
goal = state.get("goal", "")
|
|
75
|
+
|
|
76
|
+
# === INHIBITION RULES (architectural, not advisory) ===
|
|
77
|
+
|
|
78
|
+
# Rule 1: unknowns exist → force ASK mode
|
|
79
|
+
if unknowns:
|
|
80
|
+
mode = "ask"
|
|
81
|
+
blocked_reason = f"Cannot act with {len(unknowns)} unknown(s). Resolve first."
|
|
82
|
+
warnings.append(f"UNKNOWNS: {', '.join(unknowns[:3])}")
|
|
83
|
+
|
|
84
|
+
# Rule 2: edit/execute without plan → force PROPOSE
|
|
85
|
+
if task_type in ("edit", "execute", "delegate") and not plan and mode == "act":
|
|
86
|
+
mode = "propose"
|
|
87
|
+
blocked_reason = "No plan defined for action task. Propose plan first."
|
|
88
|
+
warnings.append("MISSING PLAN: define steps before executing")
|
|
89
|
+
|
|
90
|
+
# Rule 3: edit/execute without verification → force PROPOSE
|
|
91
|
+
if task_type in ("edit", "execute") and not verification and mode == "act":
|
|
92
|
+
mode = "propose"
|
|
93
|
+
blocked_reason = "No verification step. How will you confirm it worked?"
|
|
94
|
+
warnings.append("MISSING VERIFICATION: define how to verify")
|
|
95
|
+
|
|
96
|
+
# Rule 4: execute without evidence → force PROPOSE
|
|
97
|
+
if task_type == "execute" and not evidence and mode == "act":
|
|
98
|
+
mode = "propose"
|
|
99
|
+
blocked_reason = "No evidence supporting this action."
|
|
100
|
+
warnings.append("MISSING EVIDENCE: what supports this action?")
|
|
101
|
+
|
|
102
|
+
# Rule 5: no goal → force ASK
|
|
103
|
+
if not goal:
|
|
104
|
+
mode = "ask"
|
|
105
|
+
blocked_reason = "No goal defined."
|
|
106
|
+
warnings.append("NO GOAL: what are you trying to achieve?")
|
|
107
|
+
|
|
108
|
+
# === TRUST-BASED ADJUSTMENTS ===
|
|
109
|
+
trust = _get_trust_score()
|
|
110
|
+
if trust < 30 and mode == "act" and task_type in ("edit", "execute"):
|
|
111
|
+
mode = "propose"
|
|
112
|
+
blocked_reason = f"Trust score {trust:.0f}/100 — propose before acting."
|
|
113
|
+
warnings.append(f"LOW TRUST ({trust:.0f}): extra verification required")
|
|
114
|
+
|
|
115
|
+
# === INJECT RELEVANT RULES ===
|
|
116
|
+
rules = _get_core_rules_for_task(task_type)
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
"mode": mode,
|
|
120
|
+
"tools_available": _tools_for_mode(mode),
|
|
121
|
+
"warnings": warnings,
|
|
122
|
+
"blocked_reason": blocked_reason,
|
|
123
|
+
"injected_rules": rules,
|
|
124
|
+
"trust_score": round(trust),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _tools_for_mode(mode: str) -> list[str]:
|
|
129
|
+
"""Define which tool categories are available per mode."""
|
|
130
|
+
if mode == "ask":
|
|
131
|
+
return ["read", "search", "ask_user"]
|
|
132
|
+
elif mode == "propose":
|
|
133
|
+
return ["read", "search", "analyze", "propose_plan"]
|
|
134
|
+
else: # act
|
|
135
|
+
return ["all"]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def handle_cortex_check(
|
|
139
|
+
goal: str,
|
|
140
|
+
task_type: str = "answer",
|
|
141
|
+
plan: str = "[]",
|
|
142
|
+
known_facts: str = "[]",
|
|
143
|
+
unknowns: str = "[]",
|
|
144
|
+
constraints: str = "[]",
|
|
145
|
+
evidence_refs: str = "[]",
|
|
146
|
+
verification_step: str = "",
|
|
147
|
+
) -> str:
|
|
148
|
+
"""Cognitive Cortex pre-action check. Call BEFORE significant actions.
|
|
149
|
+
|
|
150
|
+
Validates your reasoning state and determines if you can act, should propose,
|
|
151
|
+
or need to ask for clarification first. Implements architectural inhibitory control.
|
|
152
|
+
|
|
153
|
+
WHEN TO CALL:
|
|
154
|
+
- Before editing files or running commands
|
|
155
|
+
- Before delegating to subagents
|
|
156
|
+
- When the task has multiple possible approaches
|
|
157
|
+
- After a failed attempt (before retrying)
|
|
158
|
+
- When user instruction seems to conflict with known facts
|
|
159
|
+
|
|
160
|
+
DO NOT CALL for simple chat responses, greetings, or explanations.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
goal: What you are trying to achieve (required)
|
|
164
|
+
task_type: One of: answer, analyze, edit, execute, delegate
|
|
165
|
+
plan: JSON array of planned steps (e.g. '["read file", "edit function", "test"]')
|
|
166
|
+
known_facts: JSON array of facts you have (from user, memory, files)
|
|
167
|
+
unknowns: JSON array of things you don't know yet but need
|
|
168
|
+
constraints: JSON array of rules or limitations that apply
|
|
169
|
+
evidence_refs: JSON array of evidence supporting your plan (learnings, user statements, file contents)
|
|
170
|
+
verification_step: How you will verify the action worked
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Mode (ask/propose/act), available tools, warnings, and relevant Core Rules
|
|
174
|
+
"""
|
|
175
|
+
# Parse JSON arrays safely
|
|
176
|
+
def _parse(s):
|
|
177
|
+
try:
|
|
178
|
+
v = json.loads(s) if isinstance(s, str) else s
|
|
179
|
+
return v if isinstance(v, list) else []
|
|
180
|
+
except (json.JSONDecodeError, TypeError):
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
state = {
|
|
184
|
+
"goal": goal.strip() if goal else "",
|
|
185
|
+
"task_type": task_type if task_type in ("answer", "analyze", "edit", "execute", "delegate") else "answer",
|
|
186
|
+
"plan": _parse(plan),
|
|
187
|
+
"known_facts": _parse(known_facts),
|
|
188
|
+
"unknowns": _parse(unknowns),
|
|
189
|
+
"constraints": _parse(constraints),
|
|
190
|
+
"evidence_refs": _parse(evidence_refs),
|
|
191
|
+
"verification_step": verification_step.strip() if verification_step else "",
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
result = _validate_state(state)
|
|
195
|
+
|
|
196
|
+
# Format response
|
|
197
|
+
lines = [
|
|
198
|
+
f"CORTEX CHECK — mode: {result['mode'].upper()}",
|
|
199
|
+
f"Trust: {result['trust_score']}/100",
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
if result["mode"] == "act":
|
|
203
|
+
lines.append("CLEARED: You may proceed with the action.")
|
|
204
|
+
elif result["mode"] == "propose":
|
|
205
|
+
lines.append(f"PROPOSE ONLY: {result['blocked_reason']}")
|
|
206
|
+
lines.append("Show the user your plan and get approval before executing.")
|
|
207
|
+
elif result["mode"] == "ask":
|
|
208
|
+
lines.append(f"ASK FIRST: {result['blocked_reason']}")
|
|
209
|
+
lines.append("Gather the missing information before proceeding.")
|
|
210
|
+
|
|
211
|
+
if result["warnings"]:
|
|
212
|
+
lines.append("")
|
|
213
|
+
lines.append("Warnings:")
|
|
214
|
+
for w in result["warnings"]:
|
|
215
|
+
lines.append(f" - {w}")
|
|
216
|
+
|
|
217
|
+
if result["injected_rules"]:
|
|
218
|
+
lines.append("")
|
|
219
|
+
lines.append("Applicable Core Rules:")
|
|
220
|
+
for r in result["injected_rules"]:
|
|
221
|
+
lines.append(f" - {r}")
|
|
222
|
+
|
|
223
|
+
lines.append("")
|
|
224
|
+
lines.append(f"Tools available: {', '.join(result['tools_available'])}")
|
|
225
|
+
|
|
226
|
+
# Log cortex activation for metrics
|
|
227
|
+
try:
|
|
228
|
+
conn = _get_db()
|
|
229
|
+
conn.execute(
|
|
230
|
+
"""CREATE TABLE IF NOT EXISTS cortex_log (
|
|
231
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
232
|
+
goal TEXT,
|
|
233
|
+
task_type TEXT,
|
|
234
|
+
mode TEXT,
|
|
235
|
+
warnings TEXT,
|
|
236
|
+
trust_score INTEGER,
|
|
237
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
238
|
+
)"""
|
|
239
|
+
)
|
|
240
|
+
conn.execute(
|
|
241
|
+
"INSERT INTO cortex_log (goal, task_type, mode, warnings, trust_score) VALUES (?, ?, ?, ?, ?)",
|
|
242
|
+
(goal[:200], task_type, result["mode"], json.dumps(result["warnings"]), result["trust_score"])
|
|
243
|
+
)
|
|
244
|
+
conn.commit()
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
return "\n".join(lines)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def handle_cortex_stats(days: int = 7) -> str:
|
|
252
|
+
"""View Cortex activation statistics — how often it activates, modes, warnings.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
days: Period to analyze (default 7)
|
|
256
|
+
"""
|
|
257
|
+
conn = _get_db()
|
|
258
|
+
try:
|
|
259
|
+
conn.execute("SELECT 1 FROM cortex_log LIMIT 1")
|
|
260
|
+
except Exception:
|
|
261
|
+
return "No Cortex data yet. The Cortex activates on significant actions."
|
|
262
|
+
|
|
263
|
+
cutoff = f"datetime('now', '-{days} days')"
|
|
264
|
+
|
|
265
|
+
total = conn.execute(f"SELECT COUNT(*) FROM cortex_log WHERE created_at >= {cutoff}").fetchone()[0]
|
|
266
|
+
by_mode = conn.execute(
|
|
267
|
+
f"SELECT mode, COUNT(*) as c FROM cortex_log WHERE created_at >= {cutoff} GROUP BY mode ORDER BY c DESC"
|
|
268
|
+
).fetchall()
|
|
269
|
+
by_type = conn.execute(
|
|
270
|
+
f"SELECT task_type, COUNT(*) as c FROM cortex_log WHERE created_at >= {cutoff} GROUP BY task_type ORDER BY c DESC"
|
|
271
|
+
).fetchall()
|
|
272
|
+
|
|
273
|
+
lines = [
|
|
274
|
+
f"CORTEX STATS — last {days} days",
|
|
275
|
+
f"Total activations: {total}",
|
|
276
|
+
"",
|
|
277
|
+
"By mode:",
|
|
278
|
+
]
|
|
279
|
+
for r in by_mode:
|
|
280
|
+
pct = (r["c"] / total * 100) if total > 0 else 0
|
|
281
|
+
lines.append(f" {r['mode']}: {r['c']} ({pct:.0f}%)")
|
|
282
|
+
|
|
283
|
+
lines.append("")
|
|
284
|
+
lines.append("By task type:")
|
|
285
|
+
for r in by_type:
|
|
286
|
+
lines.append(f" {r['task_type']}: {r['c']}")
|
|
287
|
+
|
|
288
|
+
# Inhibition rate = % of activations that resulted in ask or propose (not act)
|
|
289
|
+
inhibited = sum(r["c"] for r in by_mode if r["mode"] != "act")
|
|
290
|
+
inhibition_rate = (inhibited / total * 100) if total > 0 else 0
|
|
291
|
+
lines.append(f"\nInhibition rate: {inhibition_rate:.0f}% (target: 30-60%)")
|
|
292
|
+
|
|
293
|
+
return "\n".join(lines)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
TOOLS = [
|
|
297
|
+
(handle_cortex_check, "nexo_cortex_check", "Cognitive pre-action check. Validates reasoning and determines if you can act, should propose, or need to ask first. Call before significant actions."),
|
|
298
|
+
(handle_cortex_stats, "nexo_cortex_stats", "View Cortex activation statistics — modes, task types, inhibition rate."),
|
|
299
|
+
]
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"source": "Consolidated from 6 months production use + multi-AI debate (Claude Opus + GPT-4o)",
|
|
7
7
|
"total_rules": 30,
|
|
8
8
|
"blocking": 25,
|
|
9
|
-
"advisory": 5
|
|
9
|
+
"advisory": 5,
|
|
10
|
+
"immutable": true,
|
|
11
|
+
"immutable_note": "Core rules are the DNA of NEXO Brain. They CANNOT be deleted or modified by the user. Only the migration system (version updates from the creators) can add, modify, or remove rules. Users can configure behavioral intensity (autonomy, communication, proactivity) but not the rules themselves."
|
|
10
12
|
},
|
|
11
13
|
"categories": {
|
|
12
14
|
"integrity": {
|
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
|
package/src/tools_sessions.py
CHANGED
|
@@ -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()
|
|
@@ -58,6 +58,28 @@ _Subagents inherit zero session memory. Without context = guaranteed errors._
|
|
|
58
58
|
|
|
59
59
|
For the full 30 rules across 6 categories (Integrity, Execution, Memory, Delegation, Communication, Proactivity), call `nexo_rules_check`.
|
|
60
60
|
|
|
61
|
+
## Cognitive Cortex (Pre-Action Reasoning)
|
|
62
|
+
|
|
63
|
+
Before significant actions, call `nexo_cortex_check` to validate your reasoning. The Cortex determines if you can **act**, should **propose**, or need to **ask** first.
|
|
64
|
+
|
|
65
|
+
**WHEN to call `nexo_cortex_check`:**
|
|
66
|
+
- Before editing files or running commands
|
|
67
|
+
- Before delegating to subagents
|
|
68
|
+
- When the task has multiple possible approaches
|
|
69
|
+
- After a failed attempt (before retrying differently)
|
|
70
|
+
- When user instruction conflicts with known facts
|
|
71
|
+
|
|
72
|
+
**DO NOT call** for simple chat, greetings, explanations, or read-only queries.
|
|
73
|
+
|
|
74
|
+
**How it works:** You provide your goal, plan, knowns, unknowns, and evidence. The Cortex validates and returns:
|
|
75
|
+
- **ACT** — proceed with execution (all tools available)
|
|
76
|
+
- **PROPOSE** — show plan to user first (no write/execute tools)
|
|
77
|
+
- **ASK** — gather missing information first (read/search only)
|
|
78
|
+
|
|
79
|
+
The Cortex enforces inhibitory control architecturally — it physically restricts available tools based on your reasoning quality. This prevents premature action, hallucinated certainty, and unverified execution.
|
|
80
|
+
|
|
81
|
+
`nexo_cortex_stats` shows activation metrics and inhibition rates.
|
|
82
|
+
|
|
61
83
|
## Personality Calibration
|
|
62
84
|
|
|
63
85
|
Read `{{NEXO_HOME}}/brain/calibration.json` at startup to load user preferences. These override defaults:
|