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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/constants.py +9 -0
- package/src/scripts/deep-sleep/extract.py +13 -4
- package/src/scripts/deep-sleep/synthesize.py +2 -1
- package/src/scripts/nexo-agent-run.py +2 -1
- package/src/scripts/nexo-catchup.py +4 -3
- package/src/scripts/nexo-daily-self-audit.py +2 -1
- package/src/scripts/nexo-evolution-run.py +2 -1
- package/src/scripts/nexo-immune.py +2 -1
- package/src/scripts/nexo-postmortem-consolidator.py +2 -1
- package/src/scripts/nexo-sleep.py +2 -1
- package/src/scripts/nexo-synthesis.py +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.5.
|
|
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.
|
|
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.
|
|
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
|
+
"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",
|
package/src/constants.py
ADDED
|
@@ -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
|
-
#
|
|
37
|
-
|
|
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
|
-
|
|
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 =
|
|
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=
|
|
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=
|
|
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} (
|
|
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=
|
|
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=
|
|
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 =
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
352
|
+
timeout=AUTOMATION_SUBPROCESS_TIMEOUT,
|
|
352
353
|
output_format="text",
|
|
353
354
|
allowed_tools="Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*",
|
|
354
355
|
)
|