nexo-brain 2.6.17 → 2.6.20

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "2.6.17",
3
+ "version": "2.6.20",
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,15 @@ 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, `2.6.16` pushes the system further in three directions, and `2.6.17` finishes the annoying last-mile migration bugs for real existing installs:
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, `2.6.18` tightens the remaining practical gaps around manual Codex use, Deep Sleep horizon artifacts, and retrieval honesty, and `2.6.20` makes the recommended Claude profile explicit across installer, runtime defaults, existing installs, and the update path itself: `Opus 4.6 with 1M context`.
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.
46
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.
47
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`.
48
52
 
@@ -52,11 +56,12 @@ Version `2.6.14` closes those parity gaps in practice, `2.6.15` hardens the inst
52
56
  |------------|-------------|-------|----------------|
53
57
  | Shared brain / MCP runtime | Yes | Yes | Yes |
54
58
  | Managed bootstrap document | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.md` | Not applicable |
55
- | Global startup bootstrap sync | Native via hooks + bootstrap | Managed via bootstrap + Codex config `initial_messages` | MCP only |
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 |
56
60
  | `nexo chat` terminal client | Yes | Yes | No |
57
61
  | Background automation backend | Recommended | Supported | No |
58
62
  | Raw transcript source for Deep Sleep | Yes | Yes | No |
59
63
  | Native hook depth | Deepest | Partial, compensated | None |
64
+ | Runtime doctor parity audit | Yes | Yes | Shared-brain only |
60
65
  | Recommended today | Yes | Supported | Shared-brain companion |
61
66
 
62
67
  ## The Problem
@@ -193,6 +198,9 @@ Deep Sleep now also mixes **recent context with older context across a 60-day ho
193
198
  - recurring multi-week themes
194
199
  - cross-domain links between older learnings and current failures
195
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.
196
204
 
197
205
  ## Cognitive Cortex
198
206
 
@@ -637,7 +645,7 @@ nexo scripts list # See your personal scripts
637
645
  During install, NEXO now asks which interactive clients you want to connect, which one `nexo chat` should open by default, whether to enable background automation, which backend should run that automation, and which model profile each active terminal/backend should use. Shared brain stays on in every mode.
638
646
 
639
647
  Recommended defaults:
640
- - Claude Code: `Opus latest`
648
+ - Claude Code: `Opus 4.6 with 1M context`
641
649
  - Codex: `gpt-5.4` with `xhigh` reasoning
642
650
 
643
651
  Or use the shell alias created during install (e.g. `atlas`), which now runs `nexo chat .` so it opens whichever terminal client you selected as default.
@@ -654,6 +662,9 @@ NEXO is being hardened in public, and the best contributions now are not only co
654
662
 
655
663
  The project still recommends Claude Code as the primary path, but contributions that improve Codex, client parity, installer clarity, and ecosystem integrations are especially valuable.
656
664
 
665
+ Maintainers and contributors touching startup, bootstrap, Deep Sleep, or shared-brain behavior should also use the client parity checklist:
666
+ - [docs/client-parity-checklist.md](docs/client-parity-checklist.md)
667
+
657
668
  ### What Gets Installed
658
669
 
659
670
  | Component | What | Where |
@@ -715,7 +726,7 @@ The Doctor system reads existing health artifacts (immune, watchdog, self-audit)
715
726
  - **macOS or Linux** (Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))
716
727
  - **Node.js 18+** (for the installer)
717
728
  - **Claude Code is the primary recommended client.** It remains the most mature NEXO path: native hooks, the most battle-tested automation contract, and the clearest parity with historical production behavior.
718
- - **Recommended profiles:** Claude Code + `Opus latest`; Codex + `gpt-5.4` with `xhigh` reasoning if you prefer Codex as your terminal or automation backend.
729
+ - **Recommended profiles:** Claude Code + `Opus 4.6 with 1M context`; Codex + `gpt-5.4` with `xhigh` reasoning if you prefer Codex as your terminal or automation backend.
719
730
  - Python 3, Homebrew, and the selected required client/backend can be installed automatically when NEXO has a supported installer path for that dependency.
720
731
 
721
732
  ## Architecture
@@ -822,7 +833,7 @@ NEXO Brain is designed as an MCP server. Claude Code remains the primary recomme
822
833
  npx nexo-brain
