nexo-brain 5.5.3 → 5.5.4

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": "5.5.3",
3
+ "version": "5.5.4",
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
@@ -18,9 +18,9 @@
18
18
 
19
19
  [Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
20
20
 
21
- Version `5.5.3` is the current packaged-runtime line: CLAUDE.md CORE now teaches the model to trust the Protocol Enforcer, so aligned backends stop rejecting heartbeat, diary, and checkpoint injections as suspected prompt injection.
21
+ Version `5.5.4` is the current packaged-runtime line: Deep Sleep no longer blocks on unparseable sessions reduced retries, added a JSON escape hatch, and unified the automation subprocess timeout to 3h across all scripts via a single shared constant.
22
22
 
23
- Previously in `5.4.6`: runtime dependency management in `nexo update` + daily auto-update cron.
23
+ Previously in `5.5.3`: CLAUDE.md CORE teaches the model to trust the Protocol Enforcer, so aligned backends stop rejecting heartbeat, diary, and checkpoint injections as suspected prompt injection.
24
24
 
25
25
  Start here:
26
26
  - [5-minute quickstart](docs/quickstart-5-minutes.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.5.3",
3
+ "version": "5.5.4",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO Brain \u2014 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",
@@ -0,0 +1,9 @@
1
+ """Shared constants for NEXO scripts and runtime."""
2
+ from __future__ import annotations
3
+
4
+ # Safety-net timeout (seconds) for Claude CLI / automation subprocess calls.
5
+ # Applied across deep-sleep, synthesis, immune, evolution, catchup, and other
6
+ # headless scripts that invoke the configured automation backend. Three hours
7
+ # is long enough for legitimate long runs but short enough to prevent zombie
8
+ # subprocesses from blocking the pipeline indefinitely.
9
+ AUTOMATION_SUBPROCESS_TIMEOUT = 10800
@@ -26,6 +26,7 @@ if str(NEXO_CODE) not in sys.path:
26
26
  sys.path.insert(0, str(NEXO_CODE))
27
27
 
28
28
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
29
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
29
30
  try:
30
31
  from client_preferences import resolve_user_model as _resolve_user_model
31
32
  _USER_MODEL = _resolve_user_model()
@@ -33,8 +34,9 @@ except Exception:
33
34
  _USER_MODEL = ""
34
35
 
35
36
 
36
- # No timeout -- headless automation can take as long as needed
37
- CLAUDE_TIMEOUT = 21600 # 3h safety net (prevents zombie processes)
37
+ # 3h safety net for the Claude CLI subprocess. Prevents zombie processes while
38
+ # still leaving enough headroom for legitimate long per-session extractions.
39
+ CLAUDE_TIMEOUT = AUTOMATION_SUBPROCESS_TIMEOUT
38
40
 
39
41
 
40
42
  def extract_json_from_response(text: str) -> dict | None:
@@ -129,7 +131,11 @@ def analyze_session(
129
131
  JSON_SYSTEM_PROMPT = (
130
132
  "You are a JSON-only analyst. Your ENTIRE response must be a single valid JSON object. "
131
133
  "No text before it. No text after it. No markdown fences. No explanations. "
132
- "If you want to summarize, put it inside the JSON fields. Start with { and end with }."
134
+ "If you want to summarize, put it inside the JSON fields. Start with { and end with }. "
135
+ "If for ANY reason you cannot comply with the requested schema (context too large, "
136
+ "file unreadable, ambiguous, uncertain), you MUST still return a JSON object shaped as "
137
+ '{"session_id":"<the id>","findings":[],"error":"cannot_comply","reason":"<short reason>"}. '
138
+ "NEVER return plain text, apology, markdown, or empty output."
133
139
  )
134
140
 
135
141
  result = run_automation_prompt(
@@ -249,7 +255,10 @@ def main():
249
255
  all_extractions = []
250
256
  total_findings = 0
251
257
  skipped = 0
252
- MAX_RETRIES = 3
258
+ # Two attempts is enough: if a session's extraction fails twice, the cause is
259
+ # almost always deterministic (JSON parse, schema violation) rather than transient,
260
+ # so further retries just burn time. Skip and continue instead.
261
+ MAX_RETRIES = 2
253
262
 
254
263
  for i, session_id in enumerate(session_files):
255
264
  sid_safe = _safe_session_slug(session_id)[:40]
@@ -34,8 +34,9 @@ if str(NEXO_CODE) not in sys.path:
34
34
  sys.path.insert(0, str(NEXO_CODE))
35
35
 
36
36
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
37
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
37
38
 
38
- CLAUDE_TIMEOUT = 21600 # 3h safety net (prevents zombie processes)
39
+ CLAUDE_TIMEOUT = AUTOMATION_SUBPROCESS_TIMEOUT
39
40
  ACTION_VERBS = {"add", "implement", "create", "write", "build", "enforce", "automate", "validate", "guard", "fix", "review"}
40
41
 
41
42
 
@@ -16,6 +16,7 @@ if str(NEXO_CODE) not in sys.path:
16
16
  sys.path.insert(0, str(NEXO_CODE))
17
17
 
18
18
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
19
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
19
20
 
20
21
 
21
22
  def _read_text(path: str | None) -> str:
@@ -32,7 +33,7 @@ def main(argv: list[str] | None = None) -> int:
32
33
  parser.add_argument("--task-profile", default="", help="Automation task profile: default|fast|balanced|deep")
33
34
  parser.add_argument("--model", default="", help="Backend model hint")
34
35
  parser.add_argument("--reasoning-effort", default="", help="Backend reasoning effort/profile")
35
- parser.add_argument("--timeout", type=int, default=21600, help="Timeout in seconds")
36
+ parser.add_argument("--timeout", type=int, default=AUTOMATION_SUBPROCESS_TIMEOUT, help="Timeout in seconds")
36
37
  parser.add_argument("--output-format", default="text", help="Requested output format")
37
38
  parser.add_argument("--allowed-tools", default="", help="Claude-style allowed tools contract")
38
39
  parser.add_argument("--append-system-prompt", default="", help="Extra system prompt text")
@@ -27,6 +27,7 @@ if str(_runtime_root) not in sys.path:
27
27
  sys.path.insert(0, str(_runtime_root))
28
28
 
29
29
  from agent_runner import AutomationBackendUnavailableError, probe_automation_backend, run_automation_prompt
30
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
30
31
  from cron_recovery import catchup_candidates
31
32
 
32
33
  HOME = Path.home()
@@ -175,7 +176,7 @@ def run_task(candidate: dict, state: dict) -> bool:
175
176
  try:
176
177
  result = subprocess.run(
177
178
  command,
178
- capture_output=True, text=True, timeout=21600,
179
+ capture_output=True, text=True, timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
179
180
  env={**os.environ, "HOME": str(HOME), "NEXO_CATCHUP": "1"}
180
181
  )
181
182
  if result.returncode == 0:
@@ -189,7 +190,7 @@ def run_task(candidate: dict, state: dict) -> bool:
189
190
  log(f" stderr: {result.stderr[:300]}")
190
191
  return False
191
192
  except subprocess.TimeoutExpired:
192
- log(f" TIMEOUT {name} (21600s)")
193
+ log(f" TIMEOUT {name} ({AUTOMATION_SUBPROCESS_TIMEOUT}s)")
193
194
  return False
194
195
  except Exception as e:
195
196
  log(f" ERROR {name}: {e}")
@@ -280,7 +281,7 @@ Format:
280
281
  result = run_automation_prompt(
281
282
  prompt,
282
283
  model=_USER_MODEL,
283
- timeout=21600,
284
+ timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
284
285
  output_format="text",
285
286
  allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
286
287
  )
@@ -37,6 +37,7 @@ if str(NEXO_CODE) not in sys.path:
37
37
  sys.path.insert(0, str(NEXO_CODE))
38
38
 
39
39
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
40
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
40
41
  import db as nexo_db
41
42
  from public_evolution_queue import queue_public_port_candidate
42
43
 
@@ -2050,7 +2051,7 @@ Also write the machine-readable summary to {LOG_DIR}/self-audit-summary.json.
2050
2051
  result = run_automation_prompt(
2051
2052
  prompt,
2052
2053
  model=_USER_MODEL,
2053
- timeout=21600,
2054
+ timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
2054
2055
  output_format="text",
2055
2056
  allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
2056
2057
  )
@@ -179,6 +179,7 @@ def log(msg: str):
179
179
  # ── Import from evolution_cycle.py (lives in NEXO_CODE, i.e. src/) ──────
180
180
  sys.path.insert(0, str(NEXO_CODE))
181
181
  from agent_runner import probe_automation_backend, run_automation_prompt
182
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
182
183
  from evolution_cycle import (
183
184
  load_objective, save_objective, get_week_data, build_evolution_prompt,
184
185
  dry_run_restore_test, max_auto_changes, create_snapshot,
@@ -214,7 +215,7 @@ def set_consecutive_failures(count: int):
214
215
 
215
216
 
216
217
  # ── Automation backend call ──────────────────────────────────────────────
217
- CLI_TIMEOUT = 21600 # 3h safety net (prevents zombie processes)
218
+ CLI_TIMEOUT = AUTOMATION_SUBPROCESS_TIMEOUT
218
219
 
219
220
 
220
221
  def verify_claude_cli() -> bool:
@@ -38,6 +38,7 @@ if str(NEXO_CODE) not in sys.path:
38
38
  sys.path.insert(0, str(NEXO_CODE))
39
39
 
40
40
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
41
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
41
42
 
42
43
  from urllib.request import Request, urlopen
43
44
  from urllib.error import URLError, HTTPError
@@ -916,7 +917,7 @@ Write the report. Be concise — max 40 lines."""
916
917
  result = run_automation_prompt(
917
918
  prompt,
918
919
  model=_USER_MODEL,
919
- timeout=21600,
920
+ timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
920
921
  output_format="text",
921
922
  allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
922
923
  )
@@ -37,6 +37,7 @@ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(_repo_src) if (_repo_src / "ser
37
37
  sys.path.insert(0, str(NEXO_CODE))
38
38
 
39
39
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
40
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
40
41
 
41
42
  try:
42
43
  from client_preferences import resolve_user_model as _resolve_user_model
@@ -255,7 +256,7 @@ Execute without asking."""
255
256
  result = run_automation_prompt(
256
257
  prompt,
257
258
  model=_USER_MODEL,
258
- timeout=21600,
259
+ timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
259
260
  output_format="text",
260
261
  allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
261
262
  )
@@ -37,6 +37,7 @@ if str(NEXO_CODE) not in sys.path:
37
37
  sys.path.insert(0, str(NEXO_CODE))
38
38
 
39
39
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
40
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
40
41
  try:
41
42
  from client_preferences import resolve_user_model as _resolve_user_model
42
43
  _USER_MODEL = _resolve_user_model()
@@ -446,7 +447,7 @@ Execute without asking."""
446
447
  result = run_automation_prompt(
447
448
  prompt,
448
449
  model=_USER_MODEL,
449
- timeout=21600,
450
+ timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
450
451
  output_format="text",
451
452
  allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
452
453
  )
@@ -34,6 +34,7 @@ if str(NEXO_CODE) not in sys.path:
34
34
  sys.path.insert(0, str(NEXO_CODE))
35
35
 
36
36
  from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
37
+ from constants import AUTOMATION_SUBPROCESS_TIMEOUT
37
38
 
38
39
  CLAUDE_DIR = NEXO_HOME
39
40
  COORD_DIR = CLAUDE_DIR / "coordination"
@@ -348,7 +349,7 @@ Execute without asking."""
348
349
  result = run_automation_prompt(
349
350
  prompt,
350
351
  model=_USER_MODEL,
351
- timeout=21600,
352
+ timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
352
353
  output_format="text",
353
354
  allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
354
355
  )