nexo-brain 2.6.16 → 2.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +12 -2
- package/package.json +1 -1
- package/src/auto_update.py +10 -1
- package/src/bootstrap_docs.py +3 -1
- package/src/client_preferences.py +68 -2
- package/src/client_sync.py +45 -21
- package/src/cognitive/_search.py +30 -3
- package/src/doctor/providers/runtime.py +325 -0
- package/src/plugins/cognitive_memory.py +4 -0
- package/src/plugins/update.py +10 -1
- package/src/scripts/deep-sleep/apply_findings.py +393 -0
- package/src/scripts/deep-sleep/collect.py +221 -0
- package/src/scripts/deep-sleep/synthesize-prompt.md +13 -0
- package/src/scripts/deep-sleep/synthesize.py +1 -0
- package/src/scripts/nexo-update.sh +7 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.18",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -38,11 +38,17 @@ That means NEXO now manages not only the shared runtime and MCP wiring, but also
|
|
|
38
38
|
- For Codex specifically, `nexo chat` and Codex headless automation inject the current bootstrap explicitly, so Codex starts as NEXO even when plain global Codex startup is inconsistent about global instructions.
|
|
39
39
|
- Deep Sleep now reads both Claude Code and Codex transcript stores, so overnight analysis still works even when the user spends the day in Codex.
|
|
40
40
|
|
|
41
|
-
Version `2.6.14` closes those parity gaps in practice, `2.6.15` hardens the installed-runtime migration path so existing users actually receive the managed bootstrap updates cleanly,
|
|
41
|
+
Version `2.6.14` closes those parity gaps in practice, `2.6.15` hardens the installed-runtime migration path so existing users actually receive the managed bootstrap updates cleanly, `2.6.16` pushes the system further in three directions, `2.6.17` finishes the annoying last-mile migration bugs for real existing installs, and `2.6.18` tightens the remaining practical gaps around manual Codex use, Deep Sleep horizon artifacts, and retrieval honesty:
|
|
42
42
|
|
|
43
43
|
- Codex now gets managed global bootstrap/model sync in `~/.codex/config.toml`, so sessions opened outside `nexo chat` are much less likely to start as plain Codex.
|
|
44
|
+
- Codex config now also persists a managed `mcp_servers.nexo` entry, so the shared brain survives even if ad-hoc Codex MCP state drifts.
|
|
45
|
+
- Runtime doctor now audits recent Codex sessions for real startup discipline and verifies Claude Desktop shared-brain metadata explicitly instead of treating both as invisible best-effort wiring.
|
|
44
46
|
- Retrieval is smarter by default: HyDE and spreading activation now auto-enable when the query shape benefits, while exact lookups remain conservative.
|
|
47
|
+
- Retrieval explanations now surface confidence and the auto-strategy that fired, while associative expansion trims itself back to `top_k` instead of leaking low-signal neighbors.
|
|
45
48
|
- Deep Sleep now blends recent context with older context over a 60-day horizon, and memory decay now tracks per-memory `stability` and `difficulty` instead of relying only on global decay constants.
|
|
49
|
+
- Deep Sleep now also carries project-priority weighting into its long-horizon context and writes reusable weekly/monthly summary artifacts instead of reasoning only day by day.
|
|
50
|
+
- Existing installs that already had NEXO connected to Codex now backfill that client state automatically during update/sync, so the managed Codex bootstrap actually lands without manual cleanup.
|
|
51
|
+
- Bootstrap docs now fall back to the operator name `NEXO` when local metadata is blank, avoiding broken headings in `CLAUDE.md` and `AGENTS.md`.
|
|
46
52
|
|
|
47
53
|
### Client Capability Matrix
|
|
48
54
|
|
|
@@ -50,11 +56,12 @@ Version `2.6.14` closes those parity gaps in practice, `2.6.15` hardens the inst
|
|
|
50
56
|
|------------|-------------|-------|----------------|
|
|
51
57
|
| Shared brain / MCP runtime | Yes | Yes | Yes |
|
|
52
58
|
| Managed bootstrap document | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.md` | Not applicable |
|
|
53
|
-
| Global startup bootstrap sync | Native via hooks + bootstrap | Managed via bootstrap + Codex config `initial_messages` | MCP
|
|
59
|
+
| Global startup bootstrap sync | Native via hooks + bootstrap | Managed via bootstrap + Codex config `initial_messages` + `mcp_servers.nexo` | Managed MCP-only shared-brain metadata |
|
|
54
60
|
| `nexo chat` terminal client | Yes | Yes | No |
|
|
55
61
|
| Background automation backend | Recommended | Supported | No |
|
|
56
62
|
| Raw transcript source for Deep Sleep | Yes | Yes | No |
|
|
57
63
|
| Native hook depth | Deepest | Partial, compensated | None |
|
|
64
|
+
| Runtime doctor parity audit | Yes | Yes | Shared-brain only |
|
|
58
65
|
| Recommended today | Yes | Supported | Shared-brain companion |
|
|
59
66
|
|
|
60
67
|
## The Problem
|
|
@@ -191,6 +198,9 @@ Deep Sleep now also mixes **recent context with older context across a 60-day ho
|
|
|
191
198
|
- recurring multi-week themes
|
|
192
199
|
- cross-domain links between older learnings and current failures
|
|
193
200
|
- stale followups and topics that keep being mentioned but never formalized
|
|
201
|
+
- weighted project pressure based on diary activity, followups, learnings, and decision outcomes
|
|
202
|
+
|
|
203
|
+
It now also writes **weekly and monthly Deep Sleep summaries** so the overnight system can reuse higher-horizon signals instead of rediscovering everything from scratch every day.
|
|
194
204
|
|
|
195
205
|
## Cognitive Cortex
|
|
196
206
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.18",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
package/src/auto_update.py
CHANGED
|
@@ -1434,10 +1434,19 @@ def _run_runtime_post_sync(dest: Path = NEXO_HOME, progress_fn=None) -> tuple[bo
|
|
|
1434
1434
|
|
|
1435
1435
|
schedule_path = dest / "config" / "schedule.json"
|
|
1436
1436
|
schedule_payload = json.loads(schedule_path.read_text()) if schedule_path.exists() else {}
|
|
1437
|
+
normalized_preferences = normalize_client_preferences(schedule_payload)
|
|
1438
|
+
if normalized_preferences != {
|
|
1439
|
+
key: schedule_payload.get(key)
|
|
1440
|
+
for key in normalized_preferences
|
|
1441
|
+
}:
|
|
1442
|
+
merged_schedule = dict(schedule_payload)
|
|
1443
|
+
merged_schedule.update(normalized_preferences)
|
|
1444
|
+
schedule_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1445
|
+
schedule_path.write_text(json.dumps(merged_schedule, indent=2, ensure_ascii=False) + "\n")
|
|
1437
1446
|
client_sync_result = sync_all_clients(
|
|
1438
1447
|
nexo_home=dest,
|
|
1439
1448
|
runtime_root=dest,
|
|
1440
|
-
preferences=
|
|
1449
|
+
preferences=normalized_preferences,
|
|
1441
1450
|
)
|
|
1442
1451
|
if client_sync_result.get("ok"):
|
|
1443
1452
|
actions.append("client-sync")
|
package/src/bootstrap_docs.py
CHANGED
|
@@ -73,7 +73,9 @@ def _resolve_operator_name(nexo_home: Path, explicit: str = "") -> str:
|
|
|
73
73
|
version_file = nexo_home / "version.json"
|
|
74
74
|
if version_file.is_file():
|
|
75
75
|
try:
|
|
76
|
-
|
|
76
|
+
candidate = str(json.loads(version_file.read_text()).get("operator_name", "")).strip()
|
|
77
|
+
if candidate:
|
|
78
|
+
return candidate
|
|
77
79
|
except Exception:
|
|
78
80
|
pass
|
|
79
81
|
return "NEXO"
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
import shutil
|
|
7
7
|
import sys
|
|
8
|
+
import tomllib
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from runtime_power import load_schedule_config, save_schedule_config
|
|
@@ -51,6 +52,14 @@ def _user_home() -> Path:
|
|
|
51
52
|
return Path(os.environ.get("HOME", str(Path.home()))).expanduser()
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def _codex_config_path(home: Path) -> Path:
|
|
56
|
+
return home / ".codex" / "config.toml"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _codex_bootstrap_path(home: Path) -> Path:
|
|
60
|
+
return home / ".codex" / "AGENTS.md"
|
|
61
|
+
|
|
62
|
+
|
|
54
63
|
def _coerce_bool(value, default: bool) -> bool:
|
|
55
64
|
if isinstance(value, bool):
|
|
56
65
|
return value
|
|
@@ -127,6 +136,56 @@ def normalize_interactive_clients(value) -> dict[str, bool]:
|
|
|
127
136
|
return normalized
|
|
128
137
|
|
|
129
138
|
|
|
139
|
+
def _codex_artifacts_suggest_nexo_management(home: Path) -> bool:
|
|
140
|
+
bootstrap_path = _codex_bootstrap_path(home)
|
|
141
|
+
if bootstrap_path.is_file():
|
|
142
|
+
try:
|
|
143
|
+
bootstrap_text = bootstrap_path.read_text()
|
|
144
|
+
except Exception:
|
|
145
|
+
bootstrap_text = ""
|
|
146
|
+
if (
|
|
147
|
+
"nexo-codex-agents-version:" in bootstrap_text
|
|
148
|
+
or "NEXO Shared Brain for Codex" in bootstrap_text
|
|
149
|
+
or "<!-- nexo:core:start -->" in bootstrap_text
|
|
150
|
+
):
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
config_path = _codex_config_path(home)
|
|
154
|
+
if not config_path.is_file():
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
payload = tomllib.loads(config_path.read_text())
|
|
159
|
+
except Exception:
|
|
160
|
+
try:
|
|
161
|
+
raw_text = config_path.read_text()
|
|
162
|
+
except Exception:
|
|
163
|
+
return False
|
|
164
|
+
return "[mcp_servers.nexo]" in raw_text or "[nexo.codex]" in raw_text
|
|
165
|
+
|
|
166
|
+
if not isinstance(payload, dict):
|
|
167
|
+
return False
|
|
168
|
+
mcp_servers = payload.get("mcp_servers")
|
|
169
|
+
if isinstance(mcp_servers, dict) and "nexo" in mcp_servers:
|
|
170
|
+
return True
|
|
171
|
+
nexo_table = payload.get("nexo")
|
|
172
|
+
if isinstance(nexo_table, dict) and "codex" in nexo_table:
|
|
173
|
+
return True
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _backfill_interactive_clients(
|
|
178
|
+
interactive_clients: dict[str, bool],
|
|
179
|
+
*,
|
|
180
|
+
user_home: str | os.PathLike[str] | None = None,
|
|
181
|
+
) -> dict[str, bool]:
|
|
182
|
+
normalized = dict(interactive_clients)
|
|
183
|
+
home = Path(user_home).expanduser() if user_home else _user_home()
|
|
184
|
+
if not normalized.get(CLIENT_CODEX, False) and _codex_artifacts_suggest_nexo_management(home):
|
|
185
|
+
normalized[CLIENT_CODEX] = True
|
|
186
|
+
return normalized
|
|
187
|
+
|
|
188
|
+
|
|
130
189
|
def normalize_default_terminal_client(value, interactive_clients: dict[str, bool] | None = None) -> str:
|
|
131
190
|
interactive_clients = normalize_interactive_clients(interactive_clients or {})
|
|
132
191
|
candidate = normalize_client_key(value)
|
|
@@ -210,9 +269,16 @@ def normalize_client_runtime_profiles(value) -> dict[str, dict[str, str]]:
|
|
|
210
269
|
return normalized
|
|
211
270
|
|
|
212
271
|
|
|
213
|
-
def normalize_client_preferences(
|
|
272
|
+
def normalize_client_preferences(
|
|
273
|
+
schedule: dict | None = None,
|
|
274
|
+
*,
|
|
275
|
+
user_home: str | os.PathLike[str] | None = None,
|
|
276
|
+
) -> dict:
|
|
214
277
|
schedule = dict(schedule or {})
|
|
215
|
-
interactive_clients =
|
|
278
|
+
interactive_clients = _backfill_interactive_clients(
|
|
279
|
+
normalize_interactive_clients(schedule.get("interactive_clients")),
|
|
280
|
+
user_home=user_home,
|
|
281
|
+
)
|
|
216
282
|
automation_enabled = normalize_automation_enabled(schedule.get("automation_enabled"))
|
|
217
283
|
default_terminal_client = normalize_default_terminal_client(
|
|
218
284
|
schedule.get("default_terminal_client"),
|
package/src/client_sync.py
CHANGED
|
@@ -80,10 +80,12 @@ def _resolve_operator_name(nexo_home: Path, explicit: str = "") -> str:
|
|
|
80
80
|
version_file = nexo_home / "version.json"
|
|
81
81
|
if version_file.is_file():
|
|
82
82
|
try:
|
|
83
|
-
|
|
83
|
+
candidate = str(json.loads(version_file.read_text()).get("operator_name", "")).strip()
|
|
84
|
+
if candidate:
|
|
85
|
+
return candidate
|
|
84
86
|
except Exception:
|
|
85
87
|
pass
|
|
86
|
-
return ""
|
|
88
|
+
return "NEXO"
|
|
87
89
|
|
|
88
90
|
|
|
89
91
|
def _resolve_runtime_root(nexo_home: Path, runtime_root: str | os.PathLike[str] | None = None) -> Path:
|
|
@@ -241,10 +243,12 @@ def _sync_codex_managed_config(
|
|
|
241
243
|
*,
|
|
242
244
|
bootstrap_prompt: str,
|
|
243
245
|
runtime_profile: dict | None,
|
|
246
|
+
server_config: dict | None,
|
|
244
247
|
) -> dict:
|
|
245
248
|
payload = _load_toml_object(path)
|
|
246
249
|
action = "updated" if payload else "created"
|
|
247
250
|
runtime_profile = dict(runtime_profile or {})
|
|
251
|
+
server_config = dict(server_config or {})
|
|
248
252
|
|
|
249
253
|
if runtime_profile.get("model"):
|
|
250
254
|
payload["model"] = runtime_profile["model"]
|
|
@@ -261,10 +265,19 @@ def _sync_codex_managed_config(
|
|
|
261
265
|
nexo_table = payload.setdefault("nexo", {})
|
|
262
266
|
codex_table = nexo_table.setdefault("codex", {})
|
|
263
267
|
codex_table["bootstrap_managed"] = True
|
|
268
|
+
codex_table["mcp_managed"] = True
|
|
264
269
|
codex_table["bootstrap_bytes"] = len(bootstrap_prompt.encode("utf-8")) if bootstrap_prompt else 0
|
|
265
270
|
if runtime_profile.get("model"):
|
|
266
271
|
codex_table["managed_model"] = runtime_profile["model"]
|
|
267
272
|
codex_table["managed_reasoning_effort"] = runtime_profile.get("reasoning_effort", "") or ""
|
|
273
|
+
if server_config:
|
|
274
|
+
mcp_servers = payload.setdefault("mcp_servers", {})
|
|
275
|
+
mcp_servers["nexo"] = {
|
|
276
|
+
"command": server_config.get("command", ""),
|
|
277
|
+
"args": list(server_config.get("args", []) or []),
|
|
278
|
+
"env": dict(server_config.get("env", {}) or {}),
|
|
279
|
+
}
|
|
280
|
+
codex_table["managed_server_command"] = server_config.get("command", "")
|
|
268
281
|
|
|
269
282
|
_write_toml_object(path, payload)
|
|
270
283
|
return {
|
|
@@ -272,6 +285,7 @@ def _sync_codex_managed_config(
|
|
|
272
285
|
"action": action,
|
|
273
286
|
"path": str(path),
|
|
274
287
|
"bootstrap_managed": True,
|
|
288
|
+
"mcp_managed": True,
|
|
275
289
|
"model": runtime_profile.get("model", ""),
|
|
276
290
|
"reasoning_effort": runtime_profile.get("reasoning_effort", "") or "",
|
|
277
291
|
}
|
|
@@ -294,7 +308,7 @@ def _write_json_object(path: Path, payload: dict) -> None:
|
|
|
294
308
|
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
|
|
295
309
|
|
|
296
310
|
|
|
297
|
-
def _sync_json_client(path: Path, server_config: dict, label: str) -> dict:
|
|
311
|
+
def _sync_json_client(path: Path, server_config: dict, label: str, *, managed_metadata: dict | None = None) -> dict:
|
|
298
312
|
payload = _load_json_object(path)
|
|
299
313
|
mcp_servers = payload.setdefault("mcpServers", {})
|
|
300
314
|
if not isinstance(mcp_servers, dict):
|
|
@@ -302,6 +316,12 @@ def _sync_json_client(path: Path, server_config: dict, label: str) -> dict:
|
|
|
302
316
|
payload["mcpServers"] = mcp_servers
|
|
303
317
|
action = "updated" if "nexo" in mcp_servers else "created"
|
|
304
318
|
mcp_servers["nexo"] = server_config
|
|
319
|
+
if managed_metadata is not None:
|
|
320
|
+
nexo_meta = payload.setdefault("nexo", {})
|
|
321
|
+
if not isinstance(nexo_meta, dict):
|
|
322
|
+
nexo_meta = {}
|
|
323
|
+
payload["nexo"] = nexo_meta
|
|
324
|
+
nexo_meta.update(managed_metadata)
|
|
305
325
|
_write_json_object(path, payload)
|
|
306
326
|
return {
|
|
307
327
|
"ok": True,
|
|
@@ -311,6 +331,18 @@ def _sync_json_client(path: Path, server_config: dict, label: str) -> dict:
|
|
|
311
331
|
}
|
|
312
332
|
|
|
313
333
|
|
|
334
|
+
def _claude_desktop_managed_metadata(server_config: dict, *, operator_name: str) -> dict:
|
|
335
|
+
return {
|
|
336
|
+
"claude_desktop": {
|
|
337
|
+
"shared_brain_managed": True,
|
|
338
|
+
"shared_brain_mode": "mcp_only",
|
|
339
|
+
"managed_operator": operator_name or server_config.get("env", {}).get("NEXO_NAME", "") or "NEXO",
|
|
340
|
+
"managed_runtime_home": server_config.get("env", {}).get("NEXO_HOME", ""),
|
|
341
|
+
"managed_runtime_root": server_config.get("env", {}).get("NEXO_CODE", ""),
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
314
346
|
def sync_claude_code(
|
|
315
347
|
*,
|
|
316
348
|
nexo_home: str | os.PathLike[str] | None = None,
|
|
@@ -359,10 +391,15 @@ def sync_claude_desktop(
|
|
|
359
391
|
python_path=python_path,
|
|
360
392
|
operator_name=operator_name,
|
|
361
393
|
)
|
|
394
|
+
resolved_name = server_config.get("env", {}).get("NEXO_NAME", "") or _resolve_operator_name(
|
|
395
|
+
Path(nexo_home).expanduser() if nexo_home else _default_nexo_home(),
|
|
396
|
+
explicit=operator_name,
|
|
397
|
+
)
|
|
362
398
|
return _sync_json_client(
|
|
363
399
|
_claude_desktop_config_path(Path(user_home).expanduser() if user_home else None),
|
|
364
400
|
server_config,
|
|
365
401
|
"claude_desktop",
|
|
402
|
+
managed_metadata=_claude_desktop_managed_metadata(server_config, operator_name=resolved_name),
|
|
366
403
|
)
|
|
367
404
|
|
|
368
405
|
|
|
@@ -408,6 +445,7 @@ def sync_codex(
|
|
|
408
445
|
config_path,
|
|
409
446
|
bootstrap_prompt=prompt_text,
|
|
410
447
|
runtime_profile=runtime_profile,
|
|
448
|
+
server_config=server_config,
|
|
411
449
|
)
|
|
412
450
|
return result
|
|
413
451
|
|
|
@@ -423,29 +461,12 @@ def sync_codex(
|
|
|
423
461
|
timeout=30,
|
|
424
462
|
env=env,
|
|
425
463
|
)
|
|
426
|
-
if result.returncode != 0:
|
|
427
|
-
result = {
|
|
428
|
-
"ok": False,
|
|
429
|
-
"client": "codex",
|
|
430
|
-
"path": str(config_path),
|
|
431
|
-
"error": (result.stderr or result.stdout or "codex mcp add failed").strip(),
|
|
432
|
-
}
|
|
433
|
-
bootstrap_result = sync_client_bootstrap(
|
|
434
|
-
"codex",
|
|
435
|
-
nexo_home=nexo_home,
|
|
436
|
-
operator_name=operator_name,
|
|
437
|
-
user_home=user_home,
|
|
438
|
-
)
|
|
439
|
-
result["bootstrap"] = bootstrap_result
|
|
440
|
-
if not bootstrap_result.get("ok"):
|
|
441
|
-
result["error"] = f"{result['error']}; bootstrap: {bootstrap_result.get('error', 'unknown error')}"
|
|
442
|
-
return result
|
|
443
464
|
sync_result = {
|
|
444
465
|
"ok": True,
|
|
445
466
|
"client": "codex",
|
|
446
467
|
"action": "updated",
|
|
447
468
|
"path": str(config_path),
|
|
448
|
-
"mode": "cli",
|
|
469
|
+
"mode": "cli" if result.returncode == 0 else "config_only",
|
|
449
470
|
}
|
|
450
471
|
bootstrap_result = sync_client_bootstrap(
|
|
451
472
|
"codex",
|
|
@@ -462,7 +483,10 @@ def sync_codex(
|
|
|
462
483
|
config_path,
|
|
463
484
|
bootstrap_prompt=bootstrap_result.get("content") or "",
|
|
464
485
|
runtime_profile=runtime_profile,
|
|
486
|
+
server_config=server_config,
|
|
465
487
|
)
|
|
488
|
+
if result.returncode != 0:
|
|
489
|
+
sync_result["warning"] = (result.stderr or result.stdout or "codex mcp add failed").strip()
|
|
466
490
|
return sync_result
|
|
467
491
|
|
|
468
492
|
|
package/src/cognitive/_search.py
CHANGED
|
@@ -159,6 +159,7 @@ _HISTORICAL_CUES = frozenset({
|
|
|
159
159
|
_EXACT_LOOKUP_RE = re.compile(
|
|
160
160
|
r"(/|\\|::|\.[A-Za-z0-9]+|#L\d+|line \d+|error[: ]|exception|traceback|0x[0-9a-fA-F]+|[A-Z]{2,}-\d+)"
|
|
161
161
|
)
|
|
162
|
+
_MIN_NEIGHBOR_BOOST = 0.035
|
|
162
163
|
|
|
163
164
|
|
|
164
165
|
def _apply_temporal_boost(results: list[dict], query_text: str) -> list[dict]:
|
|
@@ -254,6 +255,14 @@ def _auto_spreading_depth(query_text: str, source_type_filter: str = "") -> int:
|
|
|
254
255
|
return 0
|
|
255
256
|
|
|
256
257
|
|
|
258
|
+
def _result_confidence(score: float) -> str:
|
|
259
|
+
if score >= 0.82:
|
|
260
|
+
return "high"
|
|
261
|
+
if score >= 0.66:
|
|
262
|
+
return "medium"
|
|
263
|
+
return "low"
|
|
264
|
+
|
|
265
|
+
|
|
257
266
|
# ============================================================================
|
|
258
267
|
# FEATURE 0.5: Knowledge Graph Boost
|
|
259
268
|
# Memories connected to more KG nodes (files, areas, other learnings) are
|
|
@@ -928,13 +937,23 @@ def search(
|
|
|
928
937
|
r["co_activation_boost"] = boost
|
|
929
938
|
|
|
930
939
|
# Add neighbor memories not already in results
|
|
931
|
-
new_neighbor_hashes =
|
|
940
|
+
new_neighbor_hashes = {
|
|
941
|
+
nh
|
|
942
|
+
for nh, boost in neighbor_boosts.items()
|
|
943
|
+
if nh not in existing_hashes and boost >= _MIN_NEIGHBOR_BOOST
|
|
944
|
+
}
|
|
932
945
|
if new_neighbor_hashes:
|
|
946
|
+
ranked_new_neighbors = sorted(
|
|
947
|
+
new_neighbor_hashes,
|
|
948
|
+
key=lambda nh: neighbor_boosts.get(nh, 0.0),
|
|
949
|
+
reverse=True,
|
|
950
|
+
)[: max(1, min(3, top_k // 3 or 1))]
|
|
951
|
+
allowed_new_neighbors = set(ranked_new_neighbors)
|
|
933
952
|
for store_name, table in [("stm", "stm_memories"), ("ltm", "ltm_memories")]:
|
|
934
953
|
rows = db.execute(f"SELECT * FROM {table}").fetchall()
|
|
935
954
|
for row in rows:
|
|
936
955
|
nh = _canonical_co_id(store_name, row["id"])
|
|
937
|
-
if nh in
|
|
956
|
+
if nh in allowed_new_neighbors:
|
|
938
957
|
boost = neighbor_boosts[nh]
|
|
939
958
|
results.append({
|
|
940
959
|
"store": store_name,
|
|
@@ -951,10 +970,11 @@ def search(
|
|
|
951
970
|
"co_activation_boost": boost,
|
|
952
971
|
"lifecycle_state": row.get("lifecycle_state", "active"),
|
|
953
972
|
})
|
|
954
|
-
|
|
973
|
+
allowed_new_neighbors.discard(nh)
|
|
955
974
|
|
|
956
975
|
# Re-sort after applying boosts
|
|
957
976
|
results.sort(key=lambda x: x["score"], reverse=True)
|
|
977
|
+
results = results[:top_k]
|
|
958
978
|
|
|
959
979
|
# Add rank explanations
|
|
960
980
|
for rank, r in enumerate(results, 1):
|
|
@@ -970,6 +990,7 @@ def search(
|
|
|
970
990
|
if resolved_use_hyde:
|
|
971
991
|
ranking_desc = "hyde_centroid_similarity"
|
|
972
992
|
parts = [f"Ranked #{rank}: {ranking_desc}={score:.3f}"]
|
|
993
|
+
parts.append(f"confidence={_result_confidence(score)}")
|
|
973
994
|
parts.append(f"store={store}, strength={strength:.2f}, accesses={access_count}")
|
|
974
995
|
if r.get("kg_boost"):
|
|
975
996
|
parts.append(f"kg_boost=+{r['kg_boost']:.3f} ({r.get('kg_connections', 0)} edges)")
|
|
@@ -979,6 +1000,12 @@ def search(
|
|
|
979
1000
|
parts.append("hyde=auto")
|
|
980
1001
|
if spreading_depth is None and resolved_spreading_depth > 0:
|
|
981
1002
|
parts.append(f"spreading=auto:{resolved_spreading_depth}")
|
|
1003
|
+
if use_hyde is None and resolved_use_hyde and spreading_depth is None and resolved_spreading_depth > 0:
|
|
1004
|
+
parts.append("auto_strategy=semantic+associative recall")
|
|
1005
|
+
elif use_hyde is None and resolved_use_hyde:
|
|
1006
|
+
parts.append("auto_strategy=semantic expansion")
|
|
1007
|
+
elif spreading_depth is None and resolved_spreading_depth > 0:
|
|
1008
|
+
parts.append("auto_strategy=associative expansion")
|
|
982
1009
|
if created:
|
|
983
1010
|
parts.append(f"created={created[:10]}")
|
|
984
1011
|
if tags:
|