nexo-brain 0.3.4 → 0.3.5
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/package.json +1 -1
- package/src/cognitive.py +114 -6
- package/src/server.py +6 -5
- package/src/tools_sessions.py +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO — Cognitive co-operator for Claude Code. Atkinson-Shiffrin memory, semantic RAG, trust scoring, and metacognitive error prevention.",
|
|
6
6
|
"bin": {
|
package/src/cognitive.py
CHANGED
|
@@ -30,13 +30,13 @@ DISCRIMINATING_ENTITIES = {
|
|
|
30
30
|
# OS / Environment
|
|
31
31
|
"linux", "mac", "macos", "windows", "darwin", "ubuntu", "debian", "alpine",
|
|
32
32
|
# Platforms
|
|
33
|
-
"
|
|
33
|
+
"nexo", "other", "whatsapp", "chrome", "firefox",
|
|
34
34
|
# Languages / Runtimes
|
|
35
35
|
"python", "php", "javascript", "typescript", "node", "deno", "ruby",
|
|
36
36
|
# Versions
|
|
37
37
|
"v1", "v2", "v3", "v4", "v5", "5.6", "7.4", "8.0", "8.1", "8.2",
|
|
38
38
|
# Infrastructure
|
|
39
|
-
"
|
|
39
|
+
"vps", "local", "production", "staging",
|
|
40
40
|
# DB
|
|
41
41
|
"mysql", "sqlite", "postgresql", "postgres", "redis",
|
|
42
42
|
}
|
|
@@ -61,8 +61,8 @@ URGENCY_SIGNALS = {
|
|
|
61
61
|
"rápido", "ya", "ahora", "urgente", "asap", "inmediatamente", "corre",
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
# Trust score events
|
|
65
|
-
|
|
64
|
+
# Trust score events — default deltas (overridable via trust_event_config table)
|
|
65
|
+
_DEFAULT_TRUST_EVENTS = {
|
|
66
66
|
# Positive
|
|
67
67
|
"explicit_thanks": +3,
|
|
68
68
|
"delegation": +2, # the user delegates new task without micromanaging
|
|
@@ -77,6 +77,113 @@ TRUST_EVENTS = {
|
|
|
77
77
|
"forgot_followup": -4, # Forgot to mark followup or execute it
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
# Lazy-loaded from DB (trust_event_config table overrides defaults)
|
|
81
|
+
_trust_events_cache = None
|
|
82
|
+
_trust_events_cache_ts = 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_trust_events() -> dict:
|
|
86
|
+
"""Get trust events with deltas. DB overrides take priority over defaults."""
|
|
87
|
+
global _trust_events_cache, _trust_events_cache_ts
|
|
88
|
+
import time
|
|
89
|
+
now = time.time()
|
|
90
|
+
# Cache for 60s to avoid constant DB reads
|
|
91
|
+
if _trust_events_cache is not None and (now - _trust_events_cache_ts) < 60:
|
|
92
|
+
return _trust_events_cache
|
|
93
|
+
|
|
94
|
+
events = dict(_DEFAULT_TRUST_EVENTS)
|
|
95
|
+
try:
|
|
96
|
+
db = _get_db()
|
|
97
|
+
db.execute("""
|
|
98
|
+
CREATE TABLE IF NOT EXISTS trust_event_config (
|
|
99
|
+
event TEXT PRIMARY KEY,
|
|
100
|
+
delta REAL NOT NULL,
|
|
101
|
+
description TEXT DEFAULT '',
|
|
102
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
103
|
+
)
|
|
104
|
+
""")
|
|
105
|
+
rows = db.execute("SELECT event, delta FROM trust_event_config").fetchall()
|
|
106
|
+
for r in rows:
|
|
107
|
+
events[r[0]] = r[1]
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
_trust_events_cache = events
|
|
111
|
+
_trust_events_cache_ts = now
|
|
112
|
+
return events
|
|
113
|
+
|
|
114
|
+
# For backward compat — code that reads TRUST_EVENTS directly
|
|
115
|
+
TRUST_EVENTS = _DEFAULT_TRUST_EVENTS
|
|
116
|
+
|
|
117
|
+
# Auto-detection patterns for trust events from user text
|
|
118
|
+
# Each pattern: (event_name, keywords/phrases that trigger it, min_matches)
|
|
119
|
+
TRUST_AUTO_PATTERNS = {
|
|
120
|
+
"explicit_thanks": {
|
|
121
|
+
"patterns": [
|
|
122
|
+
"gracias", "buen trabajo", "bien hecho", "perfecto", "genial",
|
|
123
|
+
"excelente", "fenomenal", "great job", "nice work", "thank",
|
|
124
|
+
"thanks", "awesome", "amazing", "love it", "me encanta",
|
|
125
|
+
],
|
|
126
|
+
"min_matches": 1,
|
|
127
|
+
},
|
|
128
|
+
"correction": {
|
|
129
|
+
"patterns": [
|
|
130
|
+
"ya te dije", "ya te lo dije", "otra vez", "te he dicho",
|
|
131
|
+
"no es así", "eso no", "mal", "incorrecto", "equivocado",
|
|
132
|
+
"no no no", "that's wrong", "te aviso", "te avisé",
|
|
133
|
+
"2ª vez", "segunda vez", "te lo repito",
|
|
134
|
+
],
|
|
135
|
+
"min_matches": 1,
|
|
136
|
+
},
|
|
137
|
+
"repeated_error": {
|
|
138
|
+
"patterns": [
|
|
139
|
+
"otra vez lo mismo", "siempre igual", "ya te lo dije antes",
|
|
140
|
+
"cuántas veces", "no aprendes", "same mistake", "again the same",
|
|
141
|
+
"ya van", "es la 2", "es la 3", "ya te avisé",
|
|
142
|
+
],
|
|
143
|
+
"min_matches": 1,
|
|
144
|
+
},
|
|
145
|
+
"delegation": {
|
|
146
|
+
"patterns": [
|
|
147
|
+
"encárgate", "hazlo tú", "dale tú", "te lo dejo",
|
|
148
|
+
"manéjalo", "resuélvelo", "handle it", "take care of",
|
|
149
|
+
"you decide", "tú decides", "lo que veas", "como veas",
|
|
150
|
+
],
|
|
151
|
+
"min_matches": 1,
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def auto_detect_trust_events(text: str) -> list[dict]:
|
|
157
|
+
"""Detect trust events from user text. Returns list of {event, delta, reason}.
|
|
158
|
+
|
|
159
|
+
Called automatically by heartbeat. Only fires once per event per heartbeat
|
|
160
|
+
to avoid double-counting.
|
|
161
|
+
"""
|
|
162
|
+
if not text or len(text.strip()) < 5:
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
text_lower = text.lower()
|
|
166
|
+
events = get_trust_events()
|
|
167
|
+
detected = []
|
|
168
|
+
|
|
169
|
+
for event_name, config in TRUST_AUTO_PATTERNS.items():
|
|
170
|
+
matches = [p for p in config["patterns"] if p in text_lower]
|
|
171
|
+
if len(matches) >= config["min_matches"]:
|
|
172
|
+
delta = events.get(event_name, _DEFAULT_TRUST_EVENTS.get(event_name, 0))
|
|
173
|
+
detected.append({
|
|
174
|
+
"event": event_name,
|
|
175
|
+
"delta": delta,
|
|
176
|
+
"reason": f"auto-detected: {', '.join(matches[:3])}",
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
# Priority: if repeated_error detected, remove correction (it's a superset)
|
|
180
|
+
event_names = {d["event"] for d in detected}
|
|
181
|
+
if "repeated_error" in event_names and "correction" in event_names:
|
|
182
|
+
detected = [d for d in detected if d["event"] != "correction"]
|
|
183
|
+
# If explicit_thanks and delegation both detected, keep both (they're independent)
|
|
184
|
+
|
|
185
|
+
return detected
|
|
186
|
+
|
|
80
187
|
_model = None
|
|
81
188
|
_conn = None
|
|
82
189
|
|
|
@@ -1956,7 +2063,7 @@ def resolve_dissonance(memory_id: int, resolution: str, context: str = "") -> st
|
|
|
1956
2063
|
Args:
|
|
1957
2064
|
memory_id: The LTM memory that conflicts with the new instruction
|
|
1958
2065
|
resolution: One of:
|
|
1959
|
-
- 'paradigm_shift': the user changed
|
|
2066
|
+
- 'paradigm_shift': the user changed their mind permanently. Decay old memory,
|
|
1960
2067
|
new instruction becomes the standard.
|
|
1961
2068
|
- 'exception': This is a one-time override. Keep old memory as standard.
|
|
1962
2069
|
- 'override': Old memory was wrong. Mark as corrupted and decay to dormant.
|
|
@@ -2159,7 +2266,8 @@ def adjust_trust(event: str, context: str = "", custom_delta: float = None) -> d
|
|
|
2159
2266
|
db = _get_db()
|
|
2160
2267
|
old_score = get_trust_score()
|
|
2161
2268
|
|
|
2162
|
-
|
|
2269
|
+
events = get_trust_events()
|
|
2270
|
+
delta = custom_delta if custom_delta is not None else events.get(event, 0)
|
|
2163
2271
|
if delta == 0 and custom_delta is None:
|
|
2164
2272
|
return {"old_score": old_score, "delta": 0, "new_score": old_score, "event": event, "error": "unknown event"}
|
|
2165
2273
|
|
package/src/server.py
CHANGED
|
@@ -52,7 +52,7 @@ mcp = FastMCP(
|
|
|
52
52
|
name="nexo",
|
|
53
53
|
instructions=(
|
|
54
54
|
"NEXO operational server. Provides session coordination, "
|
|
55
|
-
"reminders, followups, and menu for user operations.\n\n"
|
|
55
|
+
"reminders, followups, and menu for the user's operations.\n\n"
|
|
56
56
|
"When working with tool results, write down any important information "
|
|
57
57
|
"you might need later in your response, as the original tool result "
|
|
58
58
|
"may be cleared later."
|
|
@@ -75,15 +75,16 @@ def nexo_startup(task: str = "Startup") -> str:
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
@mcp.tool
|
|
78
|
-
def nexo_heartbeat(sid: str, task: str) -> str:
|
|
79
|
-
"""Update session task, check inbox and pending questions.
|
|
78
|
+
def nexo_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
|
|
79
|
+
"""Update session task, check inbox and pending questions. Auto-detects trust events.
|
|
80
80
|
|
|
81
81
|
Call this at the START of every user interaction (before doing work).
|
|
82
82
|
Args:
|
|
83
83
|
sid: Your session ID from nexo_startup.
|
|
84
84
|
task: Brief description of current work (5-10 words).
|
|
85
|
+
context_hint: Last 2-3 sentences from the user or current topic. Used for sentiment detection, trust auto-scoring, and mid-session RAG. ALWAYS provide this for best results.
|
|
85
86
|
"""
|
|
86
|
-
return handle_heartbeat(sid, task)
|
|
87
|
+
return handle_heartbeat(sid, task, context_hint)
|
|
87
88
|
|
|
88
89
|
|
|
89
90
|
@mcp.tool
|
|
@@ -312,7 +313,7 @@ def nexo_learning_add(category: str, title: str, content: str, reasoning: str =
|
|
|
312
313
|
"""Add a new learning (resolved error, pattern, gotcha).
|
|
313
314
|
|
|
314
315
|
Args:
|
|
315
|
-
category: One of:
|
|
316
|
+
category: One of: nexo-ops, infrastructure, security, brain-engine, other.
|
|
316
317
|
title: Short title for the learning.
|
|
317
318
|
content: Full description with context and solution.
|
|
318
319
|
reasoning: WHY this matters — what led to discovering this (optional).
|
package/src/tools_sessions.py
CHANGED
|
@@ -102,6 +102,19 @@ def handle_heartbeat(sid: str, task: str, context_hint: str = '') -> str:
|
|
|
102
102
|
except Exception:
|
|
103
103
|
pass
|
|
104
104
|
|
|
105
|
+
# Auto-detect trust events from context_hint
|
|
106
|
+
if context_hint and len(context_hint.strip()) >= 10:
|
|
107
|
+
try:
|
|
108
|
+
import cognitive
|
|
109
|
+
auto_events = cognitive.auto_detect_trust_events(context_hint)
|
|
110
|
+
for ae in auto_events:
|
|
111
|
+
result = cognitive.adjust_trust(ae["event"], ae["reason"], ae["delta"])
|
|
112
|
+
if result.get("delta", 0) != 0:
|
|
113
|
+
parts.append("")
|
|
114
|
+
parts.append(f"TRUST AUTO: {result['old_score']:.0f} → {result['new_score']:.0f} ({result['delta']:+.0f}) [{ae['event']}] {ae['reason']}")
|
|
115
|
+
except Exception:
|
|
116
|
+
pass # Auto-trust is best-effort
|
|
117
|
+
|
|
105
118
|
# Mid-session RAG: if context_hint provided, check for context shift
|
|
106
119
|
if context_hint and len(context_hint.strip()) >= 15:
|
|
107
120
|
try:
|