nexo-brain 3.0.1 → 3.1.0

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.
Files changed (37) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +82 -0
  3. package/community/launch/2026-04-v3-0-2/case-study-outreach.md +36 -0
  4. package/community/launch/2026-04-v3-0-2/devto-v3-0-2.md +91 -0
  5. package/community/launch/2026-04-v3-0-2/github-discussion-v3-0-2.md +58 -0
  6. package/community/launch/2026-04-v3-0-2/x-thread-v3-0-2.md +60 -0
  7. package/hooks/hooks.json +12 -0
  8. package/package.json +1 -1
  9. package/src/auto_update.py +23 -6
  10. package/src/client_sync.py +6 -0
  11. package/src/cognitive/_memory.py +14 -7
  12. package/src/cognitive/_search.py +12 -5
  13. package/src/crons/sync.py +2 -1
  14. package/src/doctor/models.py +25 -0
  15. package/src/doctor/orchestrator.py +32 -2
  16. package/src/doctor/providers/boot.py +7 -7
  17. package/src/doctor/providers/deep.py +24 -21
  18. package/src/doctor/providers/runtime.py +154 -137
  19. package/src/evolution_cycle.py +76 -47
  20. package/src/hook_guardrails.py +182 -0
  21. package/src/hooks/protocol-guardrail.sh +1 -1
  22. package/src/hooks/protocol-pretool-guardrail.sh +9 -0
  23. package/src/kg_populate.py +21 -19
  24. package/src/maintenance.py +3 -3
  25. package/src/migrate_embeddings.py +36 -34
  26. package/src/plugins/backup.py +24 -12
  27. package/src/plugins/protocol.py +15 -0
  28. package/src/plugins/schedule.py +13 -1
  29. package/src/plugins/update.py +18 -4
  30. package/src/protocol_settings.py +59 -0
  31. package/src/public_contribution.py +10 -14
  32. package/src/public_evolution_queue.py +241 -0
  33. package/src/scripts/nexo-catchup.py +15 -15
  34. package/src/scripts/nexo-daily-self-audit.py +677 -28
  35. package/src/scripts/nexo-evolution-run.py +44 -4
  36. package/src/server.py +26 -1
  37. package/src/state_watchers_runtime.py +42 -35
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
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
@@ -20,12 +20,16 @@
20
20
 
21
21
  Start here:
22
22
  - [5-minute quickstart](docs/quickstart-5-minutes.md)
23
+ - [Workflow quickstart](docs/workflows-quickstart.md)
24
+ - [Supported client guides](docs/integrations/cursor.md)
25
+ - [Docker setup](docs/docker-setup.md)
23
26
  - [Architecture visuals](docs/architecture-visuals.md)
24
27
  - [Memory classes](docs/memory-classes.md)
25
28
  - [Session portability](docs/session-portability.md)
26
29
  - [Python SDK](docs/sdk-python.md)
27
30
  - [Reference verticals](docs/reference-verticals.md)
28
31
  - [Measured compare scorecard](compare/README.md)
32
+ - [Memory benchmark harness](benchmarks/README.md)
29
33
  - [Public contribution guide](docs/public-contribution.md)
30
34
 
31
35
  Every time you close a session, everything is lost. Your agent doesn't remember yesterday's decisions, repeats the same mistakes, and starts from zero. NEXO Brain fixes this with a cognitive architecture modeled after how human memory actually works.
@@ -89,6 +93,17 @@ Versions `3.0.0` and `3.0.1` close the next execution gap:
89
93
  | Runtime doctor parity audit | Yes | Yes | Shared-brain only |
90
94
  | Recommended today | Yes | Supported | Shared-brain companion |
91
95
 
96
+ ### Supported Clients
97
+
98
+ | Client | Status | Integration style | Notes |
99
+ |--------|--------|-------------------|-------|
100
+ | Claude Code | First-class | Managed install + hooks + bootstrap | Deepest NEXO parity today |
101
+ | Codex | First-class | Managed install + bootstrap + transcript parity | Best non-Claude terminal path |
102
+ | Claude Desktop | Companion | MCP-only shared brain | Useful as read/chat companion |
103
+ | Cursor | Documented companion | MCP + `.cursor/rules` | Good editor pairing; no Deep Sleep transcript parity yet |
104
+ | Windsurf | Documented companion | MCP + `.windsurf/rules` or repo `AGENTS.md` | Native MCP support, manual companion mode |
105
+ | Gemini CLI | Adapter included | MCP + `GEMINI.md` | Best when you want Gemini as a shared-brain companion, not the primary NEXO runtime |
106
+
92
107
  ## The Problem
93
108
 
94
109
  AI coding agents are powerful but amnesic:
@@ -669,6 +684,27 @@ The installer handles everything and syncs the same `nexo` MCP brain into Claude
669
684
  +----------------------------------------------------------+
670
685
  ```
671
686
 
687
+ ### Docker Compose
688
+
689
+ NEXO now ships a root-level [`docker-compose.yml`](docs/docker-setup.md) for a persistent containerized runtime. It does two things at once:
690
+
691
+ - keeps `NEXO_HOME` on a named volume
692
+ - exposes a remote MCP endpoint at `http://localhost:8000/mcp` for IDEs that support HTTP/SSE MCP
693
+
694
+ Start it with:
695
+
696
+ ```bash
697
+ docker compose up -d
698
+ ```
699
+
700
+ For Claude Code and Codex, keep using stdio and point the MCP command at the running container:
701
+
702
+ ```bash
703
+ docker compose exec -T nexo python src/server.py
704
+ ```
705
+
706
+ That gives you the same persistent brain in the container while keeping terminal clients on their native stdio transport. The full step-by-step flow, health checks, and config examples live in [docs/docker-setup.md](docs/docker-setup.md).
707
+
672
708
  ### Starting a Session
673
709
 
674
710
  After install, use the runtime CLI:
@@ -896,6 +932,18 @@ When Claude Desktop is installed, `nexo-brain`, `nexo update`, and `nexo clients
896
932
 
897
933
  When Codex CLI is available, `nexo-brain`, `nexo update`, and `nexo clients sync` register the same `nexo` MCP server via `codex mcp add`, so Codex uses the same local memory store as Claude Code and Claude Desktop. If selected during install, `nexo chat` can open Codex directly and background automation can also run through Codex. Interactive `nexo chat` launches use Codex's aggressive no-confirmation mode so the session does not stall on repetitive approval prompts. The current recommended Codex profile is `gpt-5.4` with `xhigh` reasoning. Runtime Doctor also audits recent Codex sessions for NEXO startup markers and conditioned-file protocol discipline so parity drift does not hide behind the lack of native Claude-style hooks.
898
934
 
935
+ ### Cursor
936
+
937
+ Cursor works well as a documented companion client. Point Cursor at the same local `nexo` MCP server and add a project rule that forces `nexo_startup`, `nexo_heartbeat`, and the protocol path on real work. See [docs/integrations/cursor.md](docs/integrations/cursor.md).
938
+
939
+ ### Windsurf
940
+
941
+ Windsurf/Cascade supports MCP plus durable repo rules. Use the same local `nexo` server and add NEXO startup/protocol instructions in `.windsurf/rules/` or your repo `AGENTS.md`. See [docs/integrations/windsurf.md](docs/integrations/windsurf.md).
942
+
943
+ ### Gemini CLI
944
+
945
+ Gemini CLI can share the same local NEXO brain through `mcpServers` in `~/.gemini/settings.json` plus a repo `GEMINI.md`. NEXO now ships a starter adapter in [adapters/gemini/README.md](adapters/gemini/README.md).
946
+
899
947
  ### OpenClaw
900
948
 
901
949
  NEXO Brain also works as a cognitive memory backend for [OpenClaw](https://github.com/openclaw/openclaw):
@@ -980,8 +1028,42 @@ If NEXO Brain is useful to you, consider:
980
1028
 
981
1029
  [![Star History Chart](https://api.star-history.com/svg?repos=wazionapps/nexo&type=Date)](https://star-history.com/#wazionapps/nexo&Date)
982
1030
 
1031
+ ## Memory Benchmark Snapshot
1032
+
1033
+ The full harness is in [benchmarks/README.md](benchmarks/README.md). The first checked-in micro-benchmark compares the NEXO runtime against a static `CLAUDE.md`-only baseline on five recall-heavy scenarios:
1034
+
1035
+ | Scenario | NEXO full stack | Static `CLAUDE.md` | No memory |
1036
+ |----------|-----------------|--------------------|-----------|
1037
+ | Decision rationale recall | Pass | Partial | Fail |
1038
+ | User preference recall | Pass | Partial | Fail |
1039
+ | Repeat-error avoidance | Pass | Partial | Fail |
1040
+ | Resume interrupted task | Pass | Partial | Fail |
1041
+ | Related-context stitching | Pass | Fail | Fail |
1042
+
1043
+ See [benchmarks/results/memory-recall-vs-static.md](benchmarks/results/memory-recall-vs-static.md) for the rubric, prompt shape, and first-run notes.
1044
+
983
1045
  ## Changelog
984
1046
 
1047
+ ### v3.0.1 — Python 3.10 Compatibility Patch (2026-04-06)
1048
+ - Restored Python 3.10 compatibility by replacing Python 3.11-only `datetime.UTC` with `timezone.utc`.
1049
+ - Added `tomllib` → `tomli` fallback plus declared runtime dependency for Python < 3.11.
1050
+ - Boot doctor now validates all critical JSON config artifacts: `schedule.json`, `optionals.json`, `crons/manifest.json`.
1051
+
1052
+ ### v3.0.0 — Protocol Discipline, Durable Execution, Measured Runtime (2026-04-06)
1053
+ - **Protocol discipline runtime**: Enforceable `nexo_task_open`/`nexo_task_close`, persistent `protocol_debt`, `Cortex` gates with durable `check_id`, conditioned-file guardrails across Claude hooks and Codex transcript audits.
1054
+ - **Durable workflow runtime**: `nexo_workflow_open`/`update`/`resume`/`replay`/`list` with persistent runs, steps, checkpoints, replay history, retry bookkeeping, and idempotent open keys.
1055
+ - **Durable goals**: `nexo_goal_open`/`update`/`get`/`list` for long-running work that stays active/blocked/abandoned/completed.
1056
+ - **Operational truth**: Deep Sleep survives schema drift, `keep_alive` reports alive/degraded/duplicated honestly, warning storms no longer count as healthy.
1057
+ - **Measured product surface**: 5-minute quickstart, Python SDK, reference verticals, measured compare scorecard with LoCoMo baselines and `cost_per_solved_task`.
1058
+ - **Skill lifecycle**: Testing, promotion, retirement, and composition flows. Evolution public-core peer-review for opt-in PRs.
1059
+
1060
+ ### v2.7.0 — Shared Brain Baseline (2026-04-06)
1061
+ - Managed Claude Code + Codex bootstrap with explicit `CORE`/`USER` contract.
1062
+ - Codex config sync and transcript-aware Deep Sleep across both clients.
1063
+ - 60-day long-horizon analysis, weekly/monthly summary artifacts.
1064
+ - Retrieval auto-mode and first measured engineering loop.
1065
+ - `nexo chat` opens the configured client instead of assuming Claude Code.
1066
+
985
1067
  ### v2.6.9 — Integration Sync, CI/CD Pipeline (2026-04-04)
986
1068
  - **Release artifact sync**: Automated version synchronization across Claude Code plugin, OpenClaw package, and ClawHub skill before every publish.
987
1069
  - **CI/CD pipeline**: Full GitHub Actions workflow for publish + verification of all integration channels.
@@ -0,0 +1,36 @@
1
+ # NEXO case-study outreach template
2
+
3
+ Use this to ask operators for a public NEXO story without making the ask heavy.
4
+
5
+ ## Short outreach message
6
+
7
+ Hi,
8
+
9
+ I am collecting a few public NEXO operator stories so the project can show real-world usage more clearly.
10
+
11
+ If NEXO has been useful in your setup, would you be open to sending a short note with:
12
+
13
+ - your main client surface
14
+ - what problem you wanted to solve
15
+ - which NEXO pieces actually mattered
16
+ - one concrete example of how it helped
17
+
18
+ Even 5-8 lines is enough. If you prefer, I can turn it into a draft and send it back for approval before anything is published.
19
+
20
+ Thanks.
21
+
22
+ ## Structured version
23
+
24
+ Ask for:
25
+
26
+ - Role or setup
27
+ - Main client or clients
28
+ - What was painful before NEXO
29
+ - What changed after using NEXO
30
+ - Which feature or workflow mattered most
31
+ - Whether the story can be quoted publicly
32
+
33
+ ## Public proof rule
34
+
35
+ Do not fabricate company names, usage scale, or testimonials.
36
+ If a case is not approved for public quoting, keep it private until the operator confirms.
@@ -0,0 +1,91 @@
1
+ # NEXO v3.0.2: libre shared brain, review-gated Evolution, and a cleaner public path
2
+
3
+ NEXO is free/libre, open-source software for people who want a local cognitive runtime instead of another isolated AI session.
4
+
5
+ At the center is a simple promise:
6
+
7
+ - one shared brain across Claude Code, Codex, Claude Desktop, and other MCP-compatible clients
8
+ - persistent local memory instead of session-only context
9
+ - a real runtime around memory: doctor, workflows, reminders, scripts, followups, and review-gated Evolution
10
+
11
+ With `v3.0.2`, the product itself is not radically different, but the public path is much clearer.
12
+
13
+ ## What changed in this release
14
+
15
+ Three things matter most:
16
+
17
+ 1. Public integrations and release publishing were hardened.
18
+ 2. The public site now explains NEXO faster, with demo-first entry points and sharper search-intent pages.
19
+ 3. The runtime keeps moving toward a more disciplined public Evolution loop, where opt-in contributors can propose bounded changes via Draft PRs and maintainers still decide what gets merged.
20
+
21
+ ## Why "libre" matters here
22
+
23
+ NEXO is not just "free as in price."
24
+
25
+ It is AGPL-licensed, inspectable, forkable, and local-first by design. That matters because memory systems are infrastructure. If your agent remembers project state, release habits, and past failures, you should be able to inspect the system that stores and retrieves that memory.
26
+
27
+ There is no hidden hosted control plane behind the product story.
28
+
29
+ ## The shortest way to understand NEXO now
30
+
31
+ Instead of sending everyone straight into a huge feature map, the public site now starts with three short demos:
32
+
33
+ - Install in 60 seconds
34
+ - Shared brain across Claude Code and Codex
35
+ - Review-gated public Evolution with Draft PR pause/resume behavior
36
+
37
+ There are also three search-intent pages for:
38
+
39
+ - Claude Code memory
40
+ - Codex memory
41
+ - Open-source MCP memory server
42
+
43
+ That matters because most people do not search for "cognitive runtime." They search for a concrete need.
44
+
45
+ ## What NEXO is best at today
46
+
47
+ The strongest fit today is still the operator who wants:
48
+
49
+ - local persistent memory
50
+ - a shared brain across more than one client
51
+ - guardrails before changes
52
+ - durable workflows and followups
53
+ - an inspectable runtime instead of a black box
54
+
55
+ Claude Code remains the deepest integration path.
56
+ Codex is also supported and benefits from the same shared-brain model.
57
+
58
+ ## Install
59
+
60
+ ```bash
61
+ npx nexo-brain
62
+ ```
63
+
64
+ After install, the fastest sanity check is:
65
+
66
+ ```bash
67
+ nexo doctor
68
+ ```
69
+
70
+ ## Public links
71
+
72
+ - Release: https://github.com/wazionapps/nexo/releases/tag/v3.0.2
73
+ - Home: https://nexo-brain.com
74
+ - Demos: https://nexo-brain.com/demos/
75
+ - Solutions: https://nexo-brain.com/solutions/
76
+ - Docs: https://nexo-brain.com/docs/
77
+ - GitHub: https://github.com/wazionapps/nexo
78
+ - npm: https://www.npmjs.com/package/nexo-brain
79
+
80
+ ## What comes next
81
+
82
+ The next public step is not "more adjectives."
83
+
84
+ It is more proof:
85
+
86
+ - more operator case studies
87
+ - more demo material
88
+ - more high-intent entry pages
89
+ - more disciplined public Evolution cycles that generate reviewable improvements
90
+
91
+ If you use Claude Code, Codex, or other MCP-compatible clients and want a local shared brain rather than another stateless session, NEXO is ready to try now.
@@ -0,0 +1,58 @@
1
+ # NEXO v3.0.2 is out: libre shared brain, cleaner demos, and sharper public entry points
2
+
3
+ `v3.0.2` is now published:
4
+
5
+ - Release: https://github.com/wazionapps/nexo/releases/tag/v3.0.2
6
+ - npm: https://www.npmjs.com/package/nexo-brain
7
+ - Site: https://nexo-brain.com
8
+
9
+ This release matters less because of one giant feature and more because the public product story is now much easier to understand.
10
+
11
+ ## What NEXO is
12
+
13
+ NEXO is libre/open-source software for a local cognitive runtime with:
14
+
15
+ - one shared brain across Claude Code, Codex, Claude Desktop, and other MCP-compatible clients
16
+ - persistent local memory
17
+ - doctor diagnostics, workflows, scripts, reminders, followups, and public Evolution discipline around that memory layer
18
+
19
+ ## What changed
20
+
21
+ - release and public integration verification were hardened
22
+ - the public website now has a demo-first path
23
+ - new search-intent pages explain where NEXO fits without forcing everyone through the full docs first
24
+
25
+ New public surfaces:
26
+
27
+ - Demos: https://nexo-brain.com/demos/
28
+ - Solutions: https://nexo-brain.com/solutions/
29
+ - Use cases: https://nexo-brain.com/use-cases/
30
+
31
+ ## The three shortest demos
32
+
33
+ 1. Install in 60 seconds
34
+ 2. Shared brain across Claude Code and Codex
35
+ 3. Review-gated public Evolution with Draft PR pause/resume behavior
36
+
37
+ ## Why this direction
38
+
39
+ The old problem was not lack of capability. It was that the path from "I heard about NEXO" to "I understand what it actually does" was too long.
40
+
41
+ The new public surfaces are designed to fix that while keeping the product technically honest:
42
+
43
+ - local-first
44
+ - inspectable
45
+ - AGPL-licensed
46
+ - stronger for real operators than for generic AI hype
47
+
48
+ ## If you are already using NEXO
49
+
50
+ The most useful thing right now is public proof.
51
+
52
+ If you have a real setup or workflow where NEXO helps, reply here or open a new discussion with:
53
+
54
+ - your main client surface
55
+ - the problem you wanted to solve
56
+ - which NEXO pieces actually mattered
57
+
58
+ That will help turn the current honest use-case patterns into stronger public case studies.
@@ -0,0 +1,60 @@
1
+ 1. NEXO v3.0.2 is out.
2
+
3
+ Libre/open-source shared brain for Claude Code, Codex, Claude Desktop, and other MCP-compatible clients.
4
+
5
+ Local memory, workflows, doctor, followups, scripts, and review-gated Evolution.
6
+
7
+ Release:
8
+ https://github.com/wazionapps/nexo/releases/tag/v3.0.2
9
+
10
+ 2. The main idea is simple:
11
+
12
+ NEXO is not just "memory for AI."
13
+
14
+ It is a local cognitive runtime so your assistant can keep operator context across sessions instead of resetting every time the terminal resets.
15
+
16
+ 3. We also cleaned up the public path.
17
+
18
+ Three short demos now explain the product faster than a feature list:
19
+
20
+ - install in 60 seconds
21
+ - shared brain across Claude Code + Codex
22
+ - review-gated public Evolution
23
+
24
+ https://nexo-brain.com/demos/
25
+
26
+ 4. We added sharper search-intent pages too:
27
+
28
+ - Claude Code memory
29
+ - Codex memory
30
+ - open-source MCP memory server
31
+
32
+ https://nexo-brain.com/solutions/
33
+
34
+ 5. NEXO is AGPL-3.0 and local-first by design.
35
+
36
+ If your assistant remembers your work, you should be able to inspect and fork the system behind that memory.
37
+
38
+ https://github.com/wazionapps/nexo
39
+
40
+ 6. Strongest fit today:
41
+
42
+ - operators using Claude Code or Codex
43
+ - people who want one shared brain across clients
44
+ - local-first workflows
45
+ - teams that want real runtime discipline, not just retrieval
46
+
47
+ 7. If you want to try it:
48
+
49
+ ```bash
50
+ npx nexo-brain
51
+ nexo doctor
52
+ ```
53
+
54
+ 8. If you already use it, the next thing we need is public proof.
55
+
56
+ Real setups.
57
+ Real use cases.
58
+ Real feedback.
59
+
60
+ That is how the project gets easier to trust.
package/hooks/hooks.json CHANGED
@@ -38,6 +38,18 @@
38
38
  ]
39
39
  }
40
40
  ],
41
+ "PreToolUse": [
42
+ {
43
+ "matcher": "Edit|MultiEdit|Write|Delete",
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "NEXO_HOME=\"${CLAUDE_PLUGIN_DATA}\" NEXO_CODE=\"${CLAUDE_PLUGIN_ROOT}/src\" bash \"${CLAUDE_PLUGIN_ROOT}/src/hooks/protocol-pretool-guardrail.sh\"",
48
+ "timeout": 5
49
+ }
50
+ ]
51
+ }
52
+ ],
41
53
  "PostToolUse": [
42
54
  {
43
55
  "matcher": "*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
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",
@@ -311,14 +311,21 @@ def _backup_dbs() -> str | None:
311
311
 
312
312
  backup_dir.mkdir(parents=True, exist_ok=True)
313
313
  for db_file in db_files:
314
+ src_conn = None
315
+ dst_conn = None
314
316
  try:
315
317
  src_conn = sqlite3.connect(str(db_file))
316
318
  dst_conn = sqlite3.connect(str(backup_dir / db_file.name))
317
319
  src_conn.backup(dst_conn)
318
- dst_conn.close()
319
- src_conn.close()
320
320
  except Exception as e:
321
321
  _log(f"DB backup warning ({db_file.name}): {e}")
322
+ finally:
323
+ for conn in (dst_conn, src_conn):
324
+ if conn is not None:
325
+ try:
326
+ conn.close()
327
+ except Exception:
328
+ pass
322
329
  return str(backup_dir)
323
330
 
324
331
 
@@ -331,15 +338,22 @@ def _restore_dbs(backup_dir: str):
331
338
  for db_backup in bdir.glob("*.db"):
332
339
  for candidate in [DATA_DIR / db_backup.name, NEXO_HOME / db_backup.name, SRC_DIR / db_backup.name]:
333
340
  if candidate.is_file():
341
+ src_conn = None
342
+ dst_conn = None
334
343
  try:
335
344
  src_conn = sqlite3.connect(str(db_backup))
336
345
  dst_conn = sqlite3.connect(str(candidate))
337
346
  src_conn.backup(dst_conn)
338
- dst_conn.close()
339
- src_conn.close()
340
347
  _log(f"Restored DB: {db_backup.name}")
341
348
  except Exception as e:
342
349
  _log(f"DB restore warning ({db_backup.name}): {e}")
350
+ finally:
351
+ for conn in (dst_conn, src_conn):
352
+ if conn is not None:
353
+ try:
354
+ conn.close()
355
+ except Exception:
356
+ pass
343
357
  break
344
358
 
345
359
 
@@ -626,6 +640,10 @@ def _run_file_migration(path: Path) -> tuple[bool, str]:
626
640
  def run_file_migrations() -> list[dict]:
627
641
  """Run any pending file-based migrations from the migrations/ directory.
628
642
 
643
+ Migrations are ordered and sequential: if migration N fails, all subsequent
644
+ migrations are skipped so that N is retried on the next startup and no
645
+ migration is permanently skipped by a version-pointer gap.
646
+
629
647
  Returns list of results: [{"version": N, "file": "...", "status": "ok"|"failed", "message": "..."}]
630
648
  """
631
649
  current_version = _get_applied_migration_version()
@@ -655,8 +673,7 @@ def run_file_migrations() -> list[dict]:
655
673
  "message": message,
656
674
  })
657
675
  _log(f"Migration {path.name}: FAILED — {message}")
658
- # Don't advance version past a failure, but continue trying others
659
- # so independent migrations still run. Version stays at last success.
676
+ break # Stop on first failure so it retries next startup
660
677
 
661
678
  return results
662
679
 
@@ -342,6 +342,12 @@ CORE_HOOK_SPECS = [
342
342
  "timeout": 10,
343
343
  "script": "session-stop.sh",
344
344
  },
345
+ {
346
+ "event": "PreToolUse",
347
+ "identity": "protocol-pretool-guardrail.sh",
348
+ "timeout": 5,
349
+ "script": "protocol-pretool-guardrail.sh",
350
+ },
345
351
  {
346
352
  "event": "PostToolUse",
347
353
  "identity": "capture-tool-logs.sh",
@@ -1,7 +1,14 @@
1
1
  """NEXO Cognitive — Memory operations: format, stats, consolidation, somatic."""
2
2
  import json, math, re
3
3
  import numpy as np
4
- from datetime import datetime, timedelta
4
+ from datetime import datetime, timedelta, timezone
5
+
6
+
7
+ def _utcnow_naive() -> datetime:
8
+ """Timezone-aware UTC clock returned as a naive datetime to preserve
9
+ the legacy ``datetime.utcnow()`` string format on disk.
10
+ """
11
+ return datetime.now(timezone.utc).replace(tzinfo=None)
5
12
  from cognitive._core import _get_db, embed, cosine_similarity, _blob_to_array, _array_to_blob, EMBEDDING_DIM, DISCRIMINATING_ENTITIES
6
13
  from cognitive._ingest import _sanitize_memory_content
7
14
 
@@ -74,7 +81,7 @@ def get_metrics(days: int = 7) -> dict:
74
81
  score_distribution: histogram buckets [<0.5, 0.5-0.6, 0.6-0.7, 0.7-0.8, >0.8]
75
82
  """
76
83
  db = _get_db()
77
- cutoff = (datetime.utcnow() - timedelta(days=days)).isoformat()
84
+ cutoff = (_utcnow_naive() - timedelta(days=days)).isoformat()
78
85
 
79
86
  rows = db.execute(
80
87
  "SELECT top_score FROM retrieval_log WHERE created_at >= ?", (cutoff,)
@@ -130,7 +137,7 @@ def check_repeat_errors() -> dict:
130
137
  Returns count of new learnings that are semantically duplicate (cosine > 0.8).
131
138
  """
132
139
  db = _get_db()
133
- cutoff_7d = (datetime.utcnow() - timedelta(days=7)).isoformat()
140
+ cutoff_7d = (_utcnow_naive() - timedelta(days=7)).isoformat()
134
141
 
135
142
  # Recent learning STM entries
136
143
  new_learnings = db.execute(
@@ -192,7 +199,7 @@ def rehearse_by_content(content_keywords: str, source_type: str = ""):
192
199
  if np.linalg.norm(query_vec) == 0:
193
200
  return
194
201
 
195
- now = datetime.utcnow().isoformat()
202
+ now = _utcnow_naive().isoformat()
196
203
 
197
204
  # Search both stores for matches >= 0.7
198
205
  for table in ("stm_memories", "ltm_memories"):
@@ -803,7 +810,7 @@ def security_scan(content: str) -> dict:
803
810
  def somatic_accumulate(target: str, target_type: str, delta: float):
804
811
  """Increase risk_score for a target (file or area). Capped at 1.0."""
805
812
  db = _get_db()
806
- now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
813
+ now = _utcnow_naive().strftime("%Y-%m-%dT%H:%M:%S")
807
814
  existing = db.execute(
808
815
  "SELECT id, risk_score, incident_count FROM somatic_markers WHERE target = ? AND target_type = ?",
809
816
  (target, target_type)
@@ -827,8 +834,8 @@ def somatic_accumulate(target: str, target_type: str, delta: float):
827
834
  def somatic_guard_decay(target: str, target_type: str):
828
835
  """Validated recovery: multiplicative x0.7 on successful guard check. Max once/day/target."""
829
836
  db = _get_db()
830
- today = datetime.utcnow().strftime("%Y-%m-%d")
831
- now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
837
+ today = _utcnow_naive().strftime("%Y-%m-%d")
838
+ now = _utcnow_naive().strftime("%Y-%m-%dT%H:%M:%S")
832
839
  row = db.execute(
833
840
  "SELECT id, risk_score, last_guard_decay_date FROM somatic_markers WHERE target = ? AND target_type = ?",
834
841
  (target, target_type)
@@ -3,7 +3,14 @@ import math
3
3
  import re
4
4
  import sqlite3
5
5
  import numpy as np
6
- from datetime import datetime
6
+ from datetime import datetime, timezone
7
+
8
+
9
+ def _utcnow_naive() -> datetime:
10
+ """Timezone-aware UTC clock returned as a naive datetime to preserve
11
+ the legacy ``datetime.utcnow()`` string format on disk.
12
+ """
13
+ return datetime.now(timezone.utc).replace(tzinfo=None)
7
14
  from cognitive._core import (
8
15
  _get_db, embed, cosine_similarity, _blob_to_array, _array_to_blob,
9
16
  _get_model, _get_reranker, rerank_results, EMBEDDING_DIM,
@@ -461,7 +468,7 @@ def record_co_activation(memory_ids: list[tuple[str, int]]):
461
468
  return
462
469
 
463
470
  db = _get_db()
464
- now = datetime.utcnow().isoformat()
471
+ now = _utcnow_naive().isoformat()
465
472
 
466
473
  hashes = [_canonical_co_id(store, mid) for store, mid in memory_ids]
467
474
 
@@ -571,7 +578,7 @@ def _match_triggers(
571
578
  text_vec = embed(text)
572
579
 
573
580
  matched_triggers = []
574
- now = datetime.utcnow().isoformat()
581
+ now = _utcnow_naive().isoformat()
575
582
 
576
583
  for trigger in armed:
577
584
  pattern = trigger["trigger_pattern"].lower()
@@ -667,7 +674,7 @@ def rearm_trigger(trigger_id: int) -> str:
667
674
 
668
675
  def _auto_restore_snoozed(db: sqlite3.Connection):
669
676
  """Restore snoozed memories whose snooze_until date has passed."""
670
- now = datetime.utcnow().isoformat()
677
+ now = _utcnow_naive().isoformat()
671
678
  for table in ("stm_memories", "ltm_memories"):
672
679
  db.execute(
673
680
  f"UPDATE {table} SET lifecycle_state = 'active', snooze_until = NULL "
@@ -682,7 +689,7 @@ def _rehearse_results(results: list[dict], skip_ids: set = None):
682
689
  if not results:
683
690
  return
684
691
  db = _get_db()
685
- now = datetime.utcnow().isoformat()
692
+ now = _utcnow_naive().isoformat()
686
693
  skip = skip_ids or set()
687
694
  for r in results:
688
695
  if (r["store"], r["id"]) in skip:
package/src/crons/sync.py CHANGED
@@ -446,7 +446,8 @@ StandardError=append:{stderr_log}
446
446
  s = cron["schedule"]
447
447
  h, m = s.get("hour", 0), s.get("minute", 0)
448
448
  if "weekday" in s:
449
- days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
449
+ # Manifest weekday uses launchd convention: 0=Sunday 6=Saturday (7=Sunday alias)
450
+ days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
450
451
  timer_spec = f"OnCalendar={days[s['weekday']]} *-*-* {h:02d}:{m:02d}:00"
451
452
  else:
452
453
  timer_spec = f"OnCalendar=*-*-* {h:02d}:{m:02d}:00"
@@ -1,7 +1,9 @@
1
1
  """Doctor data models — check results and report structure."""
2
2
  from __future__ import annotations
3
3
 
4
+ import traceback
4
5
  from dataclasses import dataclass, field
6
+ from typing import Callable
5
7
 
6
8
 
7
9
  @dataclass
@@ -42,3 +44,26 @@ class DoctorReport:
42
44
  "critical": statuses.count("critical"),
43
45
  "total": len(statuses),
44
46
  }
47
+
48
+
49
+ def safe_check(fn: Callable[..., DoctorCheck], *args, **kwargs) -> DoctorCheck:
50
+ """Run a single check function, returning a crash DoctorCheck on exception.
51
+
52
+ This isolates individual checks so one failure doesn't take down
53
+ all sibling checks within a tier.
54
+ """
55
+ try:
56
+ return fn(*args, **kwargs)
57
+ except Exception as exc:
58
+ tb = traceback.format_exception(type(exc), exc, exc.__traceback__)
59
+ last_frame = tb[-1].strip() if tb else str(exc)
60
+ check_name = getattr(fn, "__name__", "unknown")
61
+ return DoctorCheck(
62
+ id=f"check.{check_name}_crashed",
63
+ tier="unknown",
64
+ status="critical",
65
+ severity="error",
66
+ summary=f"Check {check_name} crashed: {type(exc).__name__}: {exc}",
67
+ evidence=[last_frame],
68
+ repair_plan=[f"Investigate {check_name} — exception during check execution"],
69
+ )