nexo-brain 1.4.1 → 1.5.1
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/README.md +63 -12
- package/package.json +7 -3
- package/src/claim_graph.py +323 -0
- package/src/cognitive/__init__.py +62 -0
- package/src/cognitive/_core.py +563 -0
- package/src/cognitive/_decay.py +382 -0
- package/src/cognitive/_ingest.py +892 -0
- package/src/cognitive/_memory.py +907 -0
- package/src/cognitive/_search.py +947 -0
- package/src/cognitive/_trust.py +464 -0
- package/src/db/__init__.py +88 -0
- package/src/db/_core.py +413 -0
- package/src/db/_credentials.py +124 -0
- package/src/db/_entities.py +178 -0
- package/src/db/_episodic.py +670 -0
- package/src/db/_evolution.py +54 -0
- package/src/db/_fts.py +406 -0
- package/src/db/_learnings.py +168 -0
- package/src/db/_reminders.py +260 -0
- package/src/db/_schema.py +323 -0
- package/src/db/_sessions.py +300 -0
- package/src/db/_tasks.py +91 -0
- package/src/hnsw_index.py +254 -0
- package/src/plugins/agents.py +11 -11
- package/src/plugins/backup.py +3 -3
- package/src/plugins/cognitive_memory.py +9 -9
- package/src/plugins/episodic_memory.py +70 -70
- package/src/plugins/evolution.py +4 -4
- package/src/plugins/guard.py +2 -2
- package/src/scripts/deep-sleep/analyze_session.py +215 -0
- package/src/scripts/deep-sleep/apply_findings.py +167 -0
- package/src/scripts/deep-sleep/collect_transcripts.py +143 -0
- package/src/scripts/deep-sleep/prompt.md +109 -0
- package/src/scripts/nexo-deep-sleep.sh +76 -0
- package/src/scripts/nexo-inbox-hook.sh +73 -0
- package/src/scripts/nexo-watchdog.sh +83 -3
- package/src/server.py +13 -8
- package/src/tools_coordination.py +3 -3
- package/src/tools_credentials.py +13 -13
- package/src/tools_menu.py +55 -37
- package/src/tools_reminders.py +1 -1
- package/src/tools_reminders_crud.py +21 -21
- package/src/tools_sessions.py +39 -31
- package/src/tools_task_history.py +11 -11
- package/tests/__init__.py +0 -0
- package/tests/conftest.py +71 -0
- package/tests/test_cognitive.py +204 -0
- package/tests/test_knowledge_graph.py +140 -0
- package/tests/test_migrations.py +111 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/cognitive.cpython-314.pyc +0 -0
- package/src/__pycache__/db.cpython-314.pyc +0 -0
- package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
- package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/server.cpython-314.pyc +0 -0
- package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
- package/src/cognitive.py +0 -3803
- package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
- package/src/db.py +0 -2925
- package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
- package/src/rules/core-rules 2.json +0 -329
- package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
package/README.md
CHANGED
|
@@ -198,7 +198,7 @@ This means long sessions (8+ hours) feel like one continuous conversation instea
|
|
|
198
198
|
|
|
199
199
|
## Cognitive Features
|
|
200
200
|
|
|
201
|
-
NEXO Brain provides
|
|
201
|
+
NEXO Brain provides **100+ MCP tools** implementing cognitive science concepts that go beyond basic memory:
|
|
202
202
|
|
|
203
203
|
### Input Pipeline
|
|
204
204
|
|
|
@@ -226,11 +226,13 @@ NEXO Brain provides 29 cognitive tools on top of the 78 base tools, totaling **1
|
|
|
226
226
|
|---------|-------------|
|
|
227
227
|
| **HyDE Query Expansion** | Generates hypothetical answer embeddings for richer semantic search. Instead of searching for "deploy error", it imagines what a helpful memory about deploy errors would look like, then searches for that. |
|
|
228
228
|
| **Hybrid Search (FTS5+BM25+RRF)** | Combines dense vector search with BM25 keyword search via Reciprocal Rank Fusion. Outperforms pure semantic search on precise terminology and code identifiers. |
|
|
229
|
+
| **KG Boost** | Knowledge Graph connection count influences retrieval ranking. Memories linked to well-connected entities (many edges) receive a logarithmic score bonus, surfacing contextually important facts higher. |
|
|
230
|
+
| **HNSW Vector Index** | Optional approximate nearest neighbor index (hnswlib). Activates automatically when memory count exceeds 10,000. Falls back to exact brute-force below that threshold — no configuration needed. |
|
|
229
231
|
| **Cross-Encoder Reranking** | After initial vector retrieval, a cross-encoder model rescores candidates for precision. The top-k results are reordered by true semantic relevance before being returned to the agent. |
|
|
230
232
|
| **Multi-Query Decomposition** | Complex questions are automatically split into sub-queries. Each component is retrieved independently, then fused for a higher-quality answer — improves recall on multi-faceted prompts. |
|
|
231
233
|
| **Temporal Indexing** | Memories are indexed by time in addition to semantics. Time-sensitive queries ("what did we decide last Tuesday?") use temporal proximity scoring alongside semantic similarity. |
|
|
232
234
|
| **Spreading Activation** | Graph-based co-activation network. Memories retrieved together reinforce each other's connections, building an associative web that improves over time. |
|
|
233
|
-
| **Recall Explanations** | Transparent score breakdown for every retrieval result. Shows exactly why a memory was returned: semantic similarity, recency, access frequency, and co-activation bonuses. |
|
|
235
|
+
| **Recall Explanations** | Transparent score breakdown for every retrieval result. Shows exactly why a memory was returned: semantic similarity, recency, access frequency, KG boost, and co-activation bonuses. |
|
|
234
236
|
|
|
235
237
|
### Proactive
|
|
236
238
|
|
|
@@ -412,7 +414,7 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
|
|
|
412
414
|
| Component | What | Where |
|
|
413
415
|
|-----------|------|-------|
|
|
414
416
|
| Cognitive engine | Python: fastembed, numpy, vector search | pip packages |
|
|
415
|
-
| MCP server |
|
|
417
|
+
| MCP server | 100+ tools for memory, cognition, learning, guard | ~/.nexo/ |
|
|
416
418
|
| Plugins | Guard, episodic memory, cognitive memory, entities, preferences | ~/.nexo/plugins/ |
|
|
417
419
|
| Hooks (6) | SessionStart briefing, Stop post-mortem, PostToolUse capture, PreCompact checkpoint, PostCompact recovery, Caffeinate | ~/.nexo/hooks/ |
|
|
418
420
|
| Reflection engine | Processes session buffer, extracts patterns, updates user model | ~/.nexo/scripts/ |
|
|
@@ -425,12 +427,23 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
|
|
|
425
427
|
|
|
426
428
|
- **macOS or Linux** (Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install))
|
|
427
429
|
- **Node.js 18+** (for the installer)
|
|
428
|
-
- **Claude Opus (latest version) strongly recommended.** NEXO Brain provides
|
|
430
|
+
- **Claude Opus (latest version) strongly recommended.** NEXO Brain provides 100+ MCP tools across 20 categories. This cognitive load requires a top-tier model with large context window. Smaller models (Haiku, Sonnet) may struggle with tool selection and produce inconsistent results. Opus handles all 100+ tools without hesitation.
|
|
429
431
|
- Python 3, Homebrew, and Claude Code are installed automatically if missing.
|
|
430
432
|
|
|
431
433
|
## Architecture
|
|
432
434
|
|
|
433
|
-
###
|
|
435
|
+
### Modular Package Structure (v1.5.0)
|
|
436
|
+
|
|
437
|
+
The core is organized into two Python packages:
|
|
438
|
+
|
|
439
|
+
| Package | Modules | Responsibility |
|
|
440
|
+
|---------|---------|----------------|
|
|
441
|
+
| `db/` | 11 modules (`_core`, `_schema`, `_sessions`, `_learnings`, `_episodic`, `_credentials`, `_entities`, `_evolution`, `_fts`, `_reminders`, `_tasks`) | All SQLite persistence: schema migrations, CRUD, FTS indexing |
|
|
442
|
+
| `cognitive/` | 6 modules (`_core`, `_memory`, `_ingest`, `_search`, `_decay`, `_trust`) | Cognitive engine: embeddings, RAG, decay, trust scoring |
|
|
443
|
+
|
|
444
|
+
The rest of the server (`server.py`, `tools_*.py`, `plugins/`) stays flat for clarity.
|
|
445
|
+
|
|
446
|
+
### 100+ MCP Tools across 20 Categories
|
|
434
447
|
|
|
435
448
|
| Category | Count | Tools | Purpose |
|
|
436
449
|
|----------|-------|-------|---------|
|
|
@@ -439,7 +452,7 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
|
|
|
439
452
|
| Cognitive Advanced | 8 | hyde_search, spread_activate, explain_recall, dream, prospect, hook_capture, pin, archive | Advanced retrieval, proactive, lifecycle |
|
|
440
453
|
| Guard | 3 | check, stats, log_repetition | Metacognitive error prevention |
|
|
441
454
|
| Episodic | 10 | change_log/search/commit, decision_log/outcome/search, review_queue, diary_write/read, recall | What happened and why |
|
|
442
|
-
| Sessions | 4 | startup, heartbeat, stop, status | Session lifecycle + context shift detection |
|
|
455
|
+
| Sessions | 4 | startup, heartbeat, stop, status | Session lifecycle + context shift detection + inter-terminal auto-inbox |
|
|
443
456
|
| Coordination | 7 | track, untrack, files, send, ask, answer, check_answer | Multi-session file coordination + messaging |
|
|
444
457
|
| Reminders | 5 | list, create, update, complete, delete | User's tasks and deadlines |
|
|
445
458
|
| Followups | 4 | create, update, complete, delete | System's autonomous verification tasks |
|
|
@@ -455,6 +468,7 @@ That's it. No need to run `claude` manually. Your operator will greet you immedi
|
|
|
455
468
|
| Adaptive & Somatic | 4 | adaptive_weights, adaptive_override, somatic_check, somatic_stats | Learned signal weights + pain memory per file |
|
|
456
469
|
| Knowledge Graph | 4 | kg_query, kg_path, kg_neighbors, kg_stats | Bi-temporal entity-relationship graph |
|
|
457
470
|
| Context Continuity | 2 | checkpoint_save, checkpoint_read | Auto-compaction session preservation |
|
|
471
|
+
| Claim Graph | — | (internal) | Atomic facts with provenance and contradiction detection |
|
|
458
472
|
|
|
459
473
|
### Plugin System
|
|
460
474
|
|
|
@@ -512,7 +526,7 @@ NEXO Brain is designed as an MCP server. Claude Code is the primary supported cl
|
|
|
512
526
|
npx nexo-brain
|
|
513
527
|
```
|
|
514
528
|
|
|
515
|
-
All
|
|
529
|
+
All 100+ tools are available immediately after installation. The installer configures Claude Code's `~/.claude/settings.json` automatically.
|
|
516
530
|
|
|
517
531
|
### OpenClaw
|
|
518
532
|
|
|
@@ -591,11 +605,11 @@ NEXO Brain builds on ideas from several open-source projects. We're grateful for
|
|
|
591
605
|
|
|
592
606
|
| Project | Inspired Features |
|
|
593
607
|
|---------|------------------|
|
|
594
|
-
|
|
|
595
|
-
|
|
|
596
|
-
|
|
|
597
|
-
|
|
|
598
|
-
|
|
|
608
|
+
| Vestige | HyDE query expansion, spreading activation, prediction error gating, memory dreaming, prospective memory |
|
|
609
|
+
| ShieldCortex | Security pipeline (4-layer memory poisoning defense) |
|
|
610
|
+
| Bicameral | Quarantine queue (trust promotion policy for new facts) |
|
|
611
|
+
| claude-mem | Hook auto-capture (extracting decisions and facts from conversations) |
|
|
612
|
+
| ClawMem | Co-activation reinforcement (memories retrieved together strengthen connections) |
|
|
599
613
|
|
|
600
614
|
## Support the Project
|
|
601
615
|
|
|
@@ -610,6 +624,43 @@ If NEXO Brain is useful to you, consider:
|
|
|
610
624
|
|
|
611
625
|
## Changelog
|
|
612
626
|
|
|
627
|
+
### v1.5.0 — Modular Core + Knowledge Graph Search (2026-03-29)
|
|
628
|
+
- **Architecture**: `db.py` refactored into `db/` package (11 modules: core, schema, sessions, learnings, episodic, credentials, entities, evolution, fts, reminders, tasks)
|
|
629
|
+
- **Architecture**: `cognitive.py` refactored into `cognitive/` package (6 modules: core, memory, ingest, search, decay, trust)
|
|
630
|
+
- **KG Boost**: Knowledge Graph connection count now influences search result ranking — well-connected entities surface higher in retrieval
|
|
631
|
+
- **HNSW Vector Index**: Optional approximate nearest neighbor acceleration (activates automatically above 10,000 memories, falls back to brute-force otherwise)
|
|
632
|
+
- **Claim Graph**: Decomposes blob memories into atomic verifiable facts with provenance, confidence scores, and contradiction detection
|
|
633
|
+
- **Inter-terminal Auto-inbox (D+)**: `nexo_startup` now accepts `claude_session_id` (Claude Code session UUID) — enables automatic inbox delivery between parallel terminals via PostToolUse hook + migration v13
|
|
634
|
+
- **Tests**: 24 pytest tests across 3 suites (cognitive, knowledge graph, migrations)
|
|
635
|
+
|
|
636
|
+
### v1.4.1 — Multi-AI Code Review (2026-03-29)
|
|
637
|
+
- **Fix**: 3 bugs found by GPT-5.4 (Codex CLI) + Gemini 2.5 (Gemini CLI) reviewing full codebase
|
|
638
|
+
- `session_diaries` → `session_diary` table name (smart startup silently failed)
|
|
639
|
+
- Quarantine contradiction logic distinguished confirmation from opposition
|
|
640
|
+
- Knowledge Graph timezone import crash
|
|
641
|
+
- **Security**: Memory sanitization prevents prompt injection via stored content
|
|
642
|
+
- **Migration #13**: Normalizes legacy status values (PENDIENTE→PENDING) on upgrade
|
|
643
|
+
|
|
644
|
+
### v1.4.0 — The Brain Dreams (2026-03-29)
|
|
645
|
+
- **Major**: All 9 nightly scripts migrated from Python word-overlap to CLI wrapper pattern
|
|
646
|
+
- Postmortem consolidator: opus understands patterns by meaning, not word overlap
|
|
647
|
+
- Sleep system: detects real duplicates via semantic understanding
|
|
648
|
+
- Daily synthesis: opus prioritizes what matters for tomorrow
|
|
649
|
+
- Self-audit: interprets findings for root cause analysis
|
|
650
|
+
- Evolution: prompt reduced 95% (45K → 2.2K chars) — CLI investigates using tools
|
|
651
|
+
- **Stop Hook v8**: Session-scoped tool counting (not day-wide), buffer fallback removed
|
|
652
|
+
- **Guard**: Behavioral rules section surfaces most-violated rules at session start
|
|
653
|
+
- **Followup hygiene**: Weekly cleanup script normalizes statuses, flags stale items
|
|
654
|
+
- 8 missing core scripts + 1 plugin added to repository
|
|
655
|
+
|
|
656
|
+
### v1.3.0 — Evolution System (2026-03-28)
|
|
657
|
+
- **New**: Self-improvement cycle — NEXO proposes and applies improvements weekly
|
|
658
|
+
- Dual-mode: auto (low-risk changes) and review (owner approval required)
|
|
659
|
+
- Circuit breaker, snapshot/rollback, immutable file protection
|
|
660
|
+
|
|
661
|
+
### v1.2.3 — AGPL-3.0 License (2026-03-27)
|
|
662
|
+
- License changed from MIT to AGPL-3.0
|
|
663
|
+
|
|
613
664
|
### v1.2.1 — Stop Hook Hotfix (2026-03-27)
|
|
614
665
|
- **Fix**: v1.2.0 deleted the flag on approve, causing infinite block loops if session didn't close immediately
|
|
615
666
|
- **Fix**: Removed TTL on flag — it persists until SessionStart cleans it up next session
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
|
-
"description": "NEXO — Cognitive co-operator for Claude Code. Atkinson-Shiffrin memory, semantic RAG, trust scoring, and metacognitive error prevention.",
|
|
5
|
+
"description": "NEXO — Cognitive co-operator for Claude Code. Atkinson-Shiffrin memory, semantic RAG, knowledge graph, HNSW vector indexing, trust scoring, and metacognitive error prevention.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nexo-brain": "./bin/nexo-brain.js"
|
|
8
8
|
},
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"ai-assistant",
|
|
15
15
|
"vector-search",
|
|
16
16
|
"atkinson-shiffrin",
|
|
17
|
+
"knowledge-graph",
|
|
18
|
+
"hnsw",
|
|
19
|
+
"claim-graph",
|
|
17
20
|
"openclaw",
|
|
18
21
|
"openclaw-plugin"
|
|
19
22
|
],
|
|
@@ -30,6 +33,7 @@
|
|
|
30
33
|
"bin/",
|
|
31
34
|
"src/",
|
|
32
35
|
"templates/",
|
|
33
|
-
"scripts/"
|
|
36
|
+
"scripts/",
|
|
37
|
+
"tests/"
|
|
34
38
|
]
|
|
35
39
|
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""NEXO Claim Graph — Atomic claims with provenance and contradiction detection.
|
|
2
|
+
|
|
3
|
+
Decomposes blob memories into individual verifiable facts. Each claim has a
|
|
4
|
+
source memory, confidence score, and can be linked to contradicting claims.
|
|
5
|
+
|
|
6
|
+
Tables (created in cognitive.db alongside KG):
|
|
7
|
+
- claims: atomic facts with provenance
|
|
8
|
+
- claim_links: relationships between claims (supports, contradicts, refines)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import sqlite3
|
|
13
|
+
import numpy as np
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_db():
|
|
19
|
+
"""Get cognitive.db connection."""
|
|
20
|
+
import cognitive
|
|
21
|
+
return cognitive._get_db()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _embed(text: str) -> np.ndarray:
|
|
25
|
+
import cognitive
|
|
26
|
+
return cognitive.embed(text)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _cosine_similarity(a, b) -> float:
|
|
30
|
+
import cognitive
|
|
31
|
+
return cognitive.cosine_similarity(a, b)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _array_to_blob(arr: np.ndarray) -> bytes:
|
|
35
|
+
return arr.astype(np.float32).tobytes()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _blob_to_array(blob: bytes) -> np.ndarray:
|
|
39
|
+
return np.frombuffer(blob, dtype=np.float32)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def init_tables():
|
|
43
|
+
"""Create claim graph tables if they don't exist."""
|
|
44
|
+
db = _get_db()
|
|
45
|
+
db.executescript("""
|
|
46
|
+
CREATE TABLE IF NOT EXISTS claims (
|
|
47
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
48
|
+
text TEXT NOT NULL,
|
|
49
|
+
embedding BLOB,
|
|
50
|
+
source_type TEXT NOT NULL DEFAULT '',
|
|
51
|
+
source_id TEXT NOT NULL DEFAULT '',
|
|
52
|
+
source_memory_store TEXT DEFAULT '',
|
|
53
|
+
source_memory_id INTEGER DEFAULT 0,
|
|
54
|
+
confidence REAL DEFAULT 1.0,
|
|
55
|
+
verification_status TEXT DEFAULT 'unverified',
|
|
56
|
+
verified_at TEXT,
|
|
57
|
+
domain TEXT DEFAULT '',
|
|
58
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
59
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
CREATE TABLE IF NOT EXISTS claim_links (
|
|
63
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
64
|
+
source_claim_id INTEGER NOT NULL REFERENCES claims(id),
|
|
65
|
+
target_claim_id INTEGER NOT NULL REFERENCES claims(id),
|
|
66
|
+
relation TEXT NOT NULL,
|
|
67
|
+
confidence REAL DEFAULT 1.0,
|
|
68
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
69
|
+
UNIQUE(source_claim_id, target_claim_id, relation)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_claims_source ON claims(source_type, source_id);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_claims_domain ON claims(domain);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_claims_status ON claims(verification_status);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_claim_links_source ON claim_links(source_claim_id);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_claim_links_target ON claim_links(target_claim_id);
|
|
77
|
+
""")
|
|
78
|
+
db.commit()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def add_claim(text: str, source_type: str = "", source_id: str = "",
|
|
82
|
+
source_memory_store: str = "", source_memory_id: int = 0,
|
|
83
|
+
confidence: float = 1.0, domain: str = "") -> dict:
|
|
84
|
+
"""Add an atomic claim to the graph.
|
|
85
|
+
|
|
86
|
+
Returns the claim dict with id, or existing claim if duplicate detected.
|
|
87
|
+
"""
|
|
88
|
+
db = _get_db()
|
|
89
|
+
init_tables()
|
|
90
|
+
|
|
91
|
+
# Embed the claim
|
|
92
|
+
vec = _embed(text)
|
|
93
|
+
blob = _array_to_blob(vec)
|
|
94
|
+
|
|
95
|
+
# Check for near-duplicate claims (similarity > 0.92)
|
|
96
|
+
existing = find_similar_claims(text, threshold=0.92, limit=1)
|
|
97
|
+
if existing:
|
|
98
|
+
# Update confidence if new source provides additional evidence
|
|
99
|
+
dup = existing[0]
|
|
100
|
+
new_conf = min(1.0, dup["confidence"] + 0.1)
|
|
101
|
+
db.execute("UPDATE claims SET confidence = ?, updated_at = datetime('now') WHERE id = ?",
|
|
102
|
+
(new_conf, dup["id"]))
|
|
103
|
+
db.commit()
|
|
104
|
+
return {"id": dup["id"], "action": "merged", "confidence": new_conf}
|
|
105
|
+
|
|
106
|
+
cursor = db.execute(
|
|
107
|
+
"""INSERT INTO claims (text, embedding, source_type, source_id,
|
|
108
|
+
source_memory_store, source_memory_id, confidence, domain)
|
|
109
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
110
|
+
(text, blob, source_type, source_id, source_memory_store,
|
|
111
|
+
source_memory_id, confidence, domain)
|
|
112
|
+
)
|
|
113
|
+
db.commit()
|
|
114
|
+
return {"id": cursor.lastrowid, "action": "added", "confidence": confidence}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def find_similar_claims(text: str, threshold: float = 0.8, limit: int = 10) -> list[dict]:
|
|
118
|
+
"""Find claims similar to the given text using cosine similarity."""
|
|
119
|
+
db = _get_db()
|
|
120
|
+
init_tables()
|
|
121
|
+
|
|
122
|
+
query_vec = _embed(text)
|
|
123
|
+
if np.linalg.norm(query_vec) == 0:
|
|
124
|
+
return []
|
|
125
|
+
|
|
126
|
+
rows = db.execute("SELECT * FROM claims").fetchall()
|
|
127
|
+
results = []
|
|
128
|
+
for row in rows:
|
|
129
|
+
if not row["embedding"]:
|
|
130
|
+
continue
|
|
131
|
+
vec = _blob_to_array(row["embedding"])
|
|
132
|
+
score = _cosine_similarity(query_vec, vec)
|
|
133
|
+
if score >= threshold:
|
|
134
|
+
d = dict(row)
|
|
135
|
+
d.pop("embedding", None)
|
|
136
|
+
d["similarity"] = round(score, 4)
|
|
137
|
+
results.append(d)
|
|
138
|
+
|
|
139
|
+
results.sort(key=lambda x: x["similarity"], reverse=True)
|
|
140
|
+
return results[:limit]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def link_claims(source_id: int, target_id: int, relation: str,
|
|
144
|
+
confidence: float = 1.0) -> dict:
|
|
145
|
+
"""Create a relationship between two claims.
|
|
146
|
+
|
|
147
|
+
Relations:
|
|
148
|
+
- 'supports': source provides evidence for target
|
|
149
|
+
- 'contradicts': source contradicts target
|
|
150
|
+
- 'refines': source is a more specific version of target
|
|
151
|
+
- 'supersedes': source replaces target (target is outdated)
|
|
152
|
+
"""
|
|
153
|
+
db = _get_db()
|
|
154
|
+
init_tables()
|
|
155
|
+
|
|
156
|
+
valid_relations = {"supports", "contradicts", "refines", "supersedes"}
|
|
157
|
+
if relation not in valid_relations:
|
|
158
|
+
return {"error": f"Invalid relation. Use: {valid_relations}"}
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
db.execute(
|
|
162
|
+
"INSERT OR IGNORE INTO claim_links (source_claim_id, target_claim_id, relation, confidence) "
|
|
163
|
+
"VALUES (?, ?, ?, ?)",
|
|
164
|
+
(source_id, target_id, relation, confidence)
|
|
165
|
+
)
|
|
166
|
+
db.commit()
|
|
167
|
+
|
|
168
|
+
# If contradiction, update verification status
|
|
169
|
+
if relation == "contradicts":
|
|
170
|
+
db.execute(
|
|
171
|
+
"UPDATE claims SET verification_status = 'contradicted', updated_at = datetime('now') WHERE id = ?",
|
|
172
|
+
(target_id,)
|
|
173
|
+
)
|
|
174
|
+
db.commit()
|
|
175
|
+
|
|
176
|
+
return {"source": source_id, "target": target_id, "relation": relation}
|
|
177
|
+
except Exception as e:
|
|
178
|
+
return {"error": str(e)}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def detect_contradictions(claim_id: int) -> list[dict]:
|
|
182
|
+
"""Find claims that potentially contradict the given claim."""
|
|
183
|
+
db = _get_db()
|
|
184
|
+
init_tables()
|
|
185
|
+
|
|
186
|
+
claim = db.execute("SELECT * FROM claims WHERE id = ?", (claim_id,)).fetchone()
|
|
187
|
+
if not claim:
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
claim_vec = _blob_to_array(claim["embedding"])
|
|
191
|
+
|
|
192
|
+
# Find similar claims from different sources
|
|
193
|
+
rows = db.execute(
|
|
194
|
+
"SELECT * FROM claims WHERE id != ? AND domain = ?",
|
|
195
|
+
(claim_id, claim["domain"])
|
|
196
|
+
).fetchall()
|
|
197
|
+
|
|
198
|
+
contradictions = []
|
|
199
|
+
for row in rows:
|
|
200
|
+
if not row["embedding"]:
|
|
201
|
+
continue
|
|
202
|
+
vec = _blob_to_array(row["embedding"])
|
|
203
|
+
sim = _cosine_similarity(claim_vec, vec)
|
|
204
|
+
# High similarity but different source = potential contradiction
|
|
205
|
+
if 0.6 <= sim <= 0.9 and row["source_id"] != claim["source_id"]:
|
|
206
|
+
d = dict(row)
|
|
207
|
+
d.pop("embedding", None)
|
|
208
|
+
d["similarity"] = round(sim, 4)
|
|
209
|
+
contradictions.append(d)
|
|
210
|
+
|
|
211
|
+
return contradictions
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def verify_claim(claim_id: int, status: str = "confirmed") -> dict:
|
|
215
|
+
"""Update verification status of a claim.
|
|
216
|
+
|
|
217
|
+
Status: 'confirmed', 'contradicted', 'outdated', 'unverified'
|
|
218
|
+
"""
|
|
219
|
+
db = _get_db()
|
|
220
|
+
init_tables()
|
|
221
|
+
|
|
222
|
+
valid = {"confirmed", "contradicted", "outdated", "unverified"}
|
|
223
|
+
if status not in valid:
|
|
224
|
+
return {"error": f"Invalid status. Use: {valid}"}
|
|
225
|
+
|
|
226
|
+
db.execute(
|
|
227
|
+
"UPDATE claims SET verification_status = ?, verified_at = datetime('now'), "
|
|
228
|
+
"updated_at = datetime('now') WHERE id = ?",
|
|
229
|
+
(status, claim_id)
|
|
230
|
+
)
|
|
231
|
+
db.commit()
|
|
232
|
+
row = db.execute("SELECT * FROM claims WHERE id = ?", (claim_id,)).fetchone()
|
|
233
|
+
if row:
|
|
234
|
+
d = dict(row)
|
|
235
|
+
d.pop("embedding", None)
|
|
236
|
+
return d
|
|
237
|
+
return {"error": f"Claim {claim_id} not found"}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_claim(claim_id: int) -> Optional[dict]:
|
|
241
|
+
"""Get a single claim with its links."""
|
|
242
|
+
db = _get_db()
|
|
243
|
+
init_tables()
|
|
244
|
+
|
|
245
|
+
row = db.execute("SELECT * FROM claims WHERE id = ?", (claim_id,)).fetchone()
|
|
246
|
+
if not row:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
d = dict(row)
|
|
250
|
+
d.pop("embedding", None)
|
|
251
|
+
|
|
252
|
+
# Get links
|
|
253
|
+
outgoing = db.execute(
|
|
254
|
+
"SELECT cl.*, c.text as target_text FROM claim_links cl "
|
|
255
|
+
"JOIN claims c ON c.id = cl.target_claim_id "
|
|
256
|
+
"WHERE cl.source_claim_id = ?", (claim_id,)
|
|
257
|
+
).fetchall()
|
|
258
|
+
incoming = db.execute(
|
|
259
|
+
"SELECT cl.*, c.text as source_text FROM claim_links cl "
|
|
260
|
+
"JOIN claims c ON c.id = cl.source_claim_id "
|
|
261
|
+
"WHERE cl.target_claim_id = ?", (claim_id,)
|
|
262
|
+
).fetchall()
|
|
263
|
+
|
|
264
|
+
d["links_out"] = [dict(r) for r in outgoing]
|
|
265
|
+
d["links_in"] = [dict(r) for r in incoming]
|
|
266
|
+
return d
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def search_claims(query: str = "", domain: str = "", status: str = "",
|
|
270
|
+
limit: int = 20) -> list[dict]:
|
|
271
|
+
"""Search claims by text, domain, and/or status."""
|
|
272
|
+
db = _get_db()
|
|
273
|
+
init_tables()
|
|
274
|
+
|
|
275
|
+
if query:
|
|
276
|
+
return find_similar_claims(query, threshold=0.5, limit=limit)
|
|
277
|
+
|
|
278
|
+
conditions = []
|
|
279
|
+
params = []
|
|
280
|
+
if domain:
|
|
281
|
+
conditions.append("domain = ?")
|
|
282
|
+
params.append(domain)
|
|
283
|
+
if status:
|
|
284
|
+
conditions.append("verification_status = ?")
|
|
285
|
+
params.append(status)
|
|
286
|
+
|
|
287
|
+
where = " AND ".join(conditions) if conditions else "1=1"
|
|
288
|
+
rows = db.execute(
|
|
289
|
+
f"SELECT id, text, source_type, source_id, confidence, verification_status, "
|
|
290
|
+
f"domain, created_at FROM claims WHERE {where} ORDER BY created_at DESC LIMIT ?",
|
|
291
|
+
params + [limit]
|
|
292
|
+
).fetchall()
|
|
293
|
+
return [dict(r) for r in rows]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def stats() -> dict:
|
|
297
|
+
"""Claim graph statistics."""
|
|
298
|
+
db = _get_db()
|
|
299
|
+
init_tables()
|
|
300
|
+
|
|
301
|
+
total = db.execute("SELECT COUNT(*) FROM claims").fetchone()[0]
|
|
302
|
+
by_status = {}
|
|
303
|
+
for row in db.execute(
|
|
304
|
+
"SELECT verification_status, COUNT(*) as cnt FROM claims GROUP BY verification_status"
|
|
305
|
+
).fetchall():
|
|
306
|
+
by_status[row["verification_status"]] = row["cnt"]
|
|
307
|
+
by_domain = {}
|
|
308
|
+
for row in db.execute(
|
|
309
|
+
"SELECT domain, COUNT(*) as cnt FROM claims WHERE domain != '' GROUP BY domain ORDER BY cnt DESC LIMIT 10"
|
|
310
|
+
).fetchall():
|
|
311
|
+
by_domain[row["domain"]] = row["cnt"]
|
|
312
|
+
links = db.execute("SELECT COUNT(*) FROM claim_links").fetchone()[0]
|
|
313
|
+
contradictions = db.execute(
|
|
314
|
+
"SELECT COUNT(*) FROM claim_links WHERE relation = 'contradicts'"
|
|
315
|
+
).fetchone()[0]
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
"total_claims": total,
|
|
319
|
+
"by_status": by_status,
|
|
320
|
+
"by_domain": by_domain,
|
|
321
|
+
"total_links": links,
|
|
322
|
+
"contradictions": contradictions,
|
|
323
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""NEXO Cognitive Engine — Modular vector memory with Atkinson-Shiffrin model.
|
|
2
|
+
|
|
3
|
+
This package replaces the monolithic cognitive.py. All public functions and
|
|
4
|
+
constants are re-exported here for full backwards compatibility:
|
|
5
|
+
import cognitive
|
|
6
|
+
cognitive.search("query")
|
|
7
|
+
cognitive.embed("text")
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Core: DB, embedding, cosine, constants, tables, redaction
|
|
11
|
+
from cognitive._core import (
|
|
12
|
+
COGNITIVE_DB, EMBEDDING_DIM, LAMBDA_STM, LAMBDA_LTM,
|
|
13
|
+
PE_GATE_REJECT, PE_GATE_REFINE, _gate_stats,
|
|
14
|
+
DISCRIMINATING_ENTITIES,
|
|
15
|
+
POSITIVE_SIGNALS, NEGATIVE_SIGNALS, URGENCY_SIGNALS,
|
|
16
|
+
_get_db, _init_tables, _migrate_lifecycle, _migrate_co_activation,
|
|
17
|
+
_auto_migrate_embeddings,
|
|
18
|
+
_get_model, _get_reranker, rerank_results,
|
|
19
|
+
embed, cosine_similarity, _array_to_blob, _blob_to_array,
|
|
20
|
+
extract_temporal_date, redact_secrets,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Search
|
|
24
|
+
from cognitive._search import (
|
|
25
|
+
search, bm25_search, hyde_expand_query,
|
|
26
|
+
record_co_activation,
|
|
27
|
+
_kg_boost_results, _apply_temporal_boost,
|
|
28
|
+
create_trigger, check_triggers, list_triggers, delete_trigger, rearm_trigger,
|
|
29
|
+
# Constants
|
|
30
|
+
CO_ACTIVATION_DECAY, CO_ACTIVATION_BOOST, CO_ACTIVATION_MIN_STRENGTH,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Ingest
|
|
34
|
+
from cognitive._ingest import (
|
|
35
|
+
ingest, ingest_session, ingest_to_ltm, ingest_sensory,
|
|
36
|
+
prediction_error_gate, get_gate_stats, detect_patterns,
|
|
37
|
+
process_quarantine, quarantine_list, quarantine_promote, quarantine_reject, quarantine_stats,
|
|
38
|
+
security_scan,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Decay and maintenance
|
|
42
|
+
from cognitive._decay import (
|
|
43
|
+
apply_decay, promote_stm_to_ltm, gc_stm, gc_test_memories,
|
|
44
|
+
gc_sensory, gc_ltm_dormant, dream_cycle,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Trust and sentiment
|
|
48
|
+
from cognitive._trust import (
|
|
49
|
+
get_trust_events, auto_detect_trust_events,
|
|
50
|
+
detect_sentiment, log_sentiment,
|
|
51
|
+
get_trust_score, adjust_trust, get_trust_history,
|
|
52
|
+
detect_dissonance, resolve_dissonance, check_correction_fatigue,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Memory operations
|
|
56
|
+
from cognitive._memory import (
|
|
57
|
+
format_results, get_metrics, check_repeat_errors, rehearse_by_content,
|
|
58
|
+
consolidate_semantic, get_siblings,
|
|
59
|
+
get_stats, set_lifecycle, auto_merge_duplicates,
|
|
60
|
+
somatic_accumulate, somatic_guard_decay, somatic_nightly_decay,
|
|
61
|
+
somatic_project_events, somatic_get_risk, somatic_top_risks,
|
|
62
|
+
)
|