823
834
  ```
824
835
 
825
- All 150+ tools are available immediately after installation. The installer configures Claude Code's `~/.claude/settings.json` automatically. The recommended Claude profile is `Opus latest`.
836
+ All 150+ tools are available immediately after installation. The installer configures Claude Code's `~/.claude/settings.json` automatically. The recommended Claude profile is `Opus 4.6 with 1M context`.
826
837
 
827
838
  ### Claude Desktop
828
839
 
@@ -912,6 +923,7 @@ If NEXO Brain is useful to you, consider:
912
923
  - **[Sponsor on GitHub](https://github.com/sponsors/wazionapps)** — support ongoing development directly
913
924
  - **Share your experience** — tell others how you're using cognitive memory in your AI workflows
914
925
  - **Contribute** — see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Issues and PRs welcome
926
+ - **Client parity / shared-brain maintenance** — see [docs/client-parity-checklist.md](docs/client-parity-checklist.md)
915
927
 
916
928
  [![Star History Chart](https://api.star-history.com/svg?repos=wazionapps/nexo&type=Date)](https://star-history.com/#wazionapps/nexo&Date)
917
929
 
package/bin/nexo-brain.js CHANGED
@@ -33,6 +33,10 @@ const LAUNCH_AGENTS = path.join(
33
33
  );
34
34
  const MACOS_FDA_SETTINGS_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles";
35
35
  const PUBLIC_CONTRIBUTION_UPSTREAM = "wazionapps/nexo";
36
+ const DEFAULT_CLAUDE_CODE_MODEL = "claude-opus-4-6[1m]";
37
+ const DEFAULT_CLAUDE_CODE_REASONING_EFFORT = "";
38
+ const DEFAULT_CODEX_MODEL = "gpt-5.4";
39
+ const DEFAULT_CODEX_REASONING_EFFORT = "xhigh";
36
40
 
37
41
  function isEphemeralInstall(nexoHome) {
38
42
  const homeDir = require("os").homedir();
@@ -480,12 +484,12 @@ function getDefaultSchedule(timezone) {
480
484
  automation_backend: "claude_code",
481
485
  client_runtime_profiles: {
482
486
  claude_code: {
483
- model: "opus",
484
- reasoning_effort: "",
487
+ model: DEFAULT_CLAUDE_CODE_MODEL,
488
+ reasoning_effort: DEFAULT_CLAUDE_CODE_REASONING_EFFORT,
485
489
  },
486
490
  codex: {
487
- model: "gpt-5.4",
488
- reasoning_effort: "xhigh",
491
+ model: DEFAULT_CODEX_MODEL,
492
+ reasoning_effort: DEFAULT_CODEX_REASONING_EFFORT,
489
493
  },
490
494
  },
491
495
  client_install_preferences: {
@@ -656,12 +660,12 @@ async function askChoice(question, options, defaultValue) {
656
660
  function defaultClientRuntimeProfiles() {
657
661
  return {
658
662
  claude_code: {
659
- model: "opus",
660
- reasoning_effort: "",
663
+ model: DEFAULT_CLAUDE_CODE_MODEL,
664
+ reasoning_effort: DEFAULT_CLAUDE_CODE_REASONING_EFFORT,
661
665
  },
662
666
  codex: {
663
- model: "gpt-5.4",
664
- reasoning_effort: "xhigh",
667
+ model: DEFAULT_CODEX_MODEL,
668
+ reasoning_effort: DEFAULT_CODEX_REASONING_EFFORT,
665
669
  },
666
670
  };
667
671
  }
@@ -690,10 +694,11 @@ function runtimeProfileCatalog(lang, client) {
690
694
  customModelQuestionEn: ` Enter the model alias/name for ${runtimeClientLabel(client)} > `,
691
695
  customEffortQuestion: ` Escribe el effort para ${runtimeClientLabel(client)} (vacío = default) > `,
692
696
  customEffortQuestionEn: ` Enter the effort for ${runtimeClientLabel(client)} (blank = default) > `,
693
- modelDefault: "opus",
697
+ modelDefault: DEFAULT_CLAUDE_CODE_MODEL,
694
698
  effortDefault: "",
695
699
  modelOptions: [
696
- { value: "opus", label: `Opus latest${recommended}` },
700
+ { value: DEFAULT_CLAUDE_CODE_MODEL, label: `Opus 4.6 with 1M context${recommended}` },
701
+ { value: "claude-opus-4-6", label: "Opus 4.6" },
697
702
  { value: "sonnet", label: "Sonnet latest" },
698
703
  { value: "custom", label: lang === "es" ? "Modelo personalizado" : "Custom model" },
699
704
  ],
@@ -715,8 +720,8 @@ function runtimeProfileCatalog(lang, client) {
715
720
  customModelQuestionEn: ` Enter the model name for ${runtimeClientLabel(client)} > `,
716
721
  customEffortQuestion: ` Escribe el reasoning effort para ${runtimeClientLabel(client)} > `,
717
722
  customEffortQuestionEn: ` Enter the reasoning effort for ${runtimeClientLabel(client)} > `,
718
- modelDefault: "gpt-5.4",
719
- effortDefault: "xhigh",
723
+ modelDefault: DEFAULT_CODEX_MODEL,
724
+ effortDefault: DEFAULT_CODEX_REASONING_EFFORT,
720
725
  modelOptions: [
721
726
  { value: "gpt-5.4", label: `GPT-5.4${recommended}` },
722
727
  { value: "gpt-5.4-pro", label: "GPT-5.4 Pro" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "2.6.17",
3
+ "version": "2.6.20",
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",
@@ -229,6 +229,9 @@ def _resolve_runtime_model_and_effort(
229
229
  if client == CLIENT_CODEX:
230
230
  if not requested_model or requested_model.lower() in CLAUDE_LEGACY_MODEL_HINTS:
231
231
  requested_model = profile["model"]
232
+ elif client == CLIENT_CLAUDE_CODE:
233
+ if not requested_model or requested_model.lower() in CLAUDE_LEGACY_MODEL_HINTS:
234
+ requested_model = profile["model"]
232
235
  elif not requested_model:
233
236
  requested_model = profile["model"]
234
237
 
@@ -36,18 +36,10 @@ INSTALL_PREFERENCE_KEYS = {
36
36
  "skip",
37
37
  "manual",
38
38
  }
39
- DEFAULT_CLIENT_RUNTIME_PROFILES = {
40
- CLIENT_CLAUDE_CODE: {
41
- "model": "opus",
42
- "reasoning_effort": "",
43
- },
44
- CLIENT_CODEX: {
45
- "model": "gpt-5.4",
46
- "reasoning_effort": "xhigh",
47
- },
48
- }
49
-
50
-
39
+ DEFAULT_CLAUDE_CODE_MODEL = "claude-opus-4-6[1m]"
40
+ DEFAULT_CLAUDE_CODE_REASONING_EFFORT = ""
41
+ DEFAULT_CODEX_MODEL = "gpt-5.4"
42
+ DEFAULT_CODEX_REASONING_EFFORT = "xhigh"
51
43
  def _user_home() -> Path:
52
44
  return Path(os.environ.get("HOME", str(Path.home()))).expanduser()
53
45
 
@@ -225,8 +217,14 @@ def normalize_client_install_preferences(value) -> dict[str, str]:
225
217
 
226
218
  def default_client_runtime_profiles() -> dict[str, dict[str, str]]:
227
219
  return {
228
- client_key: dict(profile)
229
- for client_key, profile in DEFAULT_CLIENT_RUNTIME_PROFILES.items()
220
+ CLIENT_CLAUDE_CODE: {
221
+ "model": DEFAULT_CLAUDE_CODE_MODEL,
222
+ "reasoning_effort": DEFAULT_CLAUDE_CODE_REASONING_EFFORT,
223
+ },
224
+ CLIENT_CODEX: {
225
+ "model": DEFAULT_CODEX_MODEL,
226
+ "reasoning_effort": DEFAULT_CODEX_REASONING_EFFORT,
227
+ },
230
228
  }
231
229
 
232
230
 
@@ -55,7 +55,7 @@ except Exception:
55
55
 
56
56
  def resolve_client_runtime_profile(client: str, preferences: dict | None = None) -> dict:
57
57
  defaults = {
58
- "claude_code": {"model": "opus", "reasoning_effort": ""},
58
+ "claude_code": {"model": "claude-opus-4-6[1m]", "reasoning_effort": ""},
59
59
  "codex": {"model": "gpt-5.4", "reasoning_effort": "xhigh"},
60
60
  }
61
61
  return dict(defaults.get(client, {}))
@@ -243,10 +243,12 @@ def _sync_codex_managed_config(
243
243
  *,
244
244
  bootstrap_prompt: str,
245
245
  runtime_profile: dict | None,
246
+ server_config: dict | None,
246
247
  ) -> dict:
247
248
  payload = _load_toml_object(path)
248
249
  action = "updated" if payload else "created"
249
250
  runtime_profile = dict(runtime_profile or {})
251
+ server_config = dict(server_config or {})
250
252
 
251
253
  if runtime_profile.get("model"):
252
254
  payload["model"] = runtime_profile["model"]
@@ -263,10 +265,19 @@ def _sync_codex_managed_config(
263
265
  nexo_table = payload.setdefault("nexo", {})
264
266
  codex_table = nexo_table.setdefault("codex", {})
265
267
  codex_table["bootstrap_managed"] = True
268
+ codex_table["mcp_managed"] = True
266
269
  codex_table["bootstrap_bytes"] = len(bootstrap_prompt.encode("utf-8")) if bootstrap_prompt else 0
267
270
  if runtime_profile.get("model"):
268
271
  codex_table["managed_model"] = runtime_profile["model"]
269
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", "")
270
281
 
271
282
  _write_toml_object(path, payload)
272
283
  return {
@@ -274,6 +285,7 @@ def _sync_codex_managed_config(
274
285
  "action": action,
275
286
  "path": str(path),
276
287
  "bootstrap_managed": True,
288
+ "mcp_managed": True,
277
289
  "model": runtime_profile.get("model", ""),
278
290
  "reasoning_effort": runtime_profile.get("reasoning_effort", "") or "",
279
291
  }
@@ -296,7 +308,7 @@ def _write_json_object(path: Path, payload: dict) -> None:
296
308
  path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
297
309
 
298
310
 
299
- 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:
300
312
  payload = _load_json_object(path)
301
313
  mcp_servers = payload.setdefault("mcpServers", {})
302
314
  if not isinstance(mcp_servers, dict):
@@ -304,6 +316,12 @@ def _sync_json_client(path: Path, server_config: dict, label: str) -> dict:
304
316
  payload["mcpServers"] = mcp_servers
305
317
  action = "updated" if "nexo" in mcp_servers else "created"
306
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)
307
325
  _write_json_object(path, payload)
308
326
  return {
309
327
  "ok": True,
@@ -313,6 +331,18 @@ def _sync_json_client(path: Path, server_config: dict, label: str) -> dict:
313
331
  }
314
332
 
315
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
+
316
346
  def sync_claude_code(
317
347
  *,
318
348
  nexo_home: str | os.PathLike[str] | None = None,
@@ -361,10 +391,15 @@ def sync_claude_desktop(
361
391
  python_path=python_path,
362
392
  operator_name=operator_name,
363
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
+ )
364
398
  return _sync_json_client(
365
399
  _claude_desktop_config_path(Path(user_home).expanduser() if user_home else None),
366
400
  server_config,
367
401
  "claude_desktop",
402
+ managed_metadata=_claude_desktop_managed_metadata(server_config, operator_name=resolved_name),
368
403
  )
369
404
 
370
405
 
@@ -410,6 +445,7 @@ def sync_codex(
410
445
  config_path,
411
446
  bootstrap_prompt=prompt_text,
412
447
  runtime_profile=runtime_profile,
448
+ server_config=server_config,
413
449
  )
414
450
  return result
415
451
 
@@ -425,29 +461,12 @@ def sync_codex(
425
461
  timeout=30,
426
462
  env=env,
427
463
  )
428
- if result.returncode != 0:
429
- result = {
430
- "ok": False,
431
- "client": "codex",
432
- "path": str(config_path),
433
- "error": (result.stderr or result.stdout or "codex mcp add failed").strip(),
434
- }
435
- bootstrap_result = sync_client_bootstrap(
436
- "codex",
437
- nexo_home=nexo_home,
438
- operator_name=operator_name,
439
- user_home=user_home,
440
- )
441
- result["bootstrap"] = bootstrap_result
442
- if not bootstrap_result.get("ok"):
443
- result["error"] = f"{result['error']}; bootstrap: {bootstrap_result.get('error', 'unknown error')}"
444
- return result
445
464
  sync_result = {
446
465
  "ok": True,
447
466
  "client": "codex",
448
467
  "action": "updated",
449
468
  "path": str(config_path),
450
- "mode": "cli",
469
+ "mode": "cli" if result.returncode == 0 else "config_only",
451
470
  }
452
471
  bootstrap_result = sync_client_bootstrap(
453
472
  "codex",
@@ -464,7 +483,10 @@ def sync_codex(
464
483
  config_path,
465
484
  bootstrap_prompt=bootstrap_result.get("content") or "",
466
485
  runtime_profile=runtime_profile,
486
+ server_config=server_config,
467
487
  )
488
+ if result.returncode != 0:
489
+ sync_result["warning"] = (result.stderr or result.stdout or "codex mcp add failed").strip()
468
490
  return sync_result
469
491
 
470
492
 
@@ -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 = set(neighbor_boosts.keys()) - existing_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 new_neighbor_hashes:
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
- new_neighbor_hashes.discard(nh)
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